Skip to content

Commit 0fac165

Browse files
bors[bot]Veykril
andauthored
Merge #8410
8410: Use CompletionTextEdit::InsertAndReplace if supported by the client r=Veykril a=Veykril Fixes #8404, Fixes #3130 Co-authored-by: Lukas Wirth <[email protected]>
2 parents e357b6b + 8fa3011 commit 0fac165

File tree

5 files changed

+50
-20
lines changed

5 files changed

+50
-20
lines changed

crates/ide_completion/src/item.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub struct CompletionItem {
2929
/// Range of identifier that is being completed.
3030
///
3131
/// It should be used primarily for UI, but we also use this to convert
32-
/// genetic TextEdit into LSP's completion edit (see conv.rs).
32+
/// generic TextEdit into LSP's completion edit (see conv.rs).
3333
///
3434
/// `source_range` must contain the completion offset. `insert_text` should
3535
/// start with what `source_range` points to, or VSCode will filter out the

crates/rust-analyzer/src/config.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,19 @@ impl Config {
656656
pub fn code_lens_refresh(&self) -> bool {
657657
try_or!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?, false)
658658
}
659+
pub fn insert_replace_support(&self) -> bool {
660+
try_or!(
661+
self.caps
662+
.text_document
663+
.as_ref()?
664+
.completion
665+
.as_ref()?
666+
.completion_item
667+
.as_ref()?
668+
.insert_replace_support?,
669+
false
670+
)
671+
}
659672
}
660673

661674
#[derive(Deserialize, Debug, Clone)]

crates/rust-analyzer/src/handlers.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -664,10 +664,13 @@ pub(crate) fn handle_completion(
664664
};
665665
let line_index = snap.file_line_index(position.file_id)?;
666666

667+
let insert_replace_support =
668+
snap.config.insert_replace_support().then(|| text_document_position.position);
667669
let items: Vec<CompletionItem> = items
668670
.into_iter()
669671
.flat_map(|item| {
670-
let mut new_completion_items = to_proto::completion_item(&line_index, item.clone());
672+
let mut new_completion_items =
673+
to_proto::completion_item(insert_replace_support, &line_index, item.clone());
671674

672675
if completion_config.enable_imports_on_the_fly {
673676
for new_item in &mut new_completion_items {

crates/rust-analyzer/src/lsp_utils.rs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,16 @@ pub(crate) fn all_edits_are_disjoint(
150150
edit_ranges.push(edit.range);
151151
}
152152
Some(lsp_types::CompletionTextEdit::InsertAndReplace(edit)) => {
153-
edit_ranges.push(edit.insert);
154-
edit_ranges.push(edit.replace);
153+
let replace = edit.replace;
154+
let insert = edit.insert;
155+
if replace.start != insert.start
156+
|| insert.start > insert.end
157+
|| insert.end > replace.end
158+
{
159+
// insert has to be a prefix of replace but it is not
160+
return false;
161+
}
162+
edit_ranges.push(replace);
155163
}
156164
None => {}
157165
}
@@ -310,18 +318,6 @@ mod tests {
310318
"Completion with disjoint edits fails the validation even with empty extra edits"
311319
);
312320

313-
completion_with_joint_edits.text_edit =
314-
Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
315-
new_text: "new_text".to_string(),
316-
insert: disjoint_edit.range,
317-
replace: joint_edit.range,
318-
}));
319-
completion_with_joint_edits.additional_text_edits = None;
320-
assert!(
321-
!all_edits_are_disjoint(&completion_with_joint_edits, &[]),
322-
"Completion with disjoint edits fails the validation even with empty extra edits"
323-
);
324-
325321
completion_with_joint_edits.text_edit =
326322
Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
327323
new_text: "new_text".to_string(),

crates/rust-analyzer/src/to_proto.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,23 @@ pub(crate) fn text_edit(line_index: &LineIndex, indel: Indel) -> lsp_types::Text
145145
lsp_types::TextEdit { range, new_text }
146146
}
147147

148+
pub(crate) fn completion_text_edit(
149+
line_index: &LineIndex,
150+
insert_replace_support: Option<lsp_types::Position>,
151+
indel: Indel,
152+
) -> lsp_types::CompletionTextEdit {
153+
let text_edit = text_edit(line_index, indel);
154+
match insert_replace_support {
155+
Some(cursor_pos) => lsp_types::InsertReplaceEdit {
156+
new_text: text_edit.new_text,
157+
insert: lsp_types::Range { start: text_edit.range.start, end: cursor_pos },
158+
replace: text_edit.range,
159+
}
160+
.into(),
161+
None => text_edit.into(),
162+
}
163+
}
164+
148165
pub(crate) fn snippet_text_edit(
149166
line_index: &LineIndex,
150167
is_snippet: bool,
@@ -179,6 +196,7 @@ pub(crate) fn snippet_text_edit_vec(
179196
}
180197

181198
pub(crate) fn completion_item(
199+
insert_replace_support: Option<lsp_types::Position>,
182200
line_index: &LineIndex,
183201
item: CompletionItem,
184202
) -> Vec<lsp_types::CompletionItem> {
@@ -190,15 +208,15 @@ pub(crate) fn completion_item(
190208
for indel in item.text_edit().iter() {
191209
if indel.delete.contains_range(source_range) {
192210
text_edit = Some(if indel.delete == source_range {
193-
self::text_edit(line_index, indel.clone())
211+
self::completion_text_edit(line_index, insert_replace_support, indel.clone())
194212
} else {
195213
assert!(source_range.end() == indel.delete.end());
196214
let range1 = TextRange::new(indel.delete.start(), source_range.start());
197215
let range2 = source_range;
198216
let indel1 = Indel::replace(range1, String::new());
199217
let indel2 = Indel::replace(range2, indel.insert.clone());
200218
additional_text_edits.push(self::text_edit(line_index, indel1));
201-
self::text_edit(line_index, indel2)
219+
self::completion_text_edit(line_index, insert_replace_support, indel2)
202220
})
203221
} else {
204222
assert!(source_range.intersect(indel.delete).is_none());
@@ -213,7 +231,7 @@ pub(crate) fn completion_item(
213231
detail: item.detail().map(|it| it.to_string()),
214232
filter_text: Some(item.lookup().to_string()),
215233
kind: item.kind().map(completion_item_kind),
216-
text_edit: Some(text_edit.into()),
234+
text_edit: Some(text_edit),
217235
additional_text_edits: Some(additional_text_edits),
218236
documentation: item.documentation().map(documentation),
219237
deprecated: Some(item.deprecated()),
@@ -1136,7 +1154,7 @@ mod tests {
11361154
.unwrap()
11371155
.into_iter()
11381156
.filter(|c| c.label().ends_with("arg"))
1139-
.map(|c| completion_item(&line_index, c))
1157+
.map(|c| completion_item(None, &line_index, c))
11401158
.flat_map(|comps| comps.into_iter().map(|c| (c.label, c.sort_text)))
11411159
.collect();
11421160
expect_test::expect![[r#"

0 commit comments

Comments
 (0)