diff options
-rw-r--r-- | crates/ide_completion/src/item.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/config.rs | 13 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 5 | ||||
-rw-r--r-- | crates/rust-analyzer/src/lsp_utils.rs | 24 | ||||
-rw-r--r-- | crates/rust-analyzer/src/to_proto.rs | 26 |
5 files changed, 50 insertions, 20 deletions
diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs index cc4ac9ea2..16991b688 100644 --- a/crates/ide_completion/src/item.rs +++ b/crates/ide_completion/src/item.rs | |||
@@ -29,7 +29,7 @@ pub struct CompletionItem { | |||
29 | /// Range of identifier that is being completed. | 29 | /// Range of identifier that is being completed. |
30 | /// | 30 | /// |
31 | /// It should be used primarily for UI, but we also use this to convert | 31 | /// 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). |
33 | /// | 33 | /// |
34 | /// `source_range` must contain the completion offset. `insert_text` should | 34 | /// `source_range` must contain the completion offset. `insert_text` should |
35 | /// start with what `source_range` points to, or VSCode will filter out the | 35 | /// start with what `source_range` points to, or VSCode will filter out the |
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index e012b4452..f809667e9 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs | |||
@@ -656,6 +656,19 @@ impl Config { | |||
656 | pub fn code_lens_refresh(&self) -> bool { | 656 | pub fn code_lens_refresh(&self) -> bool { |
657 | try_or!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?, false) | 657 | try_or!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?, false) |
658 | } | 658 | } |
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 | } | ||
659 | } | 672 | } |
660 | 673 | ||
661 | #[derive(Deserialize, Debug, Clone)] | 674 | #[derive(Deserialize, Debug, Clone)] |
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 31d8c487b..edfa42eb5 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -664,10 +664,13 @@ pub(crate) fn handle_completion( | |||
664 | }; | 664 | }; |
665 | let line_index = snap.file_line_index(position.file_id)?; | 665 | let line_index = snap.file_line_index(position.file_id)?; |
666 | 666 | ||
667 | let insert_replace_support = | ||
668 | snap.config.insert_replace_support().then(|| text_document_position.position); | ||
667 | let items: Vec<CompletionItem> = items | 669 | let items: Vec<CompletionItem> = items |
668 | .into_iter() | 670 | .into_iter() |
669 | .flat_map(|item| { | 671 | .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()); | ||
671 | 674 | ||
672 | if completion_config.enable_imports_on_the_fly { | 675 | if completion_config.enable_imports_on_the_fly { |
673 | for new_item in &mut new_completion_items { | 676 | for new_item in &mut new_completion_items { |
diff --git a/crates/rust-analyzer/src/lsp_utils.rs b/crates/rust-analyzer/src/lsp_utils.rs index 2ac487632..73c4193e8 100644 --- a/crates/rust-analyzer/src/lsp_utils.rs +++ b/crates/rust-analyzer/src/lsp_utils.rs | |||
@@ -150,8 +150,16 @@ pub(crate) fn all_edits_are_disjoint( | |||
150 | edit_ranges.push(edit.range); | 150 | edit_ranges.push(edit.range); |
151 | } | 151 | } |
152 | Some(lsp_types::CompletionTextEdit::InsertAndReplace(edit)) => { | 152 | Some(lsp_types::CompletionTextEdit::InsertAndReplace(edit)) => { |
153 | edit_ranges.push(edit.insert); | 153 | let replace = edit.replace; |
154 | edit_ranges.push(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); | ||
155 | } | 163 | } |
156 | None => {} | 164 | None => {} |
157 | } | 165 | } |
@@ -314,18 +322,6 @@ mod tests { | |||
314 | Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit { | 322 | Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit { |
315 | new_text: "new_text".to_string(), | 323 | new_text: "new_text".to_string(), |
316 | insert: disjoint_edit.range, | 324 | 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 | |||
325 | completion_with_joint_edits.text_edit = | ||
326 | Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit { | ||
327 | new_text: "new_text".to_string(), | ||
328 | insert: disjoint_edit.range, | ||
329 | replace: disjoint_edit_2.range, | 325 | replace: disjoint_edit_2.range, |
330 | })); | 326 | })); |
331 | completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit]); | 327 | completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit]); |
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index c3820944b..d5c0d22b8 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -145,6 +145,23 @@ pub(crate) fn text_edit(line_index: &LineIndex, indel: Indel) -> lsp_types::Text | |||
145 | lsp_types::TextEdit { range, new_text } | 145 | lsp_types::TextEdit { range, new_text } |
146 | } | 146 | } |
147 | 147 | ||
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 | |||
148 | pub(crate) fn snippet_text_edit( | 165 | pub(crate) fn snippet_text_edit( |
149 | line_index: &LineIndex, | 166 | line_index: &LineIndex, |
150 | is_snippet: bool, | 167 | is_snippet: bool, |
@@ -179,6 +196,7 @@ pub(crate) fn snippet_text_edit_vec( | |||
179 | } | 196 | } |
180 | 197 | ||
181 | pub(crate) fn completion_item( | 198 | pub(crate) fn completion_item( |
199 | insert_replace_support: Option<lsp_types::Position>, | ||
182 | line_index: &LineIndex, | 200 | line_index: &LineIndex, |
183 | item: CompletionItem, | 201 | item: CompletionItem, |
184 | ) -> Vec<lsp_types::CompletionItem> { | 202 | ) -> Vec<lsp_types::CompletionItem> { |
@@ -190,7 +208,7 @@ pub(crate) fn completion_item( | |||
190 | for indel in item.text_edit().iter() { | 208 | for indel in item.text_edit().iter() { |
191 | if indel.delete.contains_range(source_range) { | 209 | if indel.delete.contains_range(source_range) { |
192 | text_edit = Some(if indel.delete == source_range { | 210 | 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()) |
194 | } else { | 212 | } else { |
195 | assert!(source_range.end() == indel.delete.end()); | 213 | assert!(source_range.end() == indel.delete.end()); |
196 | let range1 = TextRange::new(indel.delete.start(), source_range.start()); | 214 | let range1 = TextRange::new(indel.delete.start(), source_range.start()); |
@@ -198,7 +216,7 @@ pub(crate) fn completion_item( | |||
198 | let indel1 = Indel::replace(range1, String::new()); | 216 | let indel1 = Indel::replace(range1, String::new()); |
199 | let indel2 = Indel::replace(range2, indel.insert.clone()); | 217 | let indel2 = Indel::replace(range2, indel.insert.clone()); |
200 | additional_text_edits.push(self::text_edit(line_index, indel1)); | 218 | 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) |
202 | }) | 220 | }) |
203 | } else { | 221 | } else { |
204 | assert!(source_range.intersect(indel.delete).is_none()); | 222 | assert!(source_range.intersect(indel.delete).is_none()); |
@@ -213,7 +231,7 @@ pub(crate) fn completion_item( | |||
213 | detail: item.detail().map(|it| it.to_string()), | 231 | detail: item.detail().map(|it| it.to_string()), |
214 | filter_text: Some(item.lookup().to_string()), | 232 | filter_text: Some(item.lookup().to_string()), |
215 | kind: item.kind().map(completion_item_kind), | 233 | kind: item.kind().map(completion_item_kind), |
216 | text_edit: Some(text_edit.into()), | 234 | text_edit: Some(text_edit), |
217 | additional_text_edits: Some(additional_text_edits), | 235 | additional_text_edits: Some(additional_text_edits), |
218 | documentation: item.documentation().map(documentation), | 236 | documentation: item.documentation().map(documentation), |
219 | deprecated: Some(item.deprecated()), | 237 | deprecated: Some(item.deprecated()), |
@@ -1135,7 +1153,7 @@ mod tests { | |||
1135 | .unwrap() | 1153 | .unwrap() |
1136 | .into_iter() | 1154 | .into_iter() |
1137 | .filter(|c| c.label().ends_with("arg")) | 1155 | .filter(|c| c.label().ends_with("arg")) |
1138 | .map(|c| completion_item(&line_index, c)) | 1156 | .map(|c| completion_item(None, &line_index, c)) |
1139 | .flat_map(|comps| comps.into_iter().map(|c| (c.label, c.sort_text))) | 1157 | .flat_map(|comps| comps.into_iter().map(|c| (c.label, c.sort_text))) |
1140 | .collect(); | 1158 | .collect(); |
1141 | expect_test::expect![[r#" | 1159 | expect_test::expect![[r#" |