]> git.lizzy.rs Git - rust.git/blob - crates/ra_ide_api/src/completion/completion_item.rs
49bd636a5dc20c5b23972738e22123fcf8aa7c05
[rust.git] / crates / ra_ide_api / src / completion / completion_item.rs
1 use hir::{Docs, Documentation};
2 use ra_syntax::{
3     ast::{self, AstNode},
4     TextRange,
5 };
6 use ra_text_edit::TextEdit;
7 use test_utils::tested_by;
8
9 use crate::completion::completion_context::CompletionContext;
10
11 /// `CompletionItem` describes a single completion variant in the editor pop-up.
12 /// It is basically a POD with various properties. To construct a
13 /// `CompletionItem`, use `new` method and the `Builder` struct.
14 #[derive(Debug)]
15 pub struct CompletionItem {
16     /// Used only internally in tests, to check only specific kind of
17     /// completion.
18     completion_kind: CompletionKind,
19     label: String,
20     kind: Option<CompletionItemKind>,
21     detail: Option<String>,
22     documentation: Option<Documentation>,
23     lookup: Option<String>,
24     insert_text: Option<String>,
25     insert_text_format: InsertTextFormat,
26     /// Where completion occurs. `source_range` must contain the completion offset.
27     /// `insert_text` should start with what `source_range` points to, or VSCode
28     /// will filter out the completion silently.
29     source_range: TextRange,
30     /// Additional text edit, ranges in `text_edit` must never intersect with `source_range`.
31     /// Or VSCode will drop it silently.
32     text_edit: Option<TextEdit>,
33 }
34
35 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
36 pub enum CompletionItemKind {
37     Snippet,
38     Keyword,
39     Module,
40     Function,
41     Struct,
42     Enum,
43     EnumVariant,
44     Binding,
45     Field,
46     Static,
47     Const,
48     Trait,
49     TypeAlias,
50     Method,
51 }
52
53 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
54 pub(crate) enum CompletionKind {
55     /// Parser-based keyword completion.
56     Keyword,
57     /// Your usual "complete all valid identifiers".
58     Reference,
59     /// "Secret sauce" completions.
60     Magic,
61     Snippet,
62     Postfix,
63 }
64
65 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
66 pub enum InsertTextFormat {
67     PlainText,
68     Snippet,
69 }
70
71 impl CompletionItem {
72     pub(crate) fn new(
73         completion_kind: CompletionKind,
74         replace_range: TextRange,
75         label: impl Into<String>,
76     ) -> Builder {
77         let label = label.into();
78         Builder {
79             source_range: replace_range,
80             completion_kind,
81             label,
82             insert_text: None,
83             insert_text_format: InsertTextFormat::PlainText,
84             detail: None,
85             documentation: None,
86             lookup: None,
87             kind: None,
88             text_edit: None,
89         }
90     }
91     /// What user sees in pop-up in the UI.
92     pub fn label(&self) -> &str {
93         &self.label
94     }
95     /// Short one-line additional information, like a type
96     pub fn detail(&self) -> Option<&str> {
97         self.detail.as_ref().map(|it| it.as_str())
98     }
99     /// A doc-comment
100     pub fn documentation(&self) -> Option<&str> {
101         self.documentation.as_ref().map(|it| it.contents())
102     }
103     /// What string is used for filtering.
104     pub fn lookup(&self) -> &str {
105         self.lookup
106             .as_ref()
107             .map(|it| it.as_str())
108             .unwrap_or(self.label())
109     }
110
111     pub fn insert_text_format(&self) -> InsertTextFormat {
112         self.insert_text_format.clone()
113     }
114     pub fn insert_text(&self) -> String {
115         match &self.insert_text {
116             Some(t) => t.clone(),
117             None => self.label.clone(),
118         }
119     }
120     pub fn kind(&self) -> Option<CompletionItemKind> {
121         self.kind
122     }
123     pub fn take_text_edit(&mut self) -> Option<TextEdit> {
124         self.text_edit.take()
125     }
126     pub fn source_range(&self) -> TextRange {
127         self.source_range
128     }
129 }
130
131 /// A helper to make `CompletionItem`s.
132 #[must_use]
133 pub(crate) struct Builder {
134     source_range: TextRange,
135     completion_kind: CompletionKind,
136     label: String,
137     insert_text: Option<String>,
138     insert_text_format: InsertTextFormat,
139     detail: Option<String>,
140     documentation: Option<Documentation>,
141     lookup: Option<String>,
142     kind: Option<CompletionItemKind>,
143     text_edit: Option<TextEdit>,
144 }
145
146 impl Builder {
147     pub(crate) fn add_to(self, acc: &mut Completions) {
148         acc.add(self.build())
149     }
150
151     pub(crate) fn build(self) -> CompletionItem {
152         CompletionItem {
153             source_range: self.source_range,
154             label: self.label,
155             detail: self.detail,
156             documentation: self.documentation,
157             insert_text_format: self.insert_text_format,
158             lookup: self.lookup,
159             kind: self.kind,
160             completion_kind: self.completion_kind,
161             text_edit: self.text_edit,
162             insert_text: self.insert_text,
163         }
164     }
165     pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
166         self.lookup = Some(lookup.into());
167         self
168     }
169     pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder {
170         self.insert_text = Some(insert_text.into());
171         self
172     }
173     #[allow(unused)]
174     pub(crate) fn insert_text_format(mut self, insert_text_format: InsertTextFormat) -> Builder {
175         self.insert_text_format = insert_text_format;
176         self
177     }
178     pub(crate) fn snippet(mut self, snippet: impl Into<String>) -> Builder {
179         self.insert_text_format = InsertTextFormat::Snippet;
180         self.insert_text(snippet)
181     }
182     pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder {
183         self.kind = Some(kind);
184         self
185     }
186     #[allow(unused)]
187     pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder {
188         self.text_edit = Some(edit);
189         self
190     }
191     #[allow(unused)]
192     pub(crate) fn detail(self, detail: impl Into<String>) -> Builder {
193         self.set_detail(Some(detail))
194     }
195     pub(crate) fn set_detail(mut self, detail: Option<impl Into<String>>) -> Builder {
196         self.detail = detail.map(Into::into);
197         self
198     }
199     #[allow(unused)]
200     pub(crate) fn documentation(self, docs: Documentation) -> Builder {
201         self.set_documentation(Some(docs))
202     }
203     pub(crate) fn set_documentation(mut self, docs: Option<Documentation>) -> Builder {
204         self.documentation = docs.map(Into::into);
205         self
206     }
207     pub(super) fn from_resolution(
208         mut self,
209         ctx: &CompletionContext,
210         resolution: &hir::Resolution,
211     ) -> Builder {
212         let def = resolution.def.take_types().or(resolution.def.take_values());
213         let def = match def {
214             None => return self,
215             Some(it) => it,
216         };
217         let (kind, docs) = match def {
218             hir::ModuleDef::Module(it) => (CompletionItemKind::Module, it.docs(ctx.db)),
219             hir::ModuleDef::Function(func) => return self.from_function(ctx, func),
220             hir::ModuleDef::Struct(it) => (CompletionItemKind::Struct, it.docs(ctx.db)),
221             hir::ModuleDef::Enum(it) => (CompletionItemKind::Enum, it.docs(ctx.db)),
222             hir::ModuleDef::EnumVariant(it) => (CompletionItemKind::EnumVariant, it.docs(ctx.db)),
223             hir::ModuleDef::Const(it) => (CompletionItemKind::Const, it.docs(ctx.db)),
224             hir::ModuleDef::Static(it) => (CompletionItemKind::Static, it.docs(ctx.db)),
225             hir::ModuleDef::Trait(it) => (CompletionItemKind::Trait, it.docs(ctx.db)),
226             hir::ModuleDef::Type(it) => (CompletionItemKind::TypeAlias, it.docs(ctx.db)),
227         };
228         self.kind = Some(kind);
229         self.documentation = docs;
230
231         self
232     }
233
234     pub(super) fn from_function(
235         mut self,
236         ctx: &CompletionContext,
237         function: hir::Function,
238     ) -> Builder {
239         // If not an import, add parenthesis automatically.
240         if ctx.use_item_syntax.is_none() && !ctx.is_call {
241             tested_by!(inserts_parens_for_function_calls);
242             let sig = function.signature(ctx.db);
243             if sig.params().is_empty() || sig.has_self_param() && sig.params().len() == 1 {
244                 self.insert_text = Some(format!("{}()$0", self.label));
245             } else {
246                 self.insert_text = Some(format!("{}($0)", self.label));
247             }
248             self.insert_text_format = InsertTextFormat::Snippet;
249         }
250
251         if let Some(docs) = function.docs(ctx.db) {
252             self.documentation = Some(docs);
253         }
254
255         if let Some(label) = function_label(ctx, function) {
256             self.detail = Some(label);
257         }
258
259         self.kind = Some(CompletionItemKind::Function);
260         self
261     }
262 }
263
264 impl<'a> Into<CompletionItem> for Builder {
265     fn into(self) -> CompletionItem {
266         self.build()
267     }
268 }
269
270 /// Represents an in-progress set of completions being built.
271 #[derive(Debug, Default)]
272 pub(crate) struct Completions {
273     buf: Vec<CompletionItem>,
274 }
275
276 impl Completions {
277     pub(crate) fn add(&mut self, item: impl Into<CompletionItem>) {
278         self.buf.push(item.into())
279     }
280     pub(crate) fn add_all<I>(&mut self, items: I)
281     where
282         I: IntoIterator,
283         I::Item: Into<CompletionItem>,
284     {
285         items.into_iter().for_each(|item| self.add(item.into()))
286     }
287 }
288
289 impl Into<Vec<CompletionItem>> for Completions {
290     fn into(self) -> Vec<CompletionItem> {
291         self.buf
292     }
293 }
294
295 fn function_label(ctx: &CompletionContext, function: hir::Function) -> Option<String> {
296     let node = function.source(ctx.db).1;
297
298     let label: String = if let Some(body) = node.body() {
299         let body_range = body.syntax().range();
300         let label: String = node
301             .syntax()
302             .children()
303             .filter(|child| !child.range().is_subrange(&body_range)) // Filter out body
304             .filter(|child| ast::Comment::cast(child).is_none()) // Filter out comments
305             .map(|node| node.text().to_string())
306             .collect();
307         label
308     } else {
309         node.syntax().text().to_string()
310     };
311
312     Some(label.trim().to_owned())
313 }
314
315 #[cfg(test)]
316 pub(crate) fn check_completion(test_name: &str, code: &str, kind: CompletionKind) {
317     use crate::mock_analysis::{single_file_with_position, analysis_and_position};
318     use crate::completion::completions;
319     use insta::assert_debug_snapshot_matches;
320     let (analysis, position) = if code.contains("//-") {
321         analysis_and_position(code)
322     } else {
323         single_file_with_position(code)
324     };
325     let completions = completions(&analysis.db, position).unwrap();
326     let completion_items: Vec<CompletionItem> = completions.into();
327     let mut kind_completions: Vec<CompletionItem> = completion_items
328         .into_iter()
329         .filter(|c| c.completion_kind == kind)
330         .collect();
331     kind_completions.sort_by_key(|c| c.label.clone());
332     assert_debug_snapshot_matches!(test_name, kind_completions);
333 }
334
335 #[cfg(test)]
336 mod tests {
337     use test_utils::covers;
338
339     use super::*;
340
341     fn check_reference_completion(code: &str, expected_completions: &str) {
342         check_completion(code, expected_completions, CompletionKind::Reference);
343     }
344
345     #[test]
346     fn inserts_parens_for_function_calls() {
347         covers!(inserts_parens_for_function_calls);
348         check_reference_completion(
349             "inserts_parens_for_function_calls1",
350             r"
351             fn no_args() {}
352             fn main() { no_<|> }
353             ",
354         );
355         check_reference_completion(
356             "inserts_parens_for_function_calls2",
357             r"
358             fn with_args(x: i32, y: String) {}
359             fn main() { with_<|> }
360             ",
361         );
362         check_reference_completion(
363             "inserts_parens_for_function_calls3",
364             r"
365             struct S {}
366             impl S {
367                 fn foo(&self) {}
368             }
369             fn bar(s: &S) {
370                 s.f<|>
371             }
372             ",
373         )
374     }
375
376     #[test]
377     fn dont_render_function_parens_in_use_item() {
378         check_reference_completion(
379             "dont_render_function_parens_in_use_item",
380             "
381             //- /lib.rs
382             mod m { pub fn foo() {} }
383             use crate::m::f<|>;
384             ",
385         )
386     }
387
388     #[test]
389     fn dont_render_function_parens_if_already_call() {
390         check_reference_completion(
391             "dont_render_function_parens_if_already_call",
392             "
393             //- /lib.rs
394             fn frobnicate() {}
395             fn main() {
396                 frob<|>();
397             }
398             ",
399         )
400     }
401
402 }