]> git.lizzy.rs Git - rust.git/blob - crates/completion/src/item.rs
Simplify import edit calculation
[rust.git] / crates / completion / src / item.rs
1 //! See `CompletionItem` structure.
2
3 use std::fmt;
4
5 use hir::{Documentation, ModPath, Mutability};
6 use ide_db::helpers::{
7     insert_use::{self, ImportScope, MergeBehaviour},
8     mod_path_to_ast,
9 };
10 use syntax::{algo, TextRange};
11 use text_edit::TextEdit;
12
13 use crate::config::SnippetCap;
14
15 /// `CompletionItem` describes a single completion variant in the editor pop-up.
16 /// It is basically a POD with various properties. To construct a
17 /// `CompletionItem`, use `new` method and the `Builder` struct.
18 #[derive(Clone)]
19 pub struct CompletionItem {
20     /// Used only internally in tests, to check only specific kind of
21     /// completion (postfix, keyword, reference, etc).
22     #[allow(unused)]
23     pub(crate) completion_kind: CompletionKind,
24     /// Label in the completion pop up which identifies completion.
25     label: String,
26     /// Range of identifier that is being completed.
27     ///
28     /// It should be used primarily for UI, but we also use this to convert
29     /// genetic TextEdit into LSP's completion edit (see conv.rs).
30     ///
31     /// `source_range` must contain the completion offset. `insert_text` should
32     /// start with what `source_range` points to, or VSCode will filter out the
33     /// completion silently.
34     source_range: TextRange,
35     /// What happens when user selects this item.
36     ///
37     /// Typically, replaces `source_range` with new identifier.
38     text_edit: TextEdit,
39
40     insert_text_format: InsertTextFormat,
41
42     /// What item (struct, function, etc) are we completing.
43     kind: Option<CompletionItemKind>,
44
45     /// Lookup is used to check if completion item indeed can complete current
46     /// ident.
47     ///
48     /// That is, in `foo.bar<|>` lookup of `abracadabra` will be accepted (it
49     /// contains `bar` sub sequence), and `quux` will rejected.
50     lookup: Option<String>,
51
52     /// Additional info to show in the UI pop up.
53     detail: Option<String>,
54     documentation: Option<Documentation>,
55
56     /// Whether this item is marked as deprecated
57     deprecated: bool,
58
59     /// If completing a function call, ask the editor to show parameter popup
60     /// after completion.
61     trigger_call_info: bool,
62
63     /// Score is useful to pre select or display in better order completion items
64     score: Option<CompletionScore>,
65
66     /// Indicates that a reference or mutable reference to this variable is a
67     /// possible match.
68     ref_match: Option<(Mutability, CompletionScore)>,
69
70     /// The import data to add to completion's edits.
71     import_to_add: Option<ImportEdit>,
72 }
73
74 // We use custom debug for CompletionItem to make snapshot tests more readable.
75 impl fmt::Debug for CompletionItem {
76     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
77         let mut s = f.debug_struct("CompletionItem");
78         s.field("label", &self.label()).field("source_range", &self.source_range());
79         if self.text_edit().len() == 1 {
80             let atom = &self.text_edit().iter().next().unwrap();
81             s.field("delete", &atom.delete);
82             s.field("insert", &atom.insert);
83         } else {
84             s.field("text_edit", &self.text_edit);
85         }
86         if let Some(kind) = self.kind().as_ref() {
87             s.field("kind", kind);
88         }
89         if self.lookup() != self.label() {
90             s.field("lookup", &self.lookup());
91         }
92         if let Some(detail) = self.detail() {
93             s.field("detail", &detail);
94         }
95         if let Some(documentation) = self.documentation() {
96             s.field("documentation", &documentation);
97         }
98         if self.deprecated {
99             s.field("deprecated", &true);
100         }
101         if let Some(score) = &self.score {
102             s.field("score", score);
103         }
104         if self.trigger_call_info {
105             s.field("trigger_call_info", &true);
106         }
107         s.finish()
108     }
109 }
110
111 #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
112 pub enum CompletionScore {
113     /// If only type match
114     TypeMatch,
115     /// If type and name match
116     TypeAndNameMatch,
117 }
118
119 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
120 pub enum CompletionItemKind {
121     Snippet,
122     Keyword,
123     Module,
124     Function,
125     BuiltinType,
126     Struct,
127     Enum,
128     EnumVariant,
129     Binding,
130     Field,
131     Static,
132     Const,
133     Trait,
134     TypeAlias,
135     Method,
136     TypeParam,
137     Macro,
138     Attribute,
139     UnresolvedReference,
140 }
141
142 impl CompletionItemKind {
143     #[cfg(test)]
144     pub(crate) fn tag(&self) -> &'static str {
145         match self {
146             CompletionItemKind::Attribute => "at",
147             CompletionItemKind::Binding => "bn",
148             CompletionItemKind::BuiltinType => "bt",
149             CompletionItemKind::Const => "ct",
150             CompletionItemKind::Enum => "en",
151             CompletionItemKind::EnumVariant => "ev",
152             CompletionItemKind::Field => "fd",
153             CompletionItemKind::Function => "fn",
154             CompletionItemKind::Keyword => "kw",
155             CompletionItemKind::Macro => "ma",
156             CompletionItemKind::Method => "me",
157             CompletionItemKind::Module => "md",
158             CompletionItemKind::Snippet => "sn",
159             CompletionItemKind::Static => "sc",
160             CompletionItemKind::Struct => "st",
161             CompletionItemKind::Trait => "tt",
162             CompletionItemKind::TypeAlias => "ta",
163             CompletionItemKind::TypeParam => "tp",
164             CompletionItemKind::UnresolvedReference => "??",
165         }
166     }
167 }
168
169 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
170 pub(crate) enum CompletionKind {
171     /// Parser-based keyword completion.
172     Keyword,
173     /// Your usual "complete all valid identifiers".
174     Reference,
175     /// "Secret sauce" completions.
176     Magic,
177     Snippet,
178     Postfix,
179     BuiltinType,
180     Attribute,
181 }
182
183 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
184 pub enum InsertTextFormat {
185     PlainText,
186     Snippet,
187 }
188
189 impl CompletionItem {
190     pub(crate) fn new(
191         completion_kind: CompletionKind,
192         source_range: TextRange,
193         label: impl Into<String>,
194     ) -> Builder {
195         let label = label.into();
196         Builder {
197             source_range,
198             completion_kind,
199             label,
200             insert_text: None,
201             insert_text_format: InsertTextFormat::PlainText,
202             detail: None,
203             documentation: None,
204             lookup: None,
205             kind: None,
206             text_edit: None,
207             deprecated: None,
208             trigger_call_info: None,
209             score: None,
210             ref_match: None,
211             import_to_add: None,
212             resolve_import_lazily: false,
213         }
214     }
215
216     /// What user sees in pop-up in the UI.
217     pub fn label(&self) -> &str {
218         &self.label
219     }
220     pub fn source_range(&self) -> TextRange {
221         self.source_range
222     }
223
224     pub fn insert_text_format(&self) -> InsertTextFormat {
225         self.insert_text_format
226     }
227
228     pub fn text_edit(&self) -> &TextEdit {
229         &self.text_edit
230     }
231
232     /// Short one-line additional information, like a type
233     pub fn detail(&self) -> Option<&str> {
234         self.detail.as_deref()
235     }
236     /// A doc-comment
237     pub fn documentation(&self) -> Option<Documentation> {
238         self.documentation.clone()
239     }
240     /// What string is used for filtering.
241     pub fn lookup(&self) -> &str {
242         self.lookup.as_deref().unwrap_or(&self.label)
243     }
244
245     pub fn kind(&self) -> Option<CompletionItemKind> {
246         self.kind
247     }
248
249     pub fn deprecated(&self) -> bool {
250         self.deprecated
251     }
252
253     pub fn score(&self) -> Option<CompletionScore> {
254         self.score
255     }
256
257     pub fn trigger_call_info(&self) -> bool {
258         self.trigger_call_info
259     }
260
261     pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> {
262         self.ref_match
263     }
264
265     pub fn import_to_add(&self) -> Option<&ImportEdit> {
266         self.import_to_add.as_ref()
267     }
268 }
269
270 /// An extra import to add after the completion is applied.
271 #[derive(Debug, Clone)]
272 pub struct ImportEdit {
273     pub import_path: ModPath,
274     pub import_scope: ImportScope,
275     pub merge_behaviour: Option<MergeBehaviour>,
276 }
277
278 impl ImportEdit {
279     /// Attempts to insert the import to the given scope, producing a text edit.
280     /// May return no edit in edge cases, such as scope already containing the import.
281     pub fn to_text_edit(&self) -> Option<TextEdit> {
282         let _p = profile::span("ImportEdit::to_edit");
283
284         let rewriter = insert_use::insert_use(
285             &self.import_scope,
286             mod_path_to_ast(&self.import_path),
287             self.merge_behaviour,
288         );
289         let old_ast = rewriter.rewrite_root()?;
290         let mut import_insert = TextEdit::builder();
291         algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
292
293         Some(import_insert.finish())
294     }
295 }
296
297 /// A helper to make `CompletionItem`s.
298 #[must_use]
299 #[derive(Clone)]
300 pub(crate) struct Builder {
301     source_range: TextRange,
302     completion_kind: CompletionKind,
303     import_to_add: Option<ImportEdit>,
304     resolve_import_lazily: bool,
305     label: String,
306     insert_text: Option<String>,
307     insert_text_format: InsertTextFormat,
308     detail: Option<String>,
309     documentation: Option<Documentation>,
310     lookup: Option<String>,
311     kind: Option<CompletionItemKind>,
312     text_edit: Option<TextEdit>,
313     deprecated: Option<bool>,
314     trigger_call_info: Option<bool>,
315     score: Option<CompletionScore>,
316     ref_match: Option<(Mutability, CompletionScore)>,
317 }
318
319 impl Builder {
320     pub(crate) fn build(self) -> CompletionItem {
321         let _p = profile::span("item::Builder::build");
322
323         let mut label = self.label;
324         let mut lookup = self.lookup;
325         let mut insert_text = self.insert_text;
326
327         if let Some(import_to_add) = self.import_to_add.as_ref() {
328             let mut import_path_without_last_segment = import_to_add.import_path.to_owned();
329             let _ = import_path_without_last_segment.segments.pop();
330
331             if !import_path_without_last_segment.segments.is_empty() {
332                 if lookup.is_none() {
333                     lookup = Some(label.clone());
334                 }
335                 if insert_text.is_none() {
336                     insert_text = Some(label.clone());
337                 }
338                 label = format!("{}::{}", import_path_without_last_segment, label);
339             }
340         }
341
342         let mut text_edit = match self.text_edit {
343             Some(it) => it,
344             None => {
345                 TextEdit::replace(self.source_range, insert_text.unwrap_or_else(|| label.clone()))
346             }
347         };
348
349         if !self.resolve_import_lazily {
350             if let Some(import_edit) =
351                 self.import_to_add.as_ref().and_then(|import_edit| import_edit.to_text_edit())
352             {
353                 text_edit.union(import_edit).expect("Failed to unite import and completion edits");
354             }
355         }
356
357         CompletionItem {
358             source_range: self.source_range,
359             label,
360             insert_text_format: self.insert_text_format,
361             text_edit,
362             detail: self.detail,
363             documentation: self.documentation,
364             lookup,
365             kind: self.kind,
366             completion_kind: self.completion_kind,
367             deprecated: self.deprecated.unwrap_or(false),
368             trigger_call_info: self.trigger_call_info.unwrap_or(false),
369             score: self.score,
370             ref_match: self.ref_match,
371             import_to_add: self.import_to_add,
372         }
373     }
374     pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
375         self.lookup = Some(lookup.into());
376         self
377     }
378     pub(crate) fn label(mut self, label: impl Into<String>) -> Builder {
379         self.label = label.into();
380         self
381     }
382     pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder {
383         self.insert_text = Some(insert_text.into());
384         self
385     }
386     pub(crate) fn insert_snippet(
387         mut self,
388         _cap: SnippetCap,
389         snippet: impl Into<String>,
390     ) -> Builder {
391         self.insert_text_format = InsertTextFormat::Snippet;
392         self.insert_text(snippet)
393     }
394     pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder {
395         self.kind = Some(kind);
396         self
397     }
398     pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder {
399         self.text_edit = Some(edit);
400         self
401     }
402     pub(crate) fn snippet_edit(mut self, _cap: SnippetCap, edit: TextEdit) -> Builder {
403         self.insert_text_format = InsertTextFormat::Snippet;
404         self.text_edit(edit)
405     }
406     #[allow(unused)]
407     pub(crate) fn detail(self, detail: impl Into<String>) -> Builder {
408         self.set_detail(Some(detail))
409     }
410     pub(crate) fn set_detail(mut self, detail: Option<impl Into<String>>) -> Builder {
411         self.detail = detail.map(Into::into);
412         self
413     }
414     #[allow(unused)]
415     pub(crate) fn documentation(self, docs: Documentation) -> Builder {
416         self.set_documentation(Some(docs))
417     }
418     pub(crate) fn set_documentation(mut self, docs: Option<Documentation>) -> Builder {
419         self.documentation = docs.map(Into::into);
420         self
421     }
422     pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder {
423         self.deprecated = Some(deprecated);
424         self
425     }
426     pub(crate) fn set_score(mut self, score: CompletionScore) -> Builder {
427         self.score = Some(score);
428         self
429     }
430     pub(crate) fn trigger_call_info(mut self) -> Builder {
431         self.trigger_call_info = Some(true);
432         self
433     }
434     pub(crate) fn add_import(
435         mut self,
436         import_to_add: Option<ImportEdit>,
437         resolve_import_lazily: bool,
438     ) -> Builder {
439         self.import_to_add = import_to_add;
440         self.resolve_import_lazily = resolve_import_lazily;
441         self
442     }
443     pub(crate) fn set_ref_match(
444         mut self,
445         ref_match: Option<(Mutability, CompletionScore)>,
446     ) -> Builder {
447         self.ref_match = ref_match;
448         self
449     }
450 }
451
452 impl<'a> Into<CompletionItem> for Builder {
453     fn into(self) -> CompletionItem {
454         self.build()
455     }
456 }