};
let line_index = snap.file_line_index(position.file_id)?;
+ let insert_replace_support =
+ snap.config.insert_replace_support().then(|| text_document_position.position);
let items: Vec<CompletionItem> = items
.into_iter()
.flat_map(|item| {
- let mut new_completion_items = to_proto::completion_item(&line_index, item.clone());
+ let mut new_completion_items =
+ to_proto::completion_item(insert_replace_support, &line_index, item.clone());
if completion_config.enable_imports_on_the_fly {
for new_item in &mut new_completion_items {
edit_ranges.push(edit.range);
}
Some(lsp_types::CompletionTextEdit::InsertAndReplace(edit)) => {
- edit_ranges.push(edit.insert);
- edit_ranges.push(edit.replace);
+ let replace = edit.replace;
+ let insert = edit.insert;
+ if replace.start != insert.start
+ || insert.start > insert.end
+ || insert.end > replace.end
+ {
+ // insert has to be a prefix of replace but it is not
+ return false;
+ }
+ edit_ranges.push(replace);
}
None => {}
}
"Completion with disjoint edits fails the validation even with empty extra edits"
);
- completion_with_joint_edits.text_edit =
- Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
- new_text: "new_text".to_string(),
- insert: disjoint_edit.range,
- replace: joint_edit.range,
- }));
- completion_with_joint_edits.additional_text_edits = None;
- assert!(
- !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
- "Completion with disjoint edits fails the validation even with empty extra edits"
- );
-
completion_with_joint_edits.text_edit =
Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
new_text: "new_text".to_string(),
lsp_types::TextEdit { range, new_text }
}
+pub(crate) fn completion_text_edit(
+ line_index: &LineIndex,
+ insert_replace_support: Option<lsp_types::Position>,
+ indel: Indel,
+) -> lsp_types::CompletionTextEdit {
+ let text_edit = text_edit(line_index, indel);
+ match insert_replace_support {
+ Some(cursor_pos) => lsp_types::InsertReplaceEdit {
+ new_text: text_edit.new_text,
+ insert: lsp_types::Range { start: text_edit.range.start, end: cursor_pos },
+ replace: text_edit.range,
+ }
+ .into(),
+ None => text_edit.into(),
+ }
+}
+
pub(crate) fn snippet_text_edit(
line_index: &LineIndex,
is_snippet: bool,
}
pub(crate) fn completion_item(
+ insert_replace_support: Option<lsp_types::Position>,
line_index: &LineIndex,
item: CompletionItem,
) -> Vec<lsp_types::CompletionItem> {
for indel in item.text_edit().iter() {
if indel.delete.contains_range(source_range) {
text_edit = Some(if indel.delete == source_range {
- self::text_edit(line_index, indel.clone())
+ self::completion_text_edit(line_index, insert_replace_support, indel.clone())
} else {
assert!(source_range.end() == indel.delete.end());
let range1 = TextRange::new(indel.delete.start(), source_range.start());
let indel1 = Indel::replace(range1, String::new());
let indel2 = Indel::replace(range2, indel.insert.clone());
additional_text_edits.push(self::text_edit(line_index, indel1));
- self::text_edit(line_index, indel2)
+ self::completion_text_edit(line_index, insert_replace_support, indel2)
})
} else {
assert!(source_range.intersect(indel.delete).is_none());
detail: item.detail().map(|it| it.to_string()),
filter_text: Some(item.lookup().to_string()),
kind: item.kind().map(completion_item_kind),
- text_edit: Some(text_edit.into()),
+ text_edit: Some(text_edit),
additional_text_edits: Some(additional_text_edits),
documentation: item.documentation().map(documentation),
deprecated: Some(item.deprecated()),
.unwrap()
.into_iter()
.filter(|c| c.label().ends_with("arg"))
- .map(|c| completion_item(&line_index, c))
+ .map(|c| completion_item(None, &line_index, c))
.flat_map(|comps| comps.into_iter().map(|c| (c.label, c.sort_text)))
.collect();
expect_test::expect![[r#"