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