]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/lib.rs
internal: rename hypothetical -> speculative
[rust.git] / crates / ide_completion / src / lib.rs
1 //! `completions` crate provides utilities for generating completions of user input.
2
3 mod config;
4 mod item;
5 mod context;
6 mod patterns;
7 mod generated_lint_completions;
8 #[cfg(test)]
9 mod test_utils;
10 mod render;
11
12 mod completions;
13
14 use completions::flyimport::position_for_import;
15 use ide_db::{
16     base_db::FilePosition,
17     helpers::{
18         import_assets::{LocatedImport, NameToImport},
19         insert_use::ImportScope,
20     },
21     items_locator, RootDatabase,
22 };
23 use text_edit::TextEdit;
24
25 use crate::{completions::Completions, context::CompletionContext, item::CompletionKind};
26
27 pub use crate::{
28     config::CompletionConfig,
29     item::{CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit, InsertTextFormat},
30 };
31
32 //FIXME: split the following feature into fine-grained features.
33
34 // Feature: Magic Completions
35 //
36 // In addition to usual reference completion, rust-analyzer provides some ✨magic✨
37 // completions as well:
38 //
39 // Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor
40 // is placed at the appropriate position. Even though `if` is easy to type, you
41 // still want to complete it, to get ` { }` for free! `return` is inserted with a
42 // space or `;` depending on the return type of the function.
43 //
44 // When completing a function call, `()` are automatically inserted. If a function
45 // takes arguments, the cursor is positioned inside the parenthesis.
46 //
47 // There are postfix completions, which can be triggered by typing something like
48 // `foo().if`. The word after `.` determines postfix completion. Possible variants are:
49 //
50 // - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result`
51 // - `expr.match` -> `match expr {}`
52 // - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result`
53 // - `expr.ref` -> `&expr`
54 // - `expr.refm` -> `&mut expr`
55 // - `expr.let` -> `let $0 = expr;`
56 // - `expr.letm` -> `let mut $0 = expr;`
57 // - `expr.not` -> `!expr`
58 // - `expr.dbg` -> `dbg!(expr)`
59 // - `expr.dbgr` -> `dbg!(&expr)`
60 // - `expr.call` -> `(expr)`
61 //
62 // There also snippet completions:
63 //
64 // .Expressions
65 // - `pd` -> `eprintln!(" = {:?}", );`
66 // - `ppd` -> `eprintln!(" = {:#?}", );`
67 //
68 // .Items
69 // - `tfn` -> `#[test] fn feature(){}`
70 // - `tmod` ->
71 // ```rust
72 // #[cfg(test)]
73 // mod tests {
74 //     use super::*;
75 //
76 //     #[test]
77 //     fn test_name() {}
78 // }
79 // ```
80 //
81 // And the auto import completions, enabled with the `rust-analyzer.completion.autoimport.enable` setting and the corresponding LSP client capabilities.
82 // Those are the additional completion options with automatic `use` import and options from all project importable items,
83 // fuzzy matched against the completion input.
84 //
85 // image::https://user-images.githubusercontent.com/48062697/113020667-b72ab880-917a-11eb-8778-716cf26a0eb3.gif[]
86
87 /// Main entry point for completion. We run completion as a two-phase process.
88 ///
89 /// First, we look at the position and collect a so-called `CompletionContext.
90 /// This is a somewhat messy process, because, during completion, syntax tree is
91 /// incomplete and can look really weird.
92 ///
93 /// Once the context is collected, we run a series of completion routines which
94 /// look at the context and produce completion items. One subtlety about this
95 /// phase is that completion engine should not filter by the substring which is
96 /// already present, it should give all possible variants for the identifier at
97 /// the caret. In other words, for
98 ///
99 /// ```no_run
100 /// fn f() {
101 ///     let foo = 92;
102 ///     let _ = bar$0
103 /// }
104 /// ```
105 ///
106 /// `foo` *should* be present among the completion variants. Filtering by
107 /// identifier prefix/fuzzy match should be done higher in the stack, together
108 /// with ordering of completions (currently this is done by the client).
109 ///
110 /// # Speculative Completion Problem
111 ///
112 /// There's a curious unsolved problem in the current implementation. Often, you
113 /// want to compute completions on a *slightly different* text document.
114 ///
115 /// In the simplest case, when the code looks like `let x = `, you want to
116 /// insert a fake identifier to get a better syntax tree: `let x = complete_me`.
117 ///
118 /// We do this in `CompletionContext`, and it works OK-enough for *syntax*
119 /// analysis. However, we might want to, eg, ask for the type of `complete_me`
120 /// variable, and that's where our current infrastructure breaks down. salsa
121 /// doesn't allow such "phantom" inputs.
122 ///
123 /// Another case where this would be instrumental is macro expansion. We want to
124 /// insert a fake ident and re-expand code. There's `expand_speculative` as a
125 /// work-around for this.
126 ///
127 /// A different use-case is completion of injection (examples and links in doc
128 /// comments). When computing completion for a path in a doc-comment, you want
129 /// to inject a fake path expression into the item being documented and complete
130 /// that.
131 ///
132 /// IntelliJ has CodeFragment/Context infrastructure for that. You can create a
133 /// temporary PSI node, and say that the context ("parent") of this node is some
134 /// existing node. Asking for, eg, type of this `CodeFragment` node works
135 /// correctly, as the underlying infrastructure makes use of contexts to do
136 /// analysis.
137 pub fn completions(
138     db: &RootDatabase,
139     config: &CompletionConfig,
140     position: FilePosition,
141 ) -> Option<Completions> {
142     let ctx = CompletionContext::new(db, position, config)?;
143
144     if ctx.no_completion_required() {
145         // No work required here.
146         return None;
147     }
148
149     let mut acc = Completions::default();
150     completions::attribute::complete_attribute(&mut acc, &ctx);
151     completions::fn_param::complete_fn_param(&mut acc, &ctx);
152     completions::keyword::complete_expr_keyword(&mut acc, &ctx);
153     completions::keyword::complete_use_tree_keyword(&mut acc, &ctx);
154     completions::snippet::complete_expr_snippet(&mut acc, &ctx);
155     completions::snippet::complete_item_snippet(&mut acc, &ctx);
156     completions::qualified_path::complete_qualified_path(&mut acc, &ctx);
157     completions::unqualified_path::complete_unqualified_path(&mut acc, &ctx);
158     completions::dot::complete_dot(&mut acc, &ctx);
159     completions::record::complete_record(&mut acc, &ctx);
160     completions::pattern::complete_pattern(&mut acc, &ctx);
161     completions::postfix::complete_postfix(&mut acc, &ctx);
162     completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
163     completions::trait_impl::complete_trait_impl(&mut acc, &ctx);
164     completions::mod_::complete_mod(&mut acc, &ctx);
165     completions::flyimport::import_on_the_fly(&mut acc, &ctx);
166     completions::lifetime::complete_lifetime(&mut acc, &ctx);
167     completions::lifetime::complete_label(&mut acc, &ctx);
168
169     Some(acc)
170 }
171
172 /// Resolves additional completion data at the position given.
173 pub fn resolve_completion_edits(
174     db: &RootDatabase,
175     config: &CompletionConfig,
176     position: FilePosition,
177     full_import_path: &str,
178     imported_name: String,
179 ) -> Option<Vec<TextEdit>> {
180     let ctx = CompletionContext::new(db, position, config)?;
181     let position_for_import = position_for_import(&ctx, None)?;
182     let scope = ImportScope::find_insert_use_container_with_macros(position_for_import, &ctx.sema)?;
183
184     let current_module = ctx.sema.scope(position_for_import).module()?;
185     let current_crate = current_module.krate();
186
187     let (import_path, item_to_import) = items_locator::items_with_name(
188         &ctx.sema,
189         current_crate,
190         NameToImport::Exact(imported_name),
191         items_locator::AssocItemSearch::Include,
192         Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT),
193     )
194     .filter_map(|candidate| {
195         current_module
196             .find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind)
197             .zip(Some(candidate))
198     })
199     .find(|(mod_path, _)| mod_path.to_string() == full_import_path)?;
200     let import =
201         LocatedImport::new(import_path.clone(), item_to_import, item_to_import, Some(import_path));
202
203     ImportEdit { import, scope }.to_text_edit(config.insert_use).map(|edit| vec![edit])
204 }
205
206 #[cfg(test)]
207 mod tests {
208     use crate::test_utils::{self, TEST_CONFIG};
209
210     struct DetailAndDocumentation<'a> {
211         detail: &'a str,
212         documentation: &'a str,
213     }
214
215     fn check_detail_and_documentation(ra_fixture: &str, expected: DetailAndDocumentation) {
216         let (db, position) = test_utils::position(ra_fixture);
217         let config = TEST_CONFIG;
218         let completions: Vec<_> = crate::completions(&db, &config, position).unwrap().into();
219         for item in completions {
220             if item.detail() == Some(expected.detail) {
221                 let opt = item.documentation();
222                 let doc = opt.as_ref().map(|it| it.as_str());
223                 assert_eq!(doc, Some(expected.documentation));
224                 return;
225             }
226         }
227         panic!("completion detail not found: {}", expected.detail)
228     }
229
230     fn check_no_completion(ra_fixture: &str) {
231         let (db, position) = test_utils::position(ra_fixture);
232         let config = TEST_CONFIG;
233
234         let completions: Option<Vec<String>> = crate::completions(&db, &config, position)
235             .and_then(|completions| {
236                 let completions: Vec<_> = completions.into();
237                 if completions.is_empty() {
238                     None
239                 } else {
240                     Some(completions)
241                 }
242             })
243             .map(|completions| {
244                 completions.into_iter().map(|completion| format!("{:?}", completion)).collect()
245             });
246
247         // `assert_eq` instead of `assert!(completions.is_none())` to get the list of completions if test will panic.
248         assert_eq!(completions, None, "Completions were generated, but weren't expected");
249     }
250
251     #[test]
252     fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() {
253         check_detail_and_documentation(
254             r#"
255 macro_rules! bar {
256     () => {
257         struct Bar;
258         impl Bar {
259             #[doc = "Do the foo"]
260             fn foo(&self) {}
261         }
262     }
263 }
264
265 bar!();
266
267 fn foo() {
268     let bar = Bar;
269     bar.fo$0;
270 }
271 "#,
272             DetailAndDocumentation { detail: "fn(&self)", documentation: "Do the foo" },
273         );
274     }
275
276     #[test]
277     fn test_completion_detail_from_macro_generated_struct_fn_doc_comment() {
278         check_detail_and_documentation(
279             r#"
280 macro_rules! bar {
281     () => {
282         struct Bar;
283         impl Bar {
284             /// Do the foo
285             fn foo(&self) {}
286         }
287     }
288 }
289
290 bar!();
291
292 fn foo() {
293     let bar = Bar;
294     bar.fo$0;
295 }
296 "#,
297             DetailAndDocumentation { detail: "fn(&self)", documentation: "Do the foo" },
298         );
299     }
300
301     #[test]
302     fn test_no_completions_required() {
303         // There must be no hint for 'in' keyword.
304         check_no_completion(r#"fn foo() { for i i$0 }"#);
305         // After 'in' keyword hints may be spawned.
306         check_detail_and_documentation(
307             r#"
308 /// Do the foo
309 fn foo() -> &'static str { "foo" }
310
311 fn bar() {
312     for c in fo$0
313 }
314 "#,
315             DetailAndDocumentation { detail: "fn() -> &str", documentation: "Do the foo" },
316         );
317     }
318 }