use ll_sparql_parser::{
ast::{AstNode, TriplesBlock},
syntax_kind::SyntaxKind,
};
use crate::server::{
Server,
lsp::{
CompletionItemKind, CompletionList,
textdocument::{Position, Range, TextEdit},
},
};
use super::super::environment::{CompletionEnvironment, CompletionLocation};
use super::CompletionTransformer;
pub struct SemicolonTransformer {
subject: String,
dot_start: Position,
indent: String,
replace_range: Range,
}
impl SemicolonTransformer {
pub(in crate::server::message_handler::completion) fn try_from_env(
server: &Server,
env: &CompletionEnvironment,
) -> Option<Self> {
if !matches!(env.location, CompletionLocation::Subject)
|| !server.settings.completion.same_subject_semicolon
{
return None;
}
let document_text = env.truncated_tree.text().to_string();
let replace_range = env.replace_range.clone();
let triple_block = TriplesBlock::cast(env.anchor_token.as_ref()?.parent()?)?;
let subject = triple_block.triples().first()?.subject()?;
let dot_start = Position::from_byte_index(
triple_block
.syntax()
.last_token()
.and_then(|last| (last.kind() == SyntaxKind::Dot).then_some(last.text_range()))?
.start(),
&document_text,
)?;
let verb_start = triple_block
.triples()
.first()?
.properties_list_path()?
.properties()
.first()?
.verb
.syntax()
.text_range()
.start()
.into();
let line_start = document_text[..verb_start]
.rfind('\n')
.map(|pos| pos + 1)
.unwrap_or(0);
let indent = " ".repeat(verb_start - line_start);
Some(Self {
replace_range,
subject: subject.text(),
dot_start,
indent,
})
}
}
impl CompletionTransformer for SemicolonTransformer {
fn transform(&self, list: &mut CompletionList) {
for item in list.items.iter_mut() {
if item
.kind
.as_ref()
.is_none_or(|kind| kind != &CompletionItemKind::Variable)
{
continue;
}
let item_value = item
.text_edit
.as_ref()
.map(|te| te.new_text.trim().to_string())
.or_else(|| item.insert_text.as_ref().map(|s| s.trim().to_string()))
.map(|insert_text| {
if item
.kind
.as_ref()
.is_some_and(|kind| kind == &CompletionItemKind::Variable)
&& !insert_text.starts_with("?")
{
format!("?{}", insert_text)
} else {
insert_text
}
})
.unwrap_or_default();
if item_value != self.subject {
continue;
}
item.text_edit = Some(TextEdit {
range: self.replace_range.clone(),
new_text: String::new(),
});
item.sort_text = Some("00000".to_string());
let dot_to_cursor_range = Range {
start: self.dot_start,
end: self.replace_range.start,
};
item.additional_text_edits = Some(vec![TextEdit {
range: dot_to_cursor_range,
new_text: format!(";\n{}", self.indent),
}]);
item.detail = Some("Continue with semicolon".to_string());
}
}
}