diff options
Diffstat (limited to 'crates/completion/src/item.rs')
-rw-r--r-- | crates/completion/src/item.rs | 64 |
1 files changed, 39 insertions, 25 deletions
diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs index e85549fef..bd94402d7 100644 --- a/crates/completion/src/item.rs +++ b/crates/completion/src/item.rs | |||
@@ -15,6 +15,7 @@ use crate::config::SnippetCap; | |||
15 | /// `CompletionItem` describes a single completion variant in the editor pop-up. | 15 | /// `CompletionItem` describes a single completion variant in the editor pop-up. |
16 | /// It is basically a POD with various properties. To construct a | 16 | /// It is basically a POD with various properties. To construct a |
17 | /// `CompletionItem`, use `new` method and the `Builder` struct. | 17 | /// `CompletionItem`, use `new` method and the `Builder` struct. |
18 | #[derive(Clone)] | ||
18 | pub struct CompletionItem { | 19 | pub struct CompletionItem { |
19 | /// Used only internally in tests, to check only specific kind of | 20 | /// Used only internally in tests, to check only specific kind of |
20 | /// completion (postfix, keyword, reference, etc). | 21 | /// completion (postfix, keyword, reference, etc). |
@@ -65,6 +66,9 @@ pub struct CompletionItem { | |||
65 | /// Indicates that a reference or mutable reference to this variable is a | 66 | /// Indicates that a reference or mutable reference to this variable is a |
66 | /// possible match. | 67 | /// possible match. |
67 | ref_match: Option<(Mutability, CompletionScore)>, | 68 | ref_match: Option<(Mutability, CompletionScore)>, |
69 | |||
70 | /// The import data to add to completion's edits. | ||
71 | import_to_add: Option<ImportEdit>, | ||
68 | } | 72 | } |
69 | 73 | ||
70 | // We use custom debug for CompletionItem to make snapshot tests more readable. | 74 | // We use custom debug for CompletionItem to make snapshot tests more readable. |
@@ -256,14 +260,37 @@ impl CompletionItem { | |||
256 | pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> { | 260 | pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> { |
257 | self.ref_match | 261 | self.ref_match |
258 | } | 262 | } |
263 | |||
264 | pub fn import_to_add(&self) -> Option<&ImportEdit> { | ||
265 | self.import_to_add.as_ref() | ||
266 | } | ||
259 | } | 267 | } |
260 | 268 | ||
261 | /// An extra import to add after the completion is applied. | 269 | /// An extra import to add after the completion is applied. |
262 | #[derive(Clone)] | 270 | #[derive(Debug, Clone)] |
263 | pub(crate) struct ImportToAdd { | 271 | pub struct ImportEdit { |
264 | pub(crate) import_path: ModPath, | 272 | pub import_path: ModPath, |
265 | pub(crate) import_scope: ImportScope, | 273 | pub import_scope: ImportScope, |
266 | pub(crate) merge_behaviour: Option<MergeBehaviour>, | 274 | pub merge_behaviour: Option<MergeBehaviour>, |
275 | } | ||
276 | |||
277 | impl ImportEdit { | ||
278 | /// Attempts to insert the import to the given scope, producing a text edit. | ||
279 | /// May return no edit in edge cases, such as scope already containing the import. | ||
280 | pub fn to_text_edit(&self) -> Option<TextEdit> { | ||
281 | let _p = profile::span("ImportEdit::to_text_edit"); | ||
282 | |||
283 | let rewriter = insert_use::insert_use( | ||
284 | &self.import_scope, | ||
285 | mod_path_to_ast(&self.import_path), | ||
286 | self.merge_behaviour, | ||
287 | ); | ||
288 | let old_ast = rewriter.rewrite_root()?; | ||
289 | let mut import_insert = TextEdit::builder(); | ||
290 | algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert); | ||
291 | |||
292 | Some(import_insert.finish()) | ||
293 | } | ||
267 | } | 294 | } |
268 | 295 | ||
269 | /// A helper to make `CompletionItem`s. | 296 | /// A helper to make `CompletionItem`s. |
@@ -272,7 +299,7 @@ pub(crate) struct ImportToAdd { | |||
272 | pub(crate) struct Builder { | 299 | pub(crate) struct Builder { |
273 | source_range: TextRange, | 300 | source_range: TextRange, |
274 | completion_kind: CompletionKind, | 301 | completion_kind: CompletionKind, |
275 | import_to_add: Option<ImportToAdd>, | 302 | import_to_add: Option<ImportEdit>, |
276 | label: String, | 303 | label: String, |
277 | insert_text: Option<String>, | 304 | insert_text: Option<String>, |
278 | insert_text_format: InsertTextFormat, | 305 | insert_text_format: InsertTextFormat, |
@@ -294,11 +321,9 @@ impl Builder { | |||
294 | let mut label = self.label; | 321 | let mut label = self.label; |
295 | let mut lookup = self.lookup; | 322 | let mut lookup = self.lookup; |
296 | let mut insert_text = self.insert_text; | 323 | let mut insert_text = self.insert_text; |
297 | let mut text_edits = TextEdit::builder(); | ||
298 | 324 | ||
299 | if let Some(import_data) = self.import_to_add { | 325 | if let Some(import_to_add) = self.import_to_add.as_ref() { |
300 | let import = mod_path_to_ast(&import_data.import_path); | 326 | let mut import_path_without_last_segment = import_to_add.import_path.to_owned(); |
301 | let mut import_path_without_last_segment = import_data.import_path; | ||
302 | let _ = import_path_without_last_segment.segments.pop(); | 327 | let _ = import_path_without_last_segment.segments.pop(); |
303 | 328 | ||
304 | if !import_path_without_last_segment.segments.is_empty() { | 329 | if !import_path_without_last_segment.segments.is_empty() { |
@@ -310,32 +335,20 @@ impl Builder { | |||
310 | } | 335 | } |
311 | label = format!("{}::{}", import_path_without_last_segment, label); | 336 | label = format!("{}::{}", import_path_without_last_segment, label); |
312 | } | 337 | } |
313 | |||
314 | let rewriter = insert_use::insert_use( | ||
315 | &import_data.import_scope, | ||
316 | import, | ||
317 | import_data.merge_behaviour, | ||
318 | ); | ||
319 | if let Some(old_ast) = rewriter.rewrite_root() { | ||
320 | algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits); | ||
321 | } | ||
322 | } | 338 | } |
323 | 339 | ||
324 | let original_edit = match self.text_edit { | 340 | let text_edit = match self.text_edit { |
325 | Some(it) => it, | 341 | Some(it) => it, |
326 | None => { | 342 | None => { |
327 | TextEdit::replace(self.source_range, insert_text.unwrap_or_else(|| label.clone())) | 343 | TextEdit::replace(self.source_range, insert_text.unwrap_or_else(|| label.clone())) |
328 | } | 344 | } |
329 | }; | 345 | }; |
330 | 346 | ||
331 | let mut resulting_edit = text_edits.finish(); | ||
332 | resulting_edit.union(original_edit).expect("Failed to unite text edits"); | ||
333 | |||
334 | CompletionItem { | 347 | CompletionItem { |
335 | source_range: self.source_range, | 348 | source_range: self.source_range, |
336 | label, | 349 | label, |
337 | insert_text_format: self.insert_text_format, | 350 | insert_text_format: self.insert_text_format, |
338 | text_edit: resulting_edit, | 351 | text_edit, |
339 | detail: self.detail, | 352 | detail: self.detail, |
340 | documentation: self.documentation, | 353 | documentation: self.documentation, |
341 | lookup, | 354 | lookup, |
@@ -345,6 +358,7 @@ impl Builder { | |||
345 | trigger_call_info: self.trigger_call_info.unwrap_or(false), | 358 | trigger_call_info: self.trigger_call_info.unwrap_or(false), |
346 | score: self.score, | 359 | score: self.score, |
347 | ref_match: self.ref_match, | 360 | ref_match: self.ref_match, |
361 | import_to_add: self.import_to_add, | ||
348 | } | 362 | } |
349 | } | 363 | } |
350 | pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { | 364 | pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { |
@@ -407,7 +421,7 @@ impl Builder { | |||
407 | self.trigger_call_info = Some(true); | 421 | self.trigger_call_info = Some(true); |
408 | self | 422 | self |
409 | } | 423 | } |
410 | pub(crate) fn add_import(mut self, import_to_add: Option<ImportToAdd>) -> Builder { | 424 | pub(crate) fn add_import(mut self, import_to_add: Option<ImportEdit>) -> Builder { |
411 | self.import_to_add = import_to_add; | 425 | self.import_to_add = import_to_add; |
412 | self | 426 | self |
413 | } | 427 | } |