]> git.lizzy.rs Git - rust.git/blob - crates/completion/src/item.rs
65f8353e7c4d53be0ecbbcf85943a050ef22bd13
[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, MergeBehavior},
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         }
213     }
214
215     /// What user sees in pop-up in the UI.
216     pub fn label(&self) -> &str {
217         &self.label
218     }
219     pub fn source_range(&self) -> TextRange {
220         self.source_range
221     }
222
223     pub fn insert_text_format(&self) -> InsertTextFormat {
224         self.insert_text_format
225     }
226
227     pub fn text_edit(&self) -> &TextEdit {
228         &self.text_edit
229     }
230
231     /// Short one-line additional information, like a type
232     pub fn detail(&self) -> Option<&str> {
233         self.detail.as_deref()
234     }
235     /// A doc-comment
236     pub fn documentation(&self) -> Option<Documentation> {
237         self.documentation.clone()
238     }
239     /// What string is used for filtering.
240     pub fn lookup(&self) -> &str {
241         self.lookup.as_deref().unwrap_or(&self.label)
242     }
243
244     pub fn kind(&self) -> Option<CompletionItemKind> {
245         self.kind
246     }
247
248     pub fn deprecated(&self) -> bool {
249         self.deprecated
250     }
251
252     pub fn score(&self) -> Option<CompletionScore> {
253         self.score
254     }
255
256     pub fn trigger_call_info(&self) -> bool {
257         self.trigger_call_info
258     }
259
260     pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> {
261         self.ref_match
262     }
263
264     pub fn import_to_add(&self) -> Option<&ImportEdit> {
265         self.import_to_add.as_ref()
266     }
267 }
268
269 /// An extra import to add after the completion is applied.
270 #[derive(Debug, Clone)]
271 pub struct ImportEdit {
272     pub import_path: ModPath,
273     pub import_scope: ImportScope,
274 }
275
276 impl ImportEdit {
277     /// Attempts to insert the import to the given scope, producing a text edit.
278     /// May return no edit in edge cases, such as scope already containing the import.
279     pub fn to_text_edit(&self, merge_behavior: Option<MergeBehavior>) -> Option<TextEdit> {
280         let _p = profile::span("ImportEdit::to_text_edit");
281
282         let rewriter = insert_use::insert_use(
283             &self.import_scope,
284             mod_path_to_ast(&self.import_path),
285             merge_behavior,
286         );
287         let old_ast = rewriter.rewrite_root()?;
288         let mut import_insert = TextEdit::builder();
289         algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
290
291         Some(import_insert.finish())
292     }
293 }
294
295 /// A helper to make `CompletionItem`s.
296 #[must_use]
297 #[derive(Clone)]
298 pub(crate) struct Builder {
299     source_range: TextRange,
300     completion_kind: CompletionKind,
301     import_to_add: Option<ImportEdit>,
302     label: String,
303     insert_text: Option<String>,
304     insert_text_format: InsertTextFormat,
305     detail: Option<String>,
306     documentation: Option<Documentation>,
307     lookup: Option<String>,
308     kind: Option<CompletionItemKind>,
309     text_edit: Option<TextEdit>,
310     deprecated: Option<bool>,
311     trigger_call_info: Option<bool>,
312     score: Option<CompletionScore>,
313     ref_match: Option<(Mutability, CompletionScore)>,
314 }
315
316 impl Builder {
317     pub(crate) fn build(self) -> CompletionItem {
318         let _p = profile::span("item::Builder::build");
319
320         let mut label = self.label;
321         let mut lookup = self.lookup;
322         let mut insert_text = self.insert_text;
323
324         if let Some(import_to_add) = self.import_to_add.as_ref() {
325             let mut import_path_without_last_segment = import_to_add.import_path.to_owned();
326             let _ = import_path_without_last_segment.segments.pop();
327
328             if !import_path_without_last_segment.segments.is_empty() {
329                 if lookup.is_none() {
330                     lookup = Some(label.clone());
331                 }
332                 if insert_text.is_none() {
333                     insert_text = Some(label.clone());
334                 }
335                 label = format!("{}::{}", import_path_without_last_segment, label);
336             }
337         }
338
339         let text_edit = match self.text_edit {
340             Some(it) => it,
341             None => {
342                 TextEdit::replace(self.source_range, insert_text.unwrap_or_else(|| label.clone()))
343             }
344         };
345
346         CompletionItem {
347             source_range: self.source_range,
348             label,
349             insert_text_format: self.insert_text_format,
350             text_edit,
351             detail: self.detail,
352             documentation: self.documentation,
353             lookup,
354             kind: self.kind,
355             completion_kind: self.completion_kind,
356             deprecated: self.deprecated.unwrap_or(false),
357             trigger_call_info: self.trigger_call_info.unwrap_or(false),
358             score: self.score,
359             ref_match: self.ref_match,
360             import_to_add: self.import_to_add,
361         }
362     }
363     pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
364         self.lookup = Some(lookup.into());
365         self
366     }
367     pub(crate) fn label(mut self, label: impl Into<String>) -> Builder {
368         self.label = label.into();
369         self
370     }
371     pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder {
372         self.insert_text = Some(insert_text.into());
373         self
374     }
375     pub(crate) fn insert_snippet(
376         mut self,
377         _cap: SnippetCap,
378         snippet: impl Into<String>,
379     ) -> Builder {
380         self.insert_text_format = InsertTextFormat::Snippet;
381         self.insert_text(snippet)
382     }
383     pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder {
384         self.kind = Some(kind);
385         self
386     }
387     pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder {
388         self.text_edit = Some(edit);
389         self
390     }
391     pub(crate) fn snippet_edit(mut self, _cap: SnippetCap, edit: TextEdit) -> Builder {
392         self.insert_text_format = InsertTextFormat::Snippet;
393         self.text_edit(edit)
394     }
395     #[allow(unused)]
396     pub(crate) fn detail(self, detail: impl Into<String>) -> Builder {
397         self.set_detail(Some(detail))
398     }
399     pub(crate) fn set_detail(mut self, detail: Option<impl Into<String>>) -> Builder {
400         self.detail = detail.map(Into::into);
401         self
402     }
403     #[allow(unused)]
404     pub(crate) fn documentation(self, docs: Documentation) -> Builder {
405         self.set_documentation(Some(docs))
406     }
407     pub(crate) fn set_documentation(mut self, docs: Option<Documentation>) -> Builder {
408         self.documentation = docs.map(Into::into);
409         self
410     }
411     pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder {
412         self.deprecated = Some(deprecated);
413         self
414     }
415     pub(crate) fn set_score(mut self, score: CompletionScore) -> Builder {
416         self.score = Some(score);
417         self
418     }
419     pub(crate) fn trigger_call_info(mut self) -> Builder {
420         self.trigger_call_info = Some(true);
421         self
422     }
423     pub(crate) fn add_import(mut self, import_to_add: Option<ImportEdit>) -> Builder {
424         self.import_to_add = import_to_add;
425         self
426     }
427     pub(crate) fn set_ref_match(
428         mut self,
429         ref_match: Option<(Mutability, CompletionScore)>,
430     ) -> Builder {
431         self.ref_match = ref_match;
432         self
433     }
434 }
435
436 impl<'a> Into<CompletionItem> for Builder {
437     fn into(self) -> CompletionItem {
438         self.build()
439     }
440 }