diff options
Diffstat (limited to 'crates/ide/src/completion')
18 files changed, 6892 insertions, 0 deletions
diff --git a/crates/ide/src/completion/complete_attribute.rs b/crates/ide/src/completion/complete_attribute.rs new file mode 100644 index 000000000..603d935de --- /dev/null +++ b/crates/ide/src/completion/complete_attribute.rs | |||
@@ -0,0 +1,644 @@ | |||
1 | //! Completion for attributes | ||
2 | //! | ||
3 | //! This module uses a bit of static metadata to provide completions | ||
4 | //! for built-in attributes. | ||
5 | |||
6 | use rustc_hash::FxHashSet; | ||
7 | use syntax::{ast, AstNode, SyntaxKind}; | ||
8 | |||
9 | use crate::completion::{ | ||
10 | completion_context::CompletionContext, | ||
11 | completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}, | ||
12 | }; | ||
13 | |||
14 | pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | ||
15 | let attribute = ctx.attribute_under_caret.as_ref()?; | ||
16 | match (attribute.path(), attribute.token_tree()) { | ||
17 | (Some(path), Some(token_tree)) if path.to_string() == "derive" => { | ||
18 | complete_derive(acc, ctx, token_tree) | ||
19 | } | ||
20 | (Some(path), Some(token_tree)) | ||
21 | if ["allow", "warn", "deny", "forbid"] | ||
22 | .iter() | ||
23 | .any(|lint_level| lint_level == &path.to_string()) => | ||
24 | { | ||
25 | complete_lint(acc, ctx, token_tree) | ||
26 | } | ||
27 | (_, Some(_token_tree)) => {} | ||
28 | _ => complete_attribute_start(acc, ctx, attribute), | ||
29 | } | ||
30 | Some(()) | ||
31 | } | ||
32 | |||
33 | fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { | ||
34 | for attr_completion in ATTRIBUTES { | ||
35 | let mut item = CompletionItem::new( | ||
36 | CompletionKind::Attribute, | ||
37 | ctx.source_range(), | ||
38 | attr_completion.label, | ||
39 | ) | ||
40 | .kind(CompletionItemKind::Attribute); | ||
41 | |||
42 | if let Some(lookup) = attr_completion.lookup { | ||
43 | item = item.lookup_by(lookup); | ||
44 | } | ||
45 | |||
46 | match (attr_completion.snippet, ctx.config.snippet_cap) { | ||
47 | (Some(snippet), Some(cap)) => { | ||
48 | item = item.insert_snippet(cap, snippet); | ||
49 | } | ||
50 | _ => {} | ||
51 | } | ||
52 | |||
53 | if attribute.kind() == ast::AttrKind::Inner || !attr_completion.prefer_inner { | ||
54 | acc.add(item); | ||
55 | } | ||
56 | } | ||
57 | } | ||
58 | |||
59 | struct AttrCompletion { | ||
60 | label: &'static str, | ||
61 | lookup: Option<&'static str>, | ||
62 | snippet: Option<&'static str>, | ||
63 | prefer_inner: bool, | ||
64 | } | ||
65 | |||
66 | impl AttrCompletion { | ||
67 | const fn prefer_inner(self) -> AttrCompletion { | ||
68 | AttrCompletion { prefer_inner: true, ..self } | ||
69 | } | ||
70 | } | ||
71 | |||
72 | const fn attr( | ||
73 | label: &'static str, | ||
74 | lookup: Option<&'static str>, | ||
75 | snippet: Option<&'static str>, | ||
76 | ) -> AttrCompletion { | ||
77 | AttrCompletion { label, lookup, snippet, prefer_inner: false } | ||
78 | } | ||
79 | |||
80 | const ATTRIBUTES: &[AttrCompletion] = &[ | ||
81 | attr("allow(…)", Some("allow"), Some("allow(${0:lint})")), | ||
82 | attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")), | ||
83 | attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")), | ||
84 | attr("deny(…)", Some("deny"), Some("deny(${0:lint})")), | ||
85 | attr(r#"deprecated = "…""#, Some("deprecated"), Some(r#"deprecated = "${0:reason}""#)), | ||
86 | attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)), | ||
87 | attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)), | ||
88 | attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(), | ||
89 | attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")), | ||
90 | // FIXME: resolve through macro resolution? | ||
91 | attr("global_allocator", None, None).prefer_inner(), | ||
92 | attr(r#"ignore = "…""#, Some("ignore"), Some(r#"ignore = "${0:reason}""#)), | ||
93 | attr("inline(…)", Some("inline"), Some("inline(${0:lint})")), | ||
94 | attr(r#"link_name = "…""#, Some("link_name"), Some(r#"link_name = "${0:symbol_name}""#)), | ||
95 | attr("link", None, None), | ||
96 | attr("macro_export", None, None), | ||
97 | attr("macro_use", None, None), | ||
98 | attr(r#"must_use = "…""#, Some("must_use"), Some(r#"must_use = "${0:reason}""#)), | ||
99 | attr("no_mangle", None, None), | ||
100 | attr("no_std", None, None).prefer_inner(), | ||
101 | attr("non_exhaustive", None, None), | ||
102 | attr("panic_handler", None, None).prefer_inner(), | ||
103 | attr("path = \"…\"", Some("path"), Some("path =\"${0:path}\"")), | ||
104 | attr("proc_macro", None, None), | ||
105 | attr("proc_macro_attribute", None, None), | ||
106 | attr("proc_macro_derive(…)", Some("proc_macro_derive"), Some("proc_macro_derive(${0:Trait})")), | ||
107 | attr("recursion_limit = …", Some("recursion_limit"), Some("recursion_limit = ${0:128}")) | ||
108 | .prefer_inner(), | ||
109 | attr("repr(…)", Some("repr"), Some("repr(${0:C})")), | ||
110 | attr( | ||
111 | "should_panic(…)", | ||
112 | Some("should_panic"), | ||
113 | Some(r#"should_panic(expected = "${0:reason}")"#), | ||
114 | ), | ||
115 | attr( | ||
116 | r#"target_feature = "…""#, | ||
117 | Some("target_feature"), | ||
118 | Some("target_feature = \"${0:feature}\""), | ||
119 | ), | ||
120 | attr("test", None, None), | ||
121 | attr("used", None, None), | ||
122 | attr("warn(…)", Some("warn"), Some("warn(${0:lint})")), | ||
123 | attr( | ||
124 | r#"windows_subsystem = "…""#, | ||
125 | Some("windows_subsystem"), | ||
126 | Some(r#"windows_subsystem = "${0:subsystem}""#), | ||
127 | ) | ||
128 | .prefer_inner(), | ||
129 | ]; | ||
130 | |||
131 | fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { | ||
132 | if let Ok(existing_derives) = parse_comma_sep_input(derive_input) { | ||
133 | for derive_completion in DEFAULT_DERIVE_COMPLETIONS | ||
134 | .into_iter() | ||
135 | .filter(|completion| !existing_derives.contains(completion.label)) | ||
136 | { | ||
137 | let mut label = derive_completion.label.to_owned(); | ||
138 | for dependency in derive_completion | ||
139 | .dependencies | ||
140 | .into_iter() | ||
141 | .filter(|&&dependency| !existing_derives.contains(dependency)) | ||
142 | { | ||
143 | label.push_str(", "); | ||
144 | label.push_str(dependency); | ||
145 | } | ||
146 | acc.add( | ||
147 | CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label) | ||
148 | .kind(CompletionItemKind::Attribute), | ||
149 | ); | ||
150 | } | ||
151 | |||
152 | for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) { | ||
153 | acc.add( | ||
154 | CompletionItem::new( | ||
155 | CompletionKind::Attribute, | ||
156 | ctx.source_range(), | ||
157 | custom_derive_name, | ||
158 | ) | ||
159 | .kind(CompletionItemKind::Attribute), | ||
160 | ); | ||
161 | } | ||
162 | } | ||
163 | } | ||
164 | |||
165 | fn complete_lint(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { | ||
166 | if let Ok(existing_lints) = parse_comma_sep_input(derive_input) { | ||
167 | for lint_completion in DEFAULT_LINT_COMPLETIONS | ||
168 | .into_iter() | ||
169 | .filter(|completion| !existing_lints.contains(completion.label)) | ||
170 | { | ||
171 | acc.add( | ||
172 | CompletionItem::new( | ||
173 | CompletionKind::Attribute, | ||
174 | ctx.source_range(), | ||
175 | lint_completion.label, | ||
176 | ) | ||
177 | .kind(CompletionItemKind::Attribute) | ||
178 | .detail(lint_completion.description), | ||
179 | ); | ||
180 | } | ||
181 | } | ||
182 | } | ||
183 | |||
184 | fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> { | ||
185 | match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) { | ||
186 | (Some(left_paren), Some(right_paren)) | ||
187 | if left_paren.kind() == SyntaxKind::L_PAREN | ||
188 | && right_paren.kind() == SyntaxKind::R_PAREN => | ||
189 | { | ||
190 | let mut input_derives = FxHashSet::default(); | ||
191 | let mut current_derive = String::new(); | ||
192 | for token in derive_input | ||
193 | .syntax() | ||
194 | .children_with_tokens() | ||
195 | .filter_map(|token| token.into_token()) | ||
196 | .skip_while(|token| token != &left_paren) | ||
197 | .skip(1) | ||
198 | .take_while(|token| token != &right_paren) | ||
199 | { | ||
200 | if SyntaxKind::COMMA == token.kind() { | ||
201 | if !current_derive.is_empty() { | ||
202 | input_derives.insert(current_derive); | ||
203 | current_derive = String::new(); | ||
204 | } | ||
205 | } else { | ||
206 | current_derive.push_str(token.to_string().trim()); | ||
207 | } | ||
208 | } | ||
209 | |||
210 | if !current_derive.is_empty() { | ||
211 | input_derives.insert(current_derive); | ||
212 | } | ||
213 | Ok(input_derives) | ||
214 | } | ||
215 | _ => Err(()), | ||
216 | } | ||
217 | } | ||
218 | |||
219 | fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet<String> { | ||
220 | let mut result = FxHashSet::default(); | ||
221 | ctx.scope.process_all_names(&mut |name, scope_def| { | ||
222 | if let hir::ScopeDef::MacroDef(mac) = scope_def { | ||
223 | if mac.is_derive_macro() { | ||
224 | result.insert(name.to_string()); | ||
225 | } | ||
226 | } | ||
227 | }); | ||
228 | result | ||
229 | } | ||
230 | |||
231 | struct DeriveCompletion { | ||
232 | label: &'static str, | ||
233 | dependencies: &'static [&'static str], | ||
234 | } | ||
235 | |||
236 | /// Standard Rust derives and the information about their dependencies | ||
237 | /// (the dependencies are needed so that the main derive don't break the compilation when added) | ||
238 | #[rustfmt::skip] | ||
239 | const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ | ||
240 | DeriveCompletion { label: "Clone", dependencies: &[] }, | ||
241 | DeriveCompletion { label: "Copy", dependencies: &["Clone"] }, | ||
242 | DeriveCompletion { label: "Debug", dependencies: &[] }, | ||
243 | DeriveCompletion { label: "Default", dependencies: &[] }, | ||
244 | DeriveCompletion { label: "Hash", dependencies: &[] }, | ||
245 | DeriveCompletion { label: "PartialEq", dependencies: &[] }, | ||
246 | DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] }, | ||
247 | DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] }, | ||
248 | DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] }, | ||
249 | ]; | ||
250 | |||
251 | struct LintCompletion { | ||
252 | label: &'static str, | ||
253 | description: &'static str, | ||
254 | } | ||
255 | |||
256 | #[rustfmt::skip] | ||
257 | const DEFAULT_LINT_COMPLETIONS: &[LintCompletion] = &[ | ||
258 | LintCompletion { label: "absolute_paths_not_starting_with_crate", description: r#"fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name"# }, | ||
259 | LintCompletion { label: "anonymous_parameters", description: r#"detects anonymous parameters"# }, | ||
260 | LintCompletion { label: "box_pointers", description: r#"use of owned (Box type) heap memory"# }, | ||
261 | LintCompletion { label: "deprecated_in_future", description: r#"detects use of items that will be deprecated in a future version"# }, | ||
262 | LintCompletion { label: "elided_lifetimes_in_paths", description: r#"hidden lifetime parameters in types are deprecated"# }, | ||
263 | LintCompletion { label: "explicit_outlives_requirements", description: r#"outlives requirements can be inferred"# }, | ||
264 | LintCompletion { label: "indirect_structural_match", description: r#"pattern with const indirectly referencing non-structural-match type"# }, | ||
265 | LintCompletion { label: "keyword_idents", description: r#"detects edition keywords being used as an identifier"# }, | ||
266 | LintCompletion { label: "macro_use_extern_crate", description: r#"the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system"# }, | ||
267 | LintCompletion { label: "meta_variable_misuse", description: r#"possible meta-variable misuse at macro definition"# }, | ||
268 | LintCompletion { label: "missing_copy_implementations", description: r#"detects potentially-forgotten implementations of `Copy`"# }, | ||
269 | LintCompletion { label: "missing_crate_level_docs", description: r#"detects crates with no crate-level documentation"# }, | ||
270 | LintCompletion { label: "missing_debug_implementations", description: r#"detects missing implementations of Debug"# }, | ||
271 | LintCompletion { label: "missing_docs", description: r#"detects missing documentation for public members"# }, | ||
272 | LintCompletion { label: "missing_doc_code_examples", description: r#"detects publicly-exported items without code samples in their documentation"# }, | ||
273 | LintCompletion { label: "non_ascii_idents", description: r#"detects non-ASCII identifiers"# }, | ||
274 | LintCompletion { label: "private_doc_tests", description: r#"detects code samples in docs of private items not documented by rustdoc"# }, | ||
275 | LintCompletion { label: "single_use_lifetimes", description: r#"detects lifetime parameters that are only used once"# }, | ||
276 | LintCompletion { label: "trivial_casts", description: r#"detects trivial casts which could be removed"# }, | ||
277 | LintCompletion { label: "trivial_numeric_casts", description: r#"detects trivial casts of numeric types which could be removed"# }, | ||
278 | LintCompletion { label: "unaligned_references", description: r#"detects unaligned references to fields of packed structs"# }, | ||
279 | LintCompletion { label: "unreachable_pub", description: r#"`pub` items not reachable from crate root"# }, | ||
280 | LintCompletion { label: "unsafe_code", description: r#"usage of `unsafe` code"# }, | ||
281 | LintCompletion { label: "unsafe_op_in_unsafe_fn", description: r#"unsafe operations in unsafe functions without an explicit unsafe block are deprecated"# }, | ||
282 | LintCompletion { label: "unstable_features", description: r#"enabling unstable features (deprecated. do not use)"# }, | ||
283 | LintCompletion { label: "unused_crate_dependencies", description: r#"crate dependencies that are never used"# }, | ||
284 | LintCompletion { label: "unused_extern_crates", description: r#"extern crates that are never used"# }, | ||
285 | LintCompletion { label: "unused_import_braces", description: r#"unnecessary braces around an imported item"# }, | ||
286 | LintCompletion { label: "unused_lifetimes", description: r#"detects lifetime parameters that are never used"# }, | ||
287 | LintCompletion { label: "unused_qualifications", description: r#"detects unnecessarily qualified names"# }, | ||
288 | LintCompletion { label: "unused_results", description: r#"unused result of an expression in a statement"# }, | ||
289 | LintCompletion { label: "variant_size_differences", description: r#"detects enums with widely varying variant sizes"# }, | ||
290 | LintCompletion { label: "array_into_iter", description: r#"detects calling `into_iter` on arrays"# }, | ||
291 | LintCompletion { label: "asm_sub_register", description: r#"using only a subset of a register for inline asm inputs"# }, | ||
292 | LintCompletion { label: "bare_trait_objects", description: r#"suggest using `dyn Trait` for trait objects"# }, | ||
293 | LintCompletion { label: "bindings_with_variant_name", description: r#"detects pattern bindings with the same name as one of the matched variants"# }, | ||
294 | LintCompletion { label: "cenum_impl_drop_cast", description: r#"a C-like enum implementing Drop is cast"# }, | ||
295 | LintCompletion { label: "clashing_extern_declarations", description: r#"detects when an extern fn has been declared with the same name but different types"# }, | ||
296 | LintCompletion { label: "coherence_leak_check", description: r#"distinct impls distinguished only by the leak-check code"# }, | ||
297 | LintCompletion { label: "confusable_idents", description: r#"detects visually confusable pairs between identifiers"# }, | ||
298 | LintCompletion { label: "dead_code", description: r#"detect unused, unexported items"# }, | ||
299 | LintCompletion { label: "deprecated", description: r#"detects use of deprecated items"# }, | ||
300 | LintCompletion { label: "ellipsis_inclusive_range_patterns", description: r#"`...` range patterns are deprecated"# }, | ||
301 | LintCompletion { label: "exported_private_dependencies", description: r#"public interface leaks type from a private dependency"# }, | ||
302 | LintCompletion { label: "illegal_floating_point_literal_pattern", description: r#"floating-point literals cannot be used in patterns"# }, | ||
303 | LintCompletion { label: "improper_ctypes", description: r#"proper use of libc types in foreign modules"# }, | ||
304 | LintCompletion { label: "improper_ctypes_definitions", description: r#"proper use of libc types in foreign item definitions"# }, | ||
305 | LintCompletion { label: "incomplete_features", description: r#"incomplete features that may function improperly in some or all cases"# }, | ||
306 | LintCompletion { label: "inline_no_sanitize", description: r#"detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`"# }, | ||
307 | LintCompletion { label: "intra_doc_link_resolution_failure", description: r#"failures in resolving intra-doc link targets"# }, | ||
308 | LintCompletion { label: "invalid_codeblock_attributes", description: r#"codeblock attribute looks a lot like a known one"# }, | ||
309 | LintCompletion { label: "invalid_value", description: r#"an invalid value is being created (such as a NULL reference)"# }, | ||
310 | LintCompletion { label: "irrefutable_let_patterns", description: r#"detects irrefutable patterns in if-let and while-let statements"# }, | ||
311 | LintCompletion { label: "late_bound_lifetime_arguments", description: r#"detects generic lifetime arguments in path segments with late bound lifetime parameters"# }, | ||
312 | LintCompletion { label: "mixed_script_confusables", description: r#"detects Unicode scripts whose mixed script confusables codepoints are solely used"# }, | ||
313 | LintCompletion { label: "mutable_borrow_reservation_conflict", description: r#"reservation of a two-phased borrow conflicts with other shared borrows"# }, | ||
314 | LintCompletion { label: "non_camel_case_types", description: r#"types, variants, traits and type parameters should have camel case names"# }, | ||
315 | LintCompletion { label: "non_shorthand_field_patterns", description: r#"using `Struct { x: x }` instead of `Struct { x }` in a pattern"# }, | ||
316 | LintCompletion { label: "non_snake_case", description: r#"variables, methods, functions, lifetime parameters and modules should have snake case names"# }, | ||
317 | LintCompletion { label: "non_upper_case_globals", description: r#"static constants should have uppercase identifiers"# }, | ||
318 | LintCompletion { label: "no_mangle_generic_items", description: r#"generic items must be mangled"# }, | ||
319 | LintCompletion { label: "overlapping_patterns", description: r#"detects overlapping patterns"# }, | ||
320 | LintCompletion { label: "path_statements", description: r#"path statements with no effect"# }, | ||
321 | LintCompletion { label: "private_in_public", description: r#"detect private items in public interfaces not caught by the old implementation"# }, | ||
322 | LintCompletion { label: "proc_macro_derive_resolution_fallback", description: r#"detects proc macro derives using inaccessible names from parent modules"# }, | ||
323 | LintCompletion { label: "redundant_semicolons", description: r#"detects unnecessary trailing semicolons"# }, | ||
324 | LintCompletion { label: "renamed_and_removed_lints", description: r#"lints that have been renamed or removed"# }, | ||
325 | LintCompletion { label: "safe_packed_borrows", description: r#"safe borrows of fields of packed structs were erroneously allowed"# }, | ||
326 | LintCompletion { label: "stable_features", description: r#"stable features found in `#[feature]` directive"# }, | ||
327 | LintCompletion { label: "trivial_bounds", description: r#"these bounds don't depend on an type parameters"# }, | ||
328 | LintCompletion { label: "type_alias_bounds", description: r#"bounds in type aliases are not enforced"# }, | ||
329 | LintCompletion { label: "tyvar_behind_raw_pointer", description: r#"raw pointer to an inference variable"# }, | ||
330 | LintCompletion { label: "uncommon_codepoints", description: r#"detects uncommon Unicode codepoints in identifiers"# }, | ||
331 | LintCompletion { label: "unconditional_recursion", description: r#"functions that cannot return without calling themselves"# }, | ||
332 | LintCompletion { label: "unknown_lints", description: r#"unrecognized lint attribute"# }, | ||
333 | LintCompletion { label: "unnameable_test_items", description: r#"detects an item that cannot be named being marked as `#[test_case]`"# }, | ||
334 | LintCompletion { label: "unreachable_code", description: r#"detects unreachable code paths"# }, | ||
335 | LintCompletion { label: "unreachable_patterns", description: r#"detects unreachable patterns"# }, | ||
336 | LintCompletion { label: "unstable_name_collisions", description: r#"detects name collision with an existing but unstable method"# }, | ||
337 | LintCompletion { label: "unused_allocation", description: r#"detects unnecessary allocations that can be eliminated"# }, | ||
338 | LintCompletion { label: "unused_assignments", description: r#"detect assignments that will never be read"# }, | ||
339 | LintCompletion { label: "unused_attributes", description: r#"detects attributes that were not used by the compiler"# }, | ||
340 | LintCompletion { label: "unused_braces", description: r#"unnecessary braces around an expression"# }, | ||
341 | LintCompletion { label: "unused_comparisons", description: r#"comparisons made useless by limits of the types involved"# }, | ||
342 | LintCompletion { label: "unused_doc_comments", description: r#"detects doc comments that aren't used by rustdoc"# }, | ||
343 | LintCompletion { label: "unused_features", description: r#"unused features found in crate-level `#[feature]` directives"# }, | ||
344 | LintCompletion { label: "unused_imports", description: r#"imports that are never used"# }, | ||
345 | LintCompletion { label: "unused_labels", description: r#"detects labels that are never used"# }, | ||
346 | LintCompletion { label: "unused_macros", description: r#"detects macros that were not used"# }, | ||
347 | LintCompletion { label: "unused_must_use", description: r#"unused result of a type flagged as `#[must_use]`"# }, | ||
348 | LintCompletion { label: "unused_mut", description: r#"detect mut variables which don't need to be mutable"# }, | ||
349 | LintCompletion { label: "unused_parens", description: r#"`if`, `match`, `while` and `return` do not need parentheses"# }, | ||
350 | LintCompletion { label: "unused_unsafe", description: r#"unnecessary use of an `unsafe` block"# }, | ||
351 | LintCompletion { label: "unused_variables", description: r#"detect variables which are not used in any way"# }, | ||
352 | LintCompletion { label: "warnings", description: r#"mass-change the level for lints which produce warnings"# }, | ||
353 | LintCompletion { label: "where_clauses_object_safety", description: r#"checks the object safety of where clauses"# }, | ||
354 | LintCompletion { label: "while_true", description: r#"suggest using `loop { }` instead of `while true { }`"# }, | ||
355 | LintCompletion { label: "ambiguous_associated_items", description: r#"ambiguous associated items"# }, | ||
356 | LintCompletion { label: "arithmetic_overflow", description: r#"arithmetic operation overflows"# }, | ||
357 | LintCompletion { label: "conflicting_repr_hints", description: r#"conflicts between `#[repr(..)]` hints that were previously accepted and used in practice"# }, | ||
358 | LintCompletion { label: "const_err", description: r#"constant evaluation detected erroneous expression"# }, | ||
359 | LintCompletion { label: "ill_formed_attribute_input", description: r#"ill-formed attribute inputs that were previously accepted and used in practice"# }, | ||
360 | LintCompletion { label: "incomplete_include", description: r#"trailing content in included file"# }, | ||
361 | LintCompletion { label: "invalid_type_param_default", description: r#"type parameter default erroneously allowed in invalid location"# }, | ||
362 | LintCompletion { label: "macro_expanded_macro_exports_accessed_by_absolute_paths", description: r#"macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths"# }, | ||
363 | LintCompletion { label: "missing_fragment_specifier", description: r#"detects missing fragment specifiers in unused `macro_rules!` patterns"# }, | ||
364 | LintCompletion { label: "mutable_transmutes", description: r#"mutating transmuted &mut T from &T may cause undefined behavior"# }, | ||
365 | LintCompletion { label: "no_mangle_const_items", description: r#"const items will not have their symbols exported"# }, | ||
366 | LintCompletion { label: "order_dependent_trait_objects", description: r#"trait-object types were treated as different depending on marker-trait order"# }, | ||
367 | LintCompletion { label: "overflowing_literals", description: r#"literal out of range for its type"# }, | ||
368 | LintCompletion { label: "patterns_in_fns_without_body", description: r#"patterns in functions without body were erroneously allowed"# }, | ||
369 | LintCompletion { label: "pub_use_of_private_extern_crate", description: r#"detect public re-exports of private extern crates"# }, | ||
370 | LintCompletion { label: "soft_unstable", description: r#"a feature gate that doesn't break dependent crates"# }, | ||
371 | LintCompletion { label: "unconditional_panic", description: r#"operation will cause a panic at runtime"# }, | ||
372 | LintCompletion { label: "unknown_crate_types", description: r#"unknown crate type found in `#[crate_type]` directive"# }, | ||
373 | ]; | ||
374 | |||
375 | #[cfg(test)] | ||
376 | mod tests { | ||
377 | use expect::{expect, Expect}; | ||
378 | |||
379 | use crate::completion::{test_utils::completion_list, CompletionKind}; | ||
380 | |||
381 | fn check(ra_fixture: &str, expect: Expect) { | ||
382 | let actual = completion_list(ra_fixture, CompletionKind::Attribute); | ||
383 | expect.assert_eq(&actual); | ||
384 | } | ||
385 | |||
386 | #[test] | ||
387 | fn empty_derive_completion() { | ||
388 | check( | ||
389 | r#" | ||
390 | #[derive(<|>)] | ||
391 | struct Test {} | ||
392 | "#, | ||
393 | expect![[r#" | ||
394 | at Clone | ||
395 | at Copy, Clone | ||
396 | at Debug | ||
397 | at Default | ||
398 | at Eq, PartialEq | ||
399 | at Hash | ||
400 | at Ord, PartialOrd, Eq, PartialEq | ||
401 | at PartialEq | ||
402 | at PartialOrd, PartialEq | ||
403 | "#]], | ||
404 | ); | ||
405 | } | ||
406 | |||
407 | #[test] | ||
408 | fn empty_lint_completion() { | ||
409 | check( | ||
410 | r#"#[allow(<|>)]"#, | ||
411 | expect![[r#" | ||
412 | at absolute_paths_not_starting_with_crate fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name | ||
413 | at ambiguous_associated_items ambiguous associated items | ||
414 | at anonymous_parameters detects anonymous parameters | ||
415 | at arithmetic_overflow arithmetic operation overflows | ||
416 | at array_into_iter detects calling `into_iter` on arrays | ||
417 | at asm_sub_register using only a subset of a register for inline asm inputs | ||
418 | at bare_trait_objects suggest using `dyn Trait` for trait objects | ||
419 | at bindings_with_variant_name detects pattern bindings with the same name as one of the matched variants | ||
420 | at box_pointers use of owned (Box type) heap memory | ||
421 | at cenum_impl_drop_cast a C-like enum implementing Drop is cast | ||
422 | at clashing_extern_declarations detects when an extern fn has been declared with the same name but different types | ||
423 | at coherence_leak_check distinct impls distinguished only by the leak-check code | ||
424 | at conflicting_repr_hints conflicts between `#[repr(..)]` hints that were previously accepted and used in practice | ||
425 | at confusable_idents detects visually confusable pairs between identifiers | ||
426 | at const_err constant evaluation detected erroneous expression | ||
427 | at dead_code detect unused, unexported items | ||
428 | at deprecated detects use of deprecated items | ||
429 | at deprecated_in_future detects use of items that will be deprecated in a future version | ||
430 | at elided_lifetimes_in_paths hidden lifetime parameters in types are deprecated | ||
431 | at ellipsis_inclusive_range_patterns `...` range patterns are deprecated | ||
432 | at explicit_outlives_requirements outlives requirements can be inferred | ||
433 | at exported_private_dependencies public interface leaks type from a private dependency | ||
434 | at ill_formed_attribute_input ill-formed attribute inputs that were previously accepted and used in practice | ||
435 | at illegal_floating_point_literal_pattern floating-point literals cannot be used in patterns | ||
436 | at improper_ctypes proper use of libc types in foreign modules | ||
437 | at improper_ctypes_definitions proper use of libc types in foreign item definitions | ||
438 | at incomplete_features incomplete features that may function improperly in some or all cases | ||
439 | at incomplete_include trailing content in included file | ||
440 | at indirect_structural_match pattern with const indirectly referencing non-structural-match type | ||
441 | at inline_no_sanitize detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]` | ||
442 | at intra_doc_link_resolution_failure failures in resolving intra-doc link targets | ||
443 | at invalid_codeblock_attributes codeblock attribute looks a lot like a known one | ||
444 | at invalid_type_param_default type parameter default erroneously allowed in invalid location | ||
445 | at invalid_value an invalid value is being created (such as a NULL reference) | ||
446 | at irrefutable_let_patterns detects irrefutable patterns in if-let and while-let statements | ||
447 | at keyword_idents detects edition keywords being used as an identifier | ||
448 | at late_bound_lifetime_arguments detects generic lifetime arguments in path segments with late bound lifetime parameters | ||
449 | at macro_expanded_macro_exports_accessed_by_absolute_paths macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths | ||
450 | at macro_use_extern_crate the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system | ||
451 | at meta_variable_misuse possible meta-variable misuse at macro definition | ||
452 | at missing_copy_implementations detects potentially-forgotten implementations of `Copy` | ||
453 | at missing_crate_level_docs detects crates with no crate-level documentation | ||
454 | at missing_debug_implementations detects missing implementations of Debug | ||
455 | at missing_doc_code_examples detects publicly-exported items without code samples in their documentation | ||
456 | at missing_docs detects missing documentation for public members | ||
457 | at missing_fragment_specifier detects missing fragment specifiers in unused `macro_rules!` patterns | ||
458 | at mixed_script_confusables detects Unicode scripts whose mixed script confusables codepoints are solely used | ||
459 | at mutable_borrow_reservation_conflict reservation of a two-phased borrow conflicts with other shared borrows | ||
460 | at mutable_transmutes mutating transmuted &mut T from &T may cause undefined behavior | ||
461 | at no_mangle_const_items const items will not have their symbols exported | ||
462 | at no_mangle_generic_items generic items must be mangled | ||
463 | at non_ascii_idents detects non-ASCII identifiers | ||
464 | at non_camel_case_types types, variants, traits and type parameters should have camel case names | ||
465 | at non_shorthand_field_patterns using `Struct { x: x }` instead of `Struct { x }` in a pattern | ||
466 | at non_snake_case variables, methods, functions, lifetime parameters and modules should have snake case names | ||
467 | at non_upper_case_globals static constants should have uppercase identifiers | ||
468 | at order_dependent_trait_objects trait-object types were treated as different depending on marker-trait order | ||
469 | at overflowing_literals literal out of range for its type | ||
470 | at overlapping_patterns detects overlapping patterns | ||
471 | at path_statements path statements with no effect | ||
472 | at patterns_in_fns_without_body patterns in functions without body were erroneously allowed | ||
473 | at private_doc_tests detects code samples in docs of private items not documented by rustdoc | ||
474 | at private_in_public detect private items in public interfaces not caught by the old implementation | ||
475 | at proc_macro_derive_resolution_fallback detects proc macro derives using inaccessible names from parent modules | ||
476 | at pub_use_of_private_extern_crate detect public re-exports of private extern crates | ||
477 | at redundant_semicolons detects unnecessary trailing semicolons | ||
478 | at renamed_and_removed_lints lints that have been renamed or removed | ||
479 | at safe_packed_borrows safe borrows of fields of packed structs were erroneously allowed | ||
480 | at single_use_lifetimes detects lifetime parameters that are only used once | ||
481 | at soft_unstable a feature gate that doesn't break dependent crates | ||
482 | at stable_features stable features found in `#[feature]` directive | ||
483 | at trivial_bounds these bounds don't depend on an type parameters | ||
484 | at trivial_casts detects trivial casts which could be removed | ||
485 | at trivial_numeric_casts detects trivial casts of numeric types which could be removed | ||
486 | at type_alias_bounds bounds in type aliases are not enforced | ||
487 | at tyvar_behind_raw_pointer raw pointer to an inference variable | ||
488 | at unaligned_references detects unaligned references to fields of packed structs | ||
489 | at uncommon_codepoints detects uncommon Unicode codepoints in identifiers | ||
490 | at unconditional_panic operation will cause a panic at runtime | ||
491 | at unconditional_recursion functions that cannot return without calling themselves | ||
492 | at unknown_crate_types unknown crate type found in `#[crate_type]` directive | ||
493 | at unknown_lints unrecognized lint attribute | ||
494 | at unnameable_test_items detects an item that cannot be named being marked as `#[test_case]` | ||
495 | at unreachable_code detects unreachable code paths | ||
496 | at unreachable_patterns detects unreachable patterns | ||
497 | at unreachable_pub `pub` items not reachable from crate root | ||
498 | at unsafe_code usage of `unsafe` code | ||
499 | at unsafe_op_in_unsafe_fn unsafe operations in unsafe functions without an explicit unsafe block are deprecated | ||
500 | at unstable_features enabling unstable features (deprecated. do not use) | ||
501 | at unstable_name_collisions detects name collision with an existing but unstable method | ||
502 | at unused_allocation detects unnecessary allocations that can be eliminated | ||
503 | at unused_assignments detect assignments that will never be read | ||
504 | at unused_attributes detects attributes that were not used by the compiler | ||
505 | at unused_braces unnecessary braces around an expression | ||
506 | at unused_comparisons comparisons made useless by limits of the types involved | ||
507 | at unused_crate_dependencies crate dependencies that are never used | ||
508 | at unused_doc_comments detects doc comments that aren't used by rustdoc | ||
509 | at unused_extern_crates extern crates that are never used | ||
510 | at unused_features unused features found in crate-level `#[feature]` directives | ||
511 | at unused_import_braces unnecessary braces around an imported item | ||
512 | at unused_imports imports that are never used | ||
513 | at unused_labels detects labels that are never used | ||
514 | at unused_lifetimes detects lifetime parameters that are never used | ||
515 | at unused_macros detects macros that were not used | ||
516 | at unused_must_use unused result of a type flagged as `#[must_use]` | ||
517 | at unused_mut detect mut variables which don't need to be mutable | ||
518 | at unused_parens `if`, `match`, `while` and `return` do not need parentheses | ||
519 | at unused_qualifications detects unnecessarily qualified names | ||
520 | at unused_results unused result of an expression in a statement | ||
521 | at unused_unsafe unnecessary use of an `unsafe` block | ||
522 | at unused_variables detect variables which are not used in any way | ||
523 | at variant_size_differences detects enums with widely varying variant sizes | ||
524 | at warnings mass-change the level for lints which produce warnings | ||
525 | at where_clauses_object_safety checks the object safety of where clauses | ||
526 | at while_true suggest using `loop { }` instead of `while true { }` | ||
527 | "#]], | ||
528 | ) | ||
529 | } | ||
530 | |||
531 | #[test] | ||
532 | fn no_completion_for_incorrect_derive() { | ||
533 | check( | ||
534 | r#" | ||
535 | #[derive{<|>)] | ||
536 | struct Test {} | ||
537 | "#, | ||
538 | expect![[r#""#]], | ||
539 | ) | ||
540 | } | ||
541 | |||
542 | #[test] | ||
543 | fn derive_with_input_completion() { | ||
544 | check( | ||
545 | r#" | ||
546 | #[derive(serde::Serialize, PartialEq, <|>)] | ||
547 | struct Test {} | ||
548 | "#, | ||
549 | expect![[r#" | ||
550 | at Clone | ||
551 | at Copy, Clone | ||
552 | at Debug | ||
553 | at Default | ||
554 | at Eq | ||
555 | at Hash | ||
556 | at Ord, PartialOrd, Eq | ||
557 | at PartialOrd | ||
558 | "#]], | ||
559 | ) | ||
560 | } | ||
561 | |||
562 | #[test] | ||
563 | fn test_attribute_completion() { | ||
564 | check( | ||
565 | r#"#[<|>]"#, | ||
566 | expect![[r#" | ||
567 | at allow(…) | ||
568 | at cfg(…) | ||
569 | at cfg_attr(…) | ||
570 | at deny(…) | ||
571 | at deprecated = "…" | ||
572 | at derive(…) | ||
573 | at doc = "…" | ||
574 | at forbid(…) | ||
575 | at ignore = "…" | ||
576 | at inline(…) | ||
577 | at link | ||
578 | at link_name = "…" | ||
579 | at macro_export | ||
580 | at macro_use | ||
581 | at must_use = "…" | ||
582 | at no_mangle | ||
583 | at non_exhaustive | ||
584 | at path = "…" | ||
585 | at proc_macro | ||
586 | at proc_macro_attribute | ||
587 | at proc_macro_derive(…) | ||
588 | at repr(…) | ||
589 | at should_panic(…) | ||
590 | at target_feature = "…" | ||
591 | at test | ||
592 | at used | ||
593 | at warn(…) | ||
594 | "#]], | ||
595 | ) | ||
596 | } | ||
597 | |||
598 | #[test] | ||
599 | fn test_attribute_completion_inside_nested_attr() { | ||
600 | check(r#"#[cfg(<|>)]"#, expect![[]]) | ||
601 | } | ||
602 | |||
603 | #[test] | ||
604 | fn test_inner_attribute_completion() { | ||
605 | check( | ||
606 | r"#![<|>]", | ||
607 | expect![[r#" | ||
608 | at allow(…) | ||
609 | at cfg(…) | ||
610 | at cfg_attr(…) | ||
611 | at deny(…) | ||
612 | at deprecated = "…" | ||
613 | at derive(…) | ||
614 | at doc = "…" | ||
615 | at feature(…) | ||
616 | at forbid(…) | ||
617 | at global_allocator | ||
618 | at ignore = "…" | ||
619 | at inline(…) | ||
620 | at link | ||
621 | at link_name = "…" | ||
622 | at macro_export | ||
623 | at macro_use | ||
624 | at must_use = "…" | ||
625 | at no_mangle | ||
626 | at no_std | ||
627 | at non_exhaustive | ||
628 | at panic_handler | ||
629 | at path = "…" | ||
630 | at proc_macro | ||
631 | at proc_macro_attribute | ||
632 | at proc_macro_derive(…) | ||
633 | at recursion_limit = … | ||
634 | at repr(…) | ||
635 | at should_panic(…) | ||
636 | at target_feature = "…" | ||
637 | at test | ||
638 | at used | ||
639 | at warn(…) | ||
640 | at windows_subsystem = "…" | ||
641 | "#]], | ||
642 | ); | ||
643 | } | ||
644 | } | ||
diff --git a/crates/ide/src/completion/complete_dot.rs b/crates/ide/src/completion/complete_dot.rs new file mode 100644 index 000000000..532665285 --- /dev/null +++ b/crates/ide/src/completion/complete_dot.rs | |||
@@ -0,0 +1,416 @@ | |||
1 | //! Completes references after dot (fields and method calls). | ||
2 | |||
3 | use hir::{HasVisibility, Type}; | ||
4 | use rustc_hash::FxHashSet; | ||
5 | use test_utils::mark; | ||
6 | |||
7 | use crate::completion::{completion_context::CompletionContext, completion_item::Completions}; | ||
8 | |||
9 | /// Complete dot accesses, i.e. fields or methods. | ||
10 | pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { | ||
11 | let dot_receiver = match &ctx.dot_receiver { | ||
12 | Some(expr) => expr, | ||
13 | _ => return, | ||
14 | }; | ||
15 | |||
16 | let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { | ||
17 | Some(ty) => ty, | ||
18 | _ => return, | ||
19 | }; | ||
20 | |||
21 | if ctx.is_call { | ||
22 | mark::hit!(test_no_struct_field_completion_for_method_call); | ||
23 | } else { | ||
24 | complete_fields(acc, ctx, &receiver_ty); | ||
25 | } | ||
26 | complete_methods(acc, ctx, &receiver_ty); | ||
27 | } | ||
28 | |||
29 | fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { | ||
30 | for receiver in receiver.autoderef(ctx.db) { | ||
31 | for (field, ty) in receiver.fields(ctx.db) { | ||
32 | if ctx.scope.module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) { | ||
33 | // Skip private field. FIXME: If the definition location of the | ||
34 | // field is editable, we should show the completion | ||
35 | continue; | ||
36 | } | ||
37 | acc.add_field(ctx, field, &ty); | ||
38 | } | ||
39 | for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() { | ||
40 | // FIXME: Handle visibility | ||
41 | acc.add_tuple_field(ctx, i, &ty); | ||
42 | } | ||
43 | } | ||
44 | } | ||
45 | |||
46 | fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { | ||
47 | if let Some(krate) = ctx.krate { | ||
48 | let mut seen_methods = FxHashSet::default(); | ||
49 | let traits_in_scope = ctx.scope.traits_in_scope(); | ||
50 | receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| { | ||
51 | if func.has_self_param(ctx.db) | ||
52 | && ctx.scope.module().map_or(true, |m| func.is_visible_from(ctx.db, m)) | ||
53 | && seen_methods.insert(func.name(ctx.db)) | ||
54 | { | ||
55 | acc.add_function(ctx, func, None); | ||
56 | } | ||
57 | None::<()> | ||
58 | }); | ||
59 | } | ||
60 | } | ||
61 | |||
62 | #[cfg(test)] | ||
63 | mod tests { | ||
64 | use expect::{expect, Expect}; | ||
65 | use test_utils::mark; | ||
66 | |||
67 | use crate::completion::{test_utils::completion_list, CompletionKind}; | ||
68 | |||
69 | fn check(ra_fixture: &str, expect: Expect) { | ||
70 | let actual = completion_list(ra_fixture, CompletionKind::Reference); | ||
71 | expect.assert_eq(&actual); | ||
72 | } | ||
73 | |||
74 | #[test] | ||
75 | fn test_struct_field_and_method_completion() { | ||
76 | check( | ||
77 | r#" | ||
78 | struct S { foo: u32 } | ||
79 | impl S { | ||
80 | fn bar(&self) {} | ||
81 | } | ||
82 | fn foo(s: S) { s.<|> } | ||
83 | "#, | ||
84 | expect![[r#" | ||
85 | me bar() fn bar(&self) | ||
86 | fd foo u32 | ||
87 | "#]], | ||
88 | ); | ||
89 | } | ||
90 | |||
91 | #[test] | ||
92 | fn test_struct_field_completion_self() { | ||
93 | check( | ||
94 | r#" | ||
95 | struct S { the_field: (u32,) } | ||
96 | impl S { | ||
97 | fn foo(self) { self.<|> } | ||
98 | } | ||
99 | "#, | ||
100 | expect![[r#" | ||
101 | me foo() fn foo(self) | ||
102 | fd the_field (u32,) | ||
103 | "#]], | ||
104 | ) | ||
105 | } | ||
106 | |||
107 | #[test] | ||
108 | fn test_struct_field_completion_autoderef() { | ||
109 | check( | ||
110 | r#" | ||
111 | struct A { the_field: (u32, i32) } | ||
112 | impl A { | ||
113 | fn foo(&self) { self.<|> } | ||
114 | } | ||
115 | "#, | ||
116 | expect![[r#" | ||
117 | me foo() fn foo(&self) | ||
118 | fd the_field (u32, i32) | ||
119 | "#]], | ||
120 | ) | ||
121 | } | ||
122 | |||
123 | #[test] | ||
124 | fn test_no_struct_field_completion_for_method_call() { | ||
125 | mark::check!(test_no_struct_field_completion_for_method_call); | ||
126 | check( | ||
127 | r#" | ||
128 | struct A { the_field: u32 } | ||
129 | fn foo(a: A) { a.<|>() } | ||
130 | "#, | ||
131 | expect![[""]], | ||
132 | ); | ||
133 | } | ||
134 | |||
135 | #[test] | ||
136 | fn test_visibility_filtering() { | ||
137 | check( | ||
138 | r#" | ||
139 | mod inner { | ||
140 | pub struct A { | ||
141 | private_field: u32, | ||
142 | pub pub_field: u32, | ||
143 | pub(crate) crate_field: u32, | ||
144 | pub(super) super_field: u32, | ||
145 | } | ||
146 | } | ||
147 | fn foo(a: inner::A) { a.<|> } | ||
148 | "#, | ||
149 | expect![[r#" | ||
150 | fd crate_field u32 | ||
151 | fd pub_field u32 | ||
152 | fd super_field u32 | ||
153 | "#]], | ||
154 | ); | ||
155 | |||
156 | check( | ||
157 | r#" | ||
158 | struct A {} | ||
159 | mod m { | ||
160 | impl super::A { | ||
161 | fn private_method(&self) {} | ||
162 | pub(super) fn the_method(&self) {} | ||
163 | } | ||
164 | } | ||
165 | fn foo(a: A) { a.<|> } | ||
166 | "#, | ||
167 | expect![[r#" | ||
168 | me the_method() pub(super) fn the_method(&self) | ||
169 | "#]], | ||
170 | ); | ||
171 | } | ||
172 | |||
173 | #[test] | ||
174 | fn test_union_field_completion() { | ||
175 | check( | ||
176 | r#" | ||
177 | union U { field: u8, other: u16 } | ||
178 | fn foo(u: U) { u.<|> } | ||
179 | "#, | ||
180 | expect![[r#" | ||
181 | fd field u8 | ||
182 | fd other u16 | ||
183 | "#]], | ||
184 | ); | ||
185 | } | ||
186 | |||
187 | #[test] | ||
188 | fn test_method_completion_only_fitting_impls() { | ||
189 | check( | ||
190 | r#" | ||
191 | struct A<T> {} | ||
192 | impl A<u32> { | ||
193 | fn the_method(&self) {} | ||
194 | } | ||
195 | impl A<i32> { | ||
196 | fn the_other_method(&self) {} | ||
197 | } | ||
198 | fn foo(a: A<u32>) { a.<|> } | ||
199 | "#, | ||
200 | expect![[r#" | ||
201 | me the_method() fn the_method(&self) | ||
202 | "#]], | ||
203 | ) | ||
204 | } | ||
205 | |||
206 | #[test] | ||
207 | fn test_trait_method_completion() { | ||
208 | check( | ||
209 | r#" | ||
210 | struct A {} | ||
211 | trait Trait { fn the_method(&self); } | ||
212 | impl Trait for A {} | ||
213 | fn foo(a: A) { a.<|> } | ||
214 | "#, | ||
215 | expect![[r#" | ||
216 | me the_method() fn the_method(&self) | ||
217 | "#]], | ||
218 | ); | ||
219 | } | ||
220 | |||
221 | #[test] | ||
222 | fn test_trait_method_completion_deduplicated() { | ||
223 | check( | ||
224 | r" | ||
225 | struct A {} | ||
226 | trait Trait { fn the_method(&self); } | ||
227 | impl<T> Trait for T {} | ||
228 | fn foo(a: &A) { a.<|> } | ||
229 | ", | ||
230 | expect![[r#" | ||
231 | me the_method() fn the_method(&self) | ||
232 | "#]], | ||
233 | ); | ||
234 | } | ||
235 | |||
236 | #[test] | ||
237 | fn completes_trait_method_from_other_module() { | ||
238 | check( | ||
239 | r" | ||
240 | struct A {} | ||
241 | mod m { | ||
242 | pub trait Trait { fn the_method(&self); } | ||
243 | } | ||
244 | use m::Trait; | ||
245 | impl Trait for A {} | ||
246 | fn foo(a: A) { a.<|> } | ||
247 | ", | ||
248 | expect![[r#" | ||
249 | me the_method() fn the_method(&self) | ||
250 | "#]], | ||
251 | ); | ||
252 | } | ||
253 | |||
254 | #[test] | ||
255 | fn test_no_non_self_method() { | ||
256 | check( | ||
257 | r#" | ||
258 | struct A {} | ||
259 | impl A { | ||
260 | fn the_method() {} | ||
261 | } | ||
262 | fn foo(a: A) { | ||
263 | a.<|> | ||
264 | } | ||
265 | "#, | ||
266 | expect![[""]], | ||
267 | ); | ||
268 | } | ||
269 | |||
270 | #[test] | ||
271 | fn test_tuple_field_completion() { | ||
272 | check( | ||
273 | r#" | ||
274 | fn foo() { | ||
275 | let b = (0, 3.14); | ||
276 | b.<|> | ||
277 | } | ||
278 | "#, | ||
279 | expect![[r#" | ||
280 | fd 0 i32 | ||
281 | fd 1 f64 | ||
282 | "#]], | ||
283 | ) | ||
284 | } | ||
285 | |||
286 | #[test] | ||
287 | fn test_tuple_field_inference() { | ||
288 | check( | ||
289 | r#" | ||
290 | pub struct S; | ||
291 | impl S { pub fn blah(&self) {} } | ||
292 | |||
293 | struct T(S); | ||
294 | |||
295 | impl T { | ||
296 | fn foo(&self) { | ||
297 | // FIXME: This doesn't work without the trailing `a` as `0.` is a float | ||
298 | self.0.a<|> | ||
299 | } | ||
300 | } | ||
301 | "#, | ||
302 | expect![[r#" | ||
303 | me blah() pub fn blah(&self) | ||
304 | "#]], | ||
305 | ); | ||
306 | } | ||
307 | |||
308 | #[test] | ||
309 | fn test_completion_works_in_consts() { | ||
310 | check( | ||
311 | r#" | ||
312 | struct A { the_field: u32 } | ||
313 | const X: u32 = { | ||
314 | A { the_field: 92 }.<|> | ||
315 | }; | ||
316 | "#, | ||
317 | expect![[r#" | ||
318 | fd the_field u32 | ||
319 | "#]], | ||
320 | ); | ||
321 | } | ||
322 | |||
323 | #[test] | ||
324 | fn works_in_simple_macro_1() { | ||
325 | check( | ||
326 | r#" | ||
327 | macro_rules! m { ($e:expr) => { $e } } | ||
328 | struct A { the_field: u32 } | ||
329 | fn foo(a: A) { | ||
330 | m!(a.x<|>) | ||
331 | } | ||
332 | "#, | ||
333 | expect![[r#" | ||
334 | fd the_field u32 | ||
335 | "#]], | ||
336 | ); | ||
337 | } | ||
338 | |||
339 | #[test] | ||
340 | fn works_in_simple_macro_2() { | ||
341 | // this doesn't work yet because the macro doesn't expand without the token -- maybe it can be fixed with better recovery | ||
342 | check( | ||
343 | r#" | ||
344 | macro_rules! m { ($e:expr) => { $e } } | ||
345 | struct A { the_field: u32 } | ||
346 | fn foo(a: A) { | ||
347 | m!(a.<|>) | ||
348 | } | ||
349 | "#, | ||
350 | expect![[r#" | ||
351 | fd the_field u32 | ||
352 | "#]], | ||
353 | ); | ||
354 | } | ||
355 | |||
356 | #[test] | ||
357 | fn works_in_simple_macro_recursive_1() { | ||
358 | check( | ||
359 | r#" | ||
360 | macro_rules! m { ($e:expr) => { $e } } | ||
361 | struct A { the_field: u32 } | ||
362 | fn foo(a: A) { | ||
363 | m!(m!(m!(a.x<|>))) | ||
364 | } | ||
365 | "#, | ||
366 | expect![[r#" | ||
367 | fd the_field u32 | ||
368 | "#]], | ||
369 | ); | ||
370 | } | ||
371 | |||
372 | #[test] | ||
373 | fn macro_expansion_resilient() { | ||
374 | check( | ||
375 | r#" | ||
376 | macro_rules! dbg { | ||
377 | () => {}; | ||
378 | ($val:expr) => { | ||
379 | match $val { tmp => { tmp } } | ||
380 | }; | ||
381 | // Trailing comma with single argument is ignored | ||
382 | ($val:expr,) => { $crate::dbg!($val) }; | ||
383 | ($($val:expr),+ $(,)?) => { | ||
384 | ($($crate::dbg!($val)),+,) | ||
385 | }; | ||
386 | } | ||
387 | struct A { the_field: u32 } | ||
388 | fn foo(a: A) { | ||
389 | dbg!(a.<|>) | ||
390 | } | ||
391 | "#, | ||
392 | expect![[r#" | ||
393 | fd the_field u32 | ||
394 | "#]], | ||
395 | ); | ||
396 | } | ||
397 | |||
398 | #[test] | ||
399 | fn test_method_completion_issue_3547() { | ||
400 | check( | ||
401 | r#" | ||
402 | struct HashSet<T> {} | ||
403 | impl<T> HashSet<T> { | ||
404 | pub fn the_method(&self) {} | ||
405 | } | ||
406 | fn foo() { | ||
407 | let s: HashSet<_>; | ||
408 | s.<|> | ||
409 | } | ||
410 | "#, | ||
411 | expect![[r#" | ||
412 | me the_method() pub fn the_method(&self) | ||
413 | "#]], | ||
414 | ); | ||
415 | } | ||
416 | } | ||
diff --git a/crates/ide/src/completion/complete_fn_param.rs b/crates/ide/src/completion/complete_fn_param.rs new file mode 100644 index 000000000..7c63ce58f --- /dev/null +++ b/crates/ide/src/completion/complete_fn_param.rs | |||
@@ -0,0 +1,135 @@ | |||
1 | //! See `complete_fn_param`. | ||
2 | |||
3 | use rustc_hash::FxHashMap; | ||
4 | use syntax::{ | ||
5 | ast::{self, ModuleItemOwner}, | ||
6 | match_ast, AstNode, | ||
7 | }; | ||
8 | |||
9 | use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions}; | ||
10 | |||
11 | /// Complete repeated parameters, both name and type. For example, if all | ||
12 | /// functions in a file have a `spam: &mut Spam` parameter, a completion with | ||
13 | /// `spam: &mut Spam` insert text/label and `spam` lookup string will be | ||
14 | /// suggested. | ||
15 | pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) { | ||
16 | if !ctx.is_param { | ||
17 | return; | ||
18 | } | ||
19 | |||
20 | let mut params = FxHashMap::default(); | ||
21 | |||
22 | let me = ctx.token.ancestors().find_map(ast::Fn::cast); | ||
23 | let mut process_fn = |func: ast::Fn| { | ||
24 | if Some(&func) == me.as_ref() { | ||
25 | return; | ||
26 | } | ||
27 | func.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| { | ||
28 | let text = param.syntax().text().to_string(); | ||
29 | params.entry(text).or_insert(param); | ||
30 | }) | ||
31 | }; | ||
32 | |||
33 | for node in ctx.token.parent().ancestors() { | ||
34 | match_ast! { | ||
35 | match node { | ||
36 | ast::SourceFile(it) => it.items().filter_map(|item| match item { | ||
37 | ast::Item::Fn(it) => Some(it), | ||
38 | _ => None, | ||
39 | }).for_each(&mut process_fn), | ||
40 | ast::ItemList(it) => it.items().filter_map(|item| match item { | ||
41 | ast::Item::Fn(it) => Some(it), | ||
42 | _ => None, | ||
43 | }).for_each(&mut process_fn), | ||
44 | ast::AssocItemList(it) => it.assoc_items().filter_map(|item| match item { | ||
45 | ast::AssocItem::Fn(it) => Some(it), | ||
46 | _ => None, | ||
47 | }).for_each(&mut process_fn), | ||
48 | _ => continue, | ||
49 | } | ||
50 | }; | ||
51 | } | ||
52 | |||
53 | params | ||
54 | .into_iter() | ||
55 | .filter_map(|(label, param)| { | ||
56 | let lookup = param.pat()?.syntax().text().to_string(); | ||
57 | Some((label, lookup)) | ||
58 | }) | ||
59 | .for_each(|(label, lookup)| { | ||
60 | CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label) | ||
61 | .kind(crate::CompletionItemKind::Binding) | ||
62 | .lookup_by(lookup) | ||
63 | .add_to(acc) | ||
64 | }); | ||
65 | } | ||
66 | |||
67 | #[cfg(test)] | ||
68 | mod tests { | ||
69 | use expect::{expect, Expect}; | ||
70 | |||
71 | use crate::completion::{test_utils::completion_list, CompletionKind}; | ||
72 | |||
73 | fn check(ra_fixture: &str, expect: Expect) { | ||
74 | let actual = completion_list(ra_fixture, CompletionKind::Magic); | ||
75 | expect.assert_eq(&actual); | ||
76 | } | ||
77 | |||
78 | #[test] | ||
79 | fn test_param_completion_last_param() { | ||
80 | check( | ||
81 | r#" | ||
82 | fn foo(file_id: FileId) {} | ||
83 | fn bar(file_id: FileId) {} | ||
84 | fn baz(file<|>) {} | ||
85 | "#, | ||
86 | expect![[r#" | ||
87 | bn file_id: FileId | ||
88 | "#]], | ||
89 | ); | ||
90 | } | ||
91 | |||
92 | #[test] | ||
93 | fn test_param_completion_nth_param() { | ||
94 | check( | ||
95 | r#" | ||
96 | fn foo(file_id: FileId) {} | ||
97 | fn baz(file<|>, x: i32) {} | ||
98 | "#, | ||
99 | expect![[r#" | ||
100 | bn file_id: FileId | ||
101 | "#]], | ||
102 | ); | ||
103 | } | ||
104 | |||
105 | #[test] | ||
106 | fn test_param_completion_trait_param() { | ||
107 | check( | ||
108 | r#" | ||
109 | pub(crate) trait SourceRoot { | ||
110 | pub fn contains(&self, file_id: FileId) -> bool; | ||
111 | pub fn module_map(&self) -> &ModuleMap; | ||
112 | pub fn lines(&self, file_id: FileId) -> &LineIndex; | ||
113 | pub fn syntax(&self, file<|>) | ||
114 | } | ||
115 | "#, | ||
116 | expect![[r#" | ||
117 | bn file_id: FileId | ||
118 | "#]], | ||
119 | ); | ||
120 | } | ||
121 | |||
122 | #[test] | ||
123 | fn completes_param_in_inner_function() { | ||
124 | check( | ||
125 | r#" | ||
126 | fn outer(text: String) { | ||
127 | fn inner(<|>) | ||
128 | } | ||
129 | "#, | ||
130 | expect![[r#" | ||
131 | bn text: String | ||
132 | "#]], | ||
133 | ) | ||
134 | } | ||
135 | } | ||
diff --git a/crates/ide/src/completion/complete_keyword.rs b/crates/ide/src/completion/complete_keyword.rs new file mode 100644 index 000000000..22ada3cf2 --- /dev/null +++ b/crates/ide/src/completion/complete_keyword.rs | |||
@@ -0,0 +1,527 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use syntax::{ast, SyntaxKind}; | ||
4 | use test_utils::mark; | ||
5 | |||
6 | use crate::completion::{ | ||
7 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, | ||
8 | }; | ||
9 | |||
10 | pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { | ||
11 | // complete keyword "crate" in use stmt | ||
12 | let source_range = ctx.source_range(); | ||
13 | |||
14 | if ctx.use_item_syntax.is_some() { | ||
15 | if ctx.path_qual.is_none() { | ||
16 | CompletionItem::new(CompletionKind::Keyword, source_range, "crate::") | ||
17 | .kind(CompletionItemKind::Keyword) | ||
18 | .insert_text("crate::") | ||
19 | .add_to(acc); | ||
20 | } | ||
21 | CompletionItem::new(CompletionKind::Keyword, source_range, "self") | ||
22 | .kind(CompletionItemKind::Keyword) | ||
23 | .add_to(acc); | ||
24 | CompletionItem::new(CompletionKind::Keyword, source_range, "super::") | ||
25 | .kind(CompletionItemKind::Keyword) | ||
26 | .insert_text("super::") | ||
27 | .add_to(acc); | ||
28 | } | ||
29 | |||
30 | // Suggest .await syntax for types that implement Future trait | ||
31 | if let Some(receiver) = &ctx.dot_receiver { | ||
32 | if let Some(ty) = ctx.sema.type_of_expr(receiver) { | ||
33 | if ty.impls_future(ctx.db) { | ||
34 | CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await") | ||
35 | .kind(CompletionItemKind::Keyword) | ||
36 | .detail("expr.await") | ||
37 | .insert_text("await") | ||
38 | .add_to(acc); | ||
39 | } | ||
40 | }; | ||
41 | } | ||
42 | } | ||
43 | |||
44 | pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { | ||
45 | if ctx.token.kind() == SyntaxKind::COMMENT { | ||
46 | mark::hit!(no_keyword_completion_in_comments); | ||
47 | return; | ||
48 | } | ||
49 | |||
50 | let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent; | ||
51 | if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling { | ||
52 | add_keyword(ctx, acc, "where", "where "); | ||
53 | return; | ||
54 | } | ||
55 | if ctx.unsafe_is_prev { | ||
56 | if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent { | ||
57 | add_keyword(ctx, acc, "fn", "fn $0() {}") | ||
58 | } | ||
59 | |||
60 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
61 | add_keyword(ctx, acc, "trait", "trait $0 {}"); | ||
62 | add_keyword(ctx, acc, "impl", "impl $0 {}"); | ||
63 | } | ||
64 | |||
65 | return; | ||
66 | } | ||
67 | if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent | ||
68 | { | ||
69 | add_keyword(ctx, acc, "fn", "fn $0() {}"); | ||
70 | } | ||
71 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
72 | add_keyword(ctx, acc, "use", "use "); | ||
73 | add_keyword(ctx, acc, "impl", "impl $0 {}"); | ||
74 | add_keyword(ctx, acc, "trait", "trait $0 {}"); | ||
75 | } | ||
76 | |||
77 | if ctx.has_item_list_or_source_file_parent { | ||
78 | add_keyword(ctx, acc, "enum", "enum $0 {}"); | ||
79 | add_keyword(ctx, acc, "struct", "struct $0"); | ||
80 | add_keyword(ctx, acc, "union", "union $0 {}"); | ||
81 | } | ||
82 | |||
83 | if ctx.is_expr { | ||
84 | add_keyword(ctx, acc, "match", "match $0 {}"); | ||
85 | add_keyword(ctx, acc, "while", "while $0 {}"); | ||
86 | add_keyword(ctx, acc, "loop", "loop {$0}"); | ||
87 | add_keyword(ctx, acc, "if", "if "); | ||
88 | add_keyword(ctx, acc, "if let", "if let "); | ||
89 | } | ||
90 | |||
91 | if ctx.if_is_prev || ctx.block_expr_parent { | ||
92 | add_keyword(ctx, acc, "let", "let "); | ||
93 | } | ||
94 | |||
95 | if ctx.after_if { | ||
96 | add_keyword(ctx, acc, "else", "else {$0}"); | ||
97 | add_keyword(ctx, acc, "else if", "else if $0 {}"); | ||
98 | } | ||
99 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
100 | add_keyword(ctx, acc, "mod", "mod $0 {}"); | ||
101 | } | ||
102 | if ctx.bind_pat_parent || ctx.ref_pat_parent { | ||
103 | add_keyword(ctx, acc, "mut", "mut "); | ||
104 | } | ||
105 | if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent | ||
106 | { | ||
107 | add_keyword(ctx, acc, "const", "const "); | ||
108 | add_keyword(ctx, acc, "type", "type "); | ||
109 | } | ||
110 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
111 | add_keyword(ctx, acc, "static", "static "); | ||
112 | }; | ||
113 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
114 | add_keyword(ctx, acc, "extern", "extern "); | ||
115 | } | ||
116 | if ctx.has_item_list_or_source_file_parent | ||
117 | || has_trait_or_impl_parent | ||
118 | || ctx.block_expr_parent | ||
119 | || ctx.is_match_arm | ||
120 | { | ||
121 | add_keyword(ctx, acc, "unsafe", "unsafe "); | ||
122 | } | ||
123 | if ctx.in_loop_body { | ||
124 | if ctx.can_be_stmt { | ||
125 | add_keyword(ctx, acc, "continue", "continue;"); | ||
126 | add_keyword(ctx, acc, "break", "break;"); | ||
127 | } else { | ||
128 | add_keyword(ctx, acc, "continue", "continue"); | ||
129 | add_keyword(ctx, acc, "break", "break"); | ||
130 | } | ||
131 | } | ||
132 | if ctx.has_item_list_or_source_file_parent || ctx.has_impl_parent { | ||
133 | add_keyword(ctx, acc, "pub", "pub ") | ||
134 | } | ||
135 | |||
136 | if !ctx.is_trivial_path { | ||
137 | return; | ||
138 | } | ||
139 | let fn_def = match &ctx.function_syntax { | ||
140 | Some(it) => it, | ||
141 | None => return, | ||
142 | }; | ||
143 | acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt)); | ||
144 | } | ||
145 | |||
146 | fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { | ||
147 | let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw) | ||
148 | .kind(CompletionItemKind::Keyword); | ||
149 | |||
150 | match ctx.config.snippet_cap { | ||
151 | Some(cap) => res.insert_snippet(cap, snippet), | ||
152 | _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }), | ||
153 | } | ||
154 | .build() | ||
155 | } | ||
156 | |||
157 | fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) { | ||
158 | acc.add(keyword(ctx, kw, snippet)); | ||
159 | } | ||
160 | |||
161 | fn complete_return( | ||
162 | ctx: &CompletionContext, | ||
163 | fn_def: &ast::Fn, | ||
164 | can_be_stmt: bool, | ||
165 | ) -> Option<CompletionItem> { | ||
166 | let snip = match (can_be_stmt, fn_def.ret_type().is_some()) { | ||
167 | (true, true) => "return $0;", | ||
168 | (true, false) => "return;", | ||
169 | (false, true) => "return $0", | ||
170 | (false, false) => "return", | ||
171 | }; | ||
172 | Some(keyword(ctx, "return", snip)) | ||
173 | } | ||
174 | |||
175 | #[cfg(test)] | ||
176 | mod tests { | ||
177 | use expect::{expect, Expect}; | ||
178 | |||
179 | use crate::completion::{ | ||
180 | test_utils::{check_edit, completion_list}, | ||
181 | CompletionKind, | ||
182 | }; | ||
183 | use test_utils::mark; | ||
184 | |||
185 | fn check(ra_fixture: &str, expect: Expect) { | ||
186 | let actual = completion_list(ra_fixture, CompletionKind::Keyword); | ||
187 | expect.assert_eq(&actual) | ||
188 | } | ||
189 | |||
190 | #[test] | ||
191 | fn test_keywords_in_use_stmt() { | ||
192 | check( | ||
193 | r"use <|>", | ||
194 | expect![[r#" | ||
195 | kw crate:: | ||
196 | kw self | ||
197 | kw super:: | ||
198 | "#]], | ||
199 | ); | ||
200 | |||
201 | check( | ||
202 | r"use a::<|>", | ||
203 | expect![[r#" | ||
204 | kw self | ||
205 | kw super:: | ||
206 | "#]], | ||
207 | ); | ||
208 | |||
209 | check( | ||
210 | r"use a::{b, <|>}", | ||
211 | expect![[r#" | ||
212 | kw self | ||
213 | kw super:: | ||
214 | "#]], | ||
215 | ); | ||
216 | } | ||
217 | |||
218 | #[test] | ||
219 | fn test_keywords_at_source_file_level() { | ||
220 | check( | ||
221 | r"m<|>", | ||
222 | expect![[r#" | ||
223 | kw const | ||
224 | kw enum | ||
225 | kw extern | ||
226 | kw fn | ||
227 | kw impl | ||
228 | kw mod | ||
229 | kw pub | ||
230 | kw static | ||
231 | kw struct | ||
232 | kw trait | ||
233 | kw type | ||
234 | kw union | ||
235 | kw unsafe | ||
236 | kw use | ||
237 | "#]], | ||
238 | ); | ||
239 | } | ||
240 | |||
241 | #[test] | ||
242 | fn test_keywords_in_function() { | ||
243 | check( | ||
244 | r"fn quux() { <|> }", | ||
245 | expect![[r#" | ||
246 | kw const | ||
247 | kw extern | ||
248 | kw fn | ||
249 | kw if | ||
250 | kw if let | ||
251 | kw impl | ||
252 | kw let | ||
253 | kw loop | ||
254 | kw match | ||
255 | kw mod | ||
256 | kw return | ||
257 | kw static | ||
258 | kw trait | ||
259 | kw type | ||
260 | kw unsafe | ||
261 | kw use | ||
262 | kw while | ||
263 | "#]], | ||
264 | ); | ||
265 | } | ||
266 | |||
267 | #[test] | ||
268 | fn test_keywords_inside_block() { | ||
269 | check( | ||
270 | r"fn quux() { if true { <|> } }", | ||
271 | expect![[r#" | ||
272 | kw const | ||
273 | kw extern | ||
274 | kw fn | ||
275 | kw if | ||
276 | kw if let | ||
277 | kw impl | ||
278 | kw let | ||
279 | kw loop | ||
280 | kw match | ||
281 | kw mod | ||
282 | kw return | ||
283 | kw static | ||
284 | kw trait | ||
285 | kw type | ||
286 | kw unsafe | ||
287 | kw use | ||
288 | kw while | ||
289 | "#]], | ||
290 | ); | ||
291 | } | ||
292 | |||
293 | #[test] | ||
294 | fn test_keywords_after_if() { | ||
295 | check( | ||
296 | r#"fn quux() { if true { () } <|> }"#, | ||
297 | expect![[r#" | ||
298 | kw const | ||
299 | kw else | ||
300 | kw else if | ||
301 | kw extern | ||
302 | kw fn | ||
303 | kw if | ||
304 | kw if let | ||
305 | kw impl | ||
306 | kw let | ||
307 | kw loop | ||
308 | kw match | ||
309 | kw mod | ||
310 | kw return | ||
311 | kw static | ||
312 | kw trait | ||
313 | kw type | ||
314 | kw unsafe | ||
315 | kw use | ||
316 | kw while | ||
317 | "#]], | ||
318 | ); | ||
319 | check_edit( | ||
320 | "else", | ||
321 | r#"fn quux() { if true { () } <|> }"#, | ||
322 | r#"fn quux() { if true { () } else {$0} }"#, | ||
323 | ); | ||
324 | } | ||
325 | |||
326 | #[test] | ||
327 | fn test_keywords_in_match_arm() { | ||
328 | check( | ||
329 | r#" | ||
330 | fn quux() -> i32 { | ||
331 | match () { () => <|> } | ||
332 | } | ||
333 | "#, | ||
334 | expect![[r#" | ||
335 | kw if | ||
336 | kw if let | ||
337 | kw loop | ||
338 | kw match | ||
339 | kw return | ||
340 | kw unsafe | ||
341 | kw while | ||
342 | "#]], | ||
343 | ); | ||
344 | } | ||
345 | |||
346 | #[test] | ||
347 | fn test_keywords_in_trait_def() { | ||
348 | check( | ||
349 | r"trait My { <|> }", | ||
350 | expect![[r#" | ||
351 | kw const | ||
352 | kw fn | ||
353 | kw type | ||
354 | kw unsafe | ||
355 | "#]], | ||
356 | ); | ||
357 | } | ||
358 | |||
359 | #[test] | ||
360 | fn test_keywords_in_impl_def() { | ||
361 | check( | ||
362 | r"impl My { <|> }", | ||
363 | expect![[r#" | ||
364 | kw const | ||
365 | kw fn | ||
366 | kw pub | ||
367 | kw type | ||
368 | kw unsafe | ||
369 | "#]], | ||
370 | ); | ||
371 | } | ||
372 | |||
373 | #[test] | ||
374 | fn test_keywords_in_loop() { | ||
375 | check( | ||
376 | r"fn my() { loop { <|> } }", | ||
377 | expect![[r#" | ||
378 | kw break | ||
379 | kw const | ||
380 | kw continue | ||
381 | kw extern | ||
382 | kw fn | ||
383 | kw if | ||
384 | kw if let | ||
385 | kw impl | ||
386 | kw let | ||
387 | kw loop | ||
388 | kw match | ||
389 | kw mod | ||
390 | kw return | ||
391 | kw static | ||
392 | kw trait | ||
393 | kw type | ||
394 | kw unsafe | ||
395 | kw use | ||
396 | kw while | ||
397 | "#]], | ||
398 | ); | ||
399 | } | ||
400 | |||
401 | #[test] | ||
402 | fn test_keywords_after_unsafe_in_item_list() { | ||
403 | check( | ||
404 | r"unsafe <|>", | ||
405 | expect![[r#" | ||
406 | kw fn | ||
407 | kw impl | ||
408 | kw trait | ||
409 | "#]], | ||
410 | ); | ||
411 | } | ||
412 | |||
413 | #[test] | ||
414 | fn test_keywords_after_unsafe_in_block_expr() { | ||
415 | check( | ||
416 | r"fn my_fn() { unsafe <|> }", | ||
417 | expect![[r#" | ||
418 | kw fn | ||
419 | kw impl | ||
420 | kw trait | ||
421 | "#]], | ||
422 | ); | ||
423 | } | ||
424 | |||
425 | #[test] | ||
426 | fn test_mut_in_ref_and_in_fn_parameters_list() { | ||
427 | check( | ||
428 | r"fn my_fn(&<|>) {}", | ||
429 | expect![[r#" | ||
430 | kw mut | ||
431 | "#]], | ||
432 | ); | ||
433 | check( | ||
434 | r"fn my_fn(<|>) {}", | ||
435 | expect![[r#" | ||
436 | kw mut | ||
437 | "#]], | ||
438 | ); | ||
439 | check( | ||
440 | r"fn my_fn() { let &<|> }", | ||
441 | expect![[r#" | ||
442 | kw mut | ||
443 | "#]], | ||
444 | ); | ||
445 | } | ||
446 | |||
447 | #[test] | ||
448 | fn test_where_keyword() { | ||
449 | check( | ||
450 | r"trait A <|>", | ||
451 | expect![[r#" | ||
452 | kw where | ||
453 | "#]], | ||
454 | ); | ||
455 | check( | ||
456 | r"impl A <|>", | ||
457 | expect![[r#" | ||
458 | kw where | ||
459 | "#]], | ||
460 | ); | ||
461 | } | ||
462 | |||
463 | #[test] | ||
464 | fn no_keyword_completion_in_comments() { | ||
465 | mark::check!(no_keyword_completion_in_comments); | ||
466 | check( | ||
467 | r#" | ||
468 | fn test() { | ||
469 | let x = 2; // A comment<|> | ||
470 | } | ||
471 | "#, | ||
472 | expect![[""]], | ||
473 | ); | ||
474 | check( | ||
475 | r#" | ||
476 | /* | ||
477 | Some multi-line comment<|> | ||
478 | */ | ||
479 | "#, | ||
480 | expect![[""]], | ||
481 | ); | ||
482 | check( | ||
483 | r#" | ||
484 | /// Some doc comment | ||
485 | /// let test<|> = 1 | ||
486 | "#, | ||
487 | expect![[""]], | ||
488 | ); | ||
489 | } | ||
490 | |||
491 | #[test] | ||
492 | fn test_completion_await_impls_future() { | ||
493 | check( | ||
494 | r#" | ||
495 | //- /main.rs | ||
496 | use std::future::*; | ||
497 | struct A {} | ||
498 | impl Future for A {} | ||
499 | fn foo(a: A) { a.<|> } | ||
500 | |||
501 | //- /std/lib.rs | ||
502 | pub mod future { | ||
503 | #[lang = "future_trait"] | ||
504 | pub trait Future {} | ||
505 | } | ||
506 | "#, | ||
507 | expect![[r#" | ||
508 | kw await expr.await | ||
509 | "#]], | ||
510 | ) | ||
511 | } | ||
512 | |||
513 | #[test] | ||
514 | fn after_let() { | ||
515 | check( | ||
516 | r#"fn main() { let _ = <|> }"#, | ||
517 | expect![[r#" | ||
518 | kw if | ||
519 | kw if let | ||
520 | kw loop | ||
521 | kw match | ||
522 | kw return | ||
523 | kw while | ||
524 | "#]], | ||
525 | ) | ||
526 | } | ||
527 | } | ||
diff --git a/crates/ide/src/completion/complete_macro_in_item_position.rs b/crates/ide/src/completion/complete_macro_in_item_position.rs new file mode 100644 index 000000000..0447f0511 --- /dev/null +++ b/crates/ide/src/completion/complete_macro_in_item_position.rs | |||
@@ -0,0 +1,41 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::completion::{CompletionContext, Completions}; | ||
4 | |||
5 | pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) { | ||
6 | // Show only macros in top level. | ||
7 | if ctx.is_new_item { | ||
8 | ctx.scope.process_all_names(&mut |name, res| { | ||
9 | if let hir::ScopeDef::MacroDef(mac) = res { | ||
10 | acc.add_macro(ctx, Some(name.to_string()), mac); | ||
11 | } | ||
12 | }) | ||
13 | } | ||
14 | } | ||
15 | |||
16 | #[cfg(test)] | ||
17 | mod tests { | ||
18 | use expect::{expect, Expect}; | ||
19 | |||
20 | use crate::completion::{test_utils::completion_list, CompletionKind}; | ||
21 | |||
22 | fn check(ra_fixture: &str, expect: Expect) { | ||
23 | let actual = completion_list(ra_fixture, CompletionKind::Reference); | ||
24 | expect.assert_eq(&actual) | ||
25 | } | ||
26 | |||
27 | #[test] | ||
28 | fn completes_macros_as_item() { | ||
29 | check( | ||
30 | r#" | ||
31 | macro_rules! foo { () => {} } | ||
32 | fn foo() {} | ||
33 | |||
34 | <|> | ||
35 | "#, | ||
36 | expect![[r#" | ||
37 | ma foo!(…) macro_rules! foo | ||
38 | "#]], | ||
39 | ) | ||
40 | } | ||
41 | } | ||
diff --git a/crates/ide/src/completion/complete_pattern.rs b/crates/ide/src/completion/complete_pattern.rs new file mode 100644 index 000000000..aceb77cb5 --- /dev/null +++ b/crates/ide/src/completion/complete_pattern.rs | |||
@@ -0,0 +1,88 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::completion::{CompletionContext, Completions}; | ||
4 | |||
5 | /// Completes constats and paths in patterns. | ||
6 | pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { | ||
7 | if !ctx.is_pat_binding_or_const { | ||
8 | return; | ||
9 | } | ||
10 | if ctx.record_pat_syntax.is_some() { | ||
11 | return; | ||
12 | } | ||
13 | |||
14 | // FIXME: ideally, we should look at the type we are matching against and | ||
15 | // suggest variants + auto-imports | ||
16 | ctx.scope.process_all_names(&mut |name, res| { | ||
17 | match &res { | ||
18 | hir::ScopeDef::ModuleDef(def) => match def { | ||
19 | hir::ModuleDef::Adt(hir::Adt::Enum(..)) | ||
20 | | hir::ModuleDef::Adt(hir::Adt::Struct(..)) | ||
21 | | hir::ModuleDef::EnumVariant(..) | ||
22 | | hir::ModuleDef::Const(..) | ||
23 | | hir::ModuleDef::Module(..) => (), | ||
24 | _ => return, | ||
25 | }, | ||
26 | hir::ScopeDef::MacroDef(_) => (), | ||
27 | _ => return, | ||
28 | }; | ||
29 | |||
30 | acc.add_resolution(ctx, name.to_string(), &res) | ||
31 | }); | ||
32 | } | ||
33 | |||
34 | #[cfg(test)] | ||
35 | mod tests { | ||
36 | use expect::{expect, Expect}; | ||
37 | |||
38 | use crate::completion::{test_utils::completion_list, CompletionKind}; | ||
39 | |||
40 | fn check(ra_fixture: &str, expect: Expect) { | ||
41 | let actual = completion_list(ra_fixture, CompletionKind::Reference); | ||
42 | expect.assert_eq(&actual) | ||
43 | } | ||
44 | |||
45 | #[test] | ||
46 | fn completes_enum_variants_and_modules() { | ||
47 | check( | ||
48 | r#" | ||
49 | enum E { X } | ||
50 | use self::E::X; | ||
51 | const Z: E = E::X; | ||
52 | mod m {} | ||
53 | |||
54 | static FOO: E = E::X; | ||
55 | struct Bar { f: u32 } | ||
56 | |||
57 | fn foo() { | ||
58 | match E::X { <|> } | ||
59 | } | ||
60 | "#, | ||
61 | expect![[r#" | ||
62 | st Bar | ||
63 | en E | ||
64 | ev X () | ||
65 | ct Z | ||
66 | md m | ||
67 | "#]], | ||
68 | ); | ||
69 | } | ||
70 | |||
71 | #[test] | ||
72 | fn completes_in_simple_macro_call() { | ||
73 | check( | ||
74 | r#" | ||
75 | macro_rules! m { ($e:expr) => { $e } } | ||
76 | enum E { X } | ||
77 | |||
78 | fn foo() { | ||
79 | m!(match E::X { <|> }) | ||
80 | } | ||
81 | "#, | ||
82 | expect![[r#" | ||
83 | en E | ||
84 | ma m!(…) macro_rules! m | ||
85 | "#]], | ||
86 | ); | ||
87 | } | ||
88 | } | ||
diff --git a/crates/ide/src/completion/complete_postfix.rs b/crates/ide/src/completion/complete_postfix.rs new file mode 100644 index 000000000..d50b13c52 --- /dev/null +++ b/crates/ide/src/completion/complete_postfix.rs | |||
@@ -0,0 +1,378 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | use assists::utils::TryEnum; | ||
3 | use syntax::{ | ||
4 | ast::{self, AstNode}, | ||
5 | TextRange, TextSize, | ||
6 | }; | ||
7 | use text_edit::TextEdit; | ||
8 | |||
9 | use crate::{ | ||
10 | completion::{ | ||
11 | completion_config::SnippetCap, | ||
12 | completion_context::CompletionContext, | ||
13 | completion_item::{Builder, CompletionKind, Completions}, | ||
14 | }, | ||
15 | CompletionItem, CompletionItemKind, | ||
16 | }; | ||
17 | |||
18 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | ||
19 | if !ctx.config.enable_postfix_completions { | ||
20 | return; | ||
21 | } | ||
22 | |||
23 | let dot_receiver = match &ctx.dot_receiver { | ||
24 | Some(it) => it, | ||
25 | None => return, | ||
26 | }; | ||
27 | |||
28 | let receiver_text = | ||
29 | get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); | ||
30 | |||
31 | let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { | ||
32 | Some(it) => it, | ||
33 | None => return, | ||
34 | }; | ||
35 | |||
36 | let cap = match ctx.config.snippet_cap { | ||
37 | Some(it) => it, | ||
38 | None => return, | ||
39 | }; | ||
40 | let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty); | ||
41 | if let Some(try_enum) = &try_enum { | ||
42 | match try_enum { | ||
43 | TryEnum::Result => { | ||
44 | postfix_snippet( | ||
45 | ctx, | ||
46 | cap, | ||
47 | &dot_receiver, | ||
48 | "ifl", | ||
49 | "if let Ok {}", | ||
50 | &format!("if let Ok($1) = {} {{\n $0\n}}", receiver_text), | ||
51 | ) | ||
52 | .add_to(acc); | ||
53 | |||
54 | postfix_snippet( | ||
55 | ctx, | ||
56 | cap, | ||
57 | &dot_receiver, | ||
58 | "while", | ||
59 | "while let Ok {}", | ||
60 | &format!("while let Ok($1) = {} {{\n $0\n}}", receiver_text), | ||
61 | ) | ||
62 | .add_to(acc); | ||
63 | } | ||
64 | TryEnum::Option => { | ||
65 | postfix_snippet( | ||
66 | ctx, | ||
67 | cap, | ||
68 | &dot_receiver, | ||
69 | "ifl", | ||
70 | "if let Some {}", | ||
71 | &format!("if let Some($1) = {} {{\n $0\n}}", receiver_text), | ||
72 | ) | ||
73 | .add_to(acc); | ||
74 | |||
75 | postfix_snippet( | ||
76 | ctx, | ||
77 | cap, | ||
78 | &dot_receiver, | ||
79 | "while", | ||
80 | "while let Some {}", | ||
81 | &format!("while let Some($1) = {} {{\n $0\n}}", receiver_text), | ||
82 | ) | ||
83 | .add_to(acc); | ||
84 | } | ||
85 | } | ||
86 | } else if receiver_ty.is_bool() || receiver_ty.is_unknown() { | ||
87 | postfix_snippet( | ||
88 | ctx, | ||
89 | cap, | ||
90 | &dot_receiver, | ||
91 | "if", | ||
92 | "if expr {}", | ||
93 | &format!("if {} {{\n $0\n}}", receiver_text), | ||
94 | ) | ||
95 | .add_to(acc); | ||
96 | postfix_snippet( | ||
97 | ctx, | ||
98 | cap, | ||
99 | &dot_receiver, | ||
100 | "while", | ||
101 | "while expr {}", | ||
102 | &format!("while {} {{\n $0\n}}", receiver_text), | ||
103 | ) | ||
104 | .add_to(acc); | ||
105 | postfix_snippet(ctx, cap, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)) | ||
106 | .add_to(acc); | ||
107 | } | ||
108 | |||
109 | postfix_snippet(ctx, cap, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text)) | ||
110 | .add_to(acc); | ||
111 | postfix_snippet( | ||
112 | ctx, | ||
113 | cap, | ||
114 | &dot_receiver, | ||
115 | "refm", | ||
116 | "&mut expr", | ||
117 | &format!("&mut {}", receiver_text), | ||
118 | ) | ||
119 | .add_to(acc); | ||
120 | |||
121 | // The rest of the postfix completions create an expression that moves an argument, | ||
122 | // so it's better to consider references now to avoid breaking the compilation | ||
123 | let dot_receiver = include_references(dot_receiver); | ||
124 | let receiver_text = | ||
125 | get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); | ||
126 | |||
127 | match try_enum { | ||
128 | Some(try_enum) => match try_enum { | ||
129 | TryEnum::Result => { | ||
130 | postfix_snippet( | ||
131 | ctx, | ||
132 | cap, | ||
133 | &dot_receiver, | ||
134 | "match", | ||
135 | "match expr {}", | ||
136 | &format!("match {} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}", receiver_text), | ||
137 | ) | ||
138 | .add_to(acc); | ||
139 | } | ||
140 | TryEnum::Option => { | ||
141 | postfix_snippet( | ||
142 | ctx, | ||
143 | cap, | ||
144 | &dot_receiver, | ||
145 | "match", | ||
146 | "match expr {}", | ||
147 | &format!( | ||
148 | "match {} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}", | ||
149 | receiver_text | ||
150 | ), | ||
151 | ) | ||
152 | .add_to(acc); | ||
153 | } | ||
154 | }, | ||
155 | None => { | ||
156 | postfix_snippet( | ||
157 | ctx, | ||
158 | cap, | ||
159 | &dot_receiver, | ||
160 | "match", | ||
161 | "match expr {}", | ||
162 | &format!("match {} {{\n ${{1:_}} => {{$0}},\n}}", receiver_text), | ||
163 | ) | ||
164 | .add_to(acc); | ||
165 | } | ||
166 | } | ||
167 | |||
168 | postfix_snippet( | ||
169 | ctx, | ||
170 | cap, | ||
171 | &dot_receiver, | ||
172 | "box", | ||
173 | "Box::new(expr)", | ||
174 | &format!("Box::new({})", receiver_text), | ||
175 | ) | ||
176 | .add_to(acc); | ||
177 | |||
178 | postfix_snippet( | ||
179 | ctx, | ||
180 | cap, | ||
181 | &dot_receiver, | ||
182 | "dbg", | ||
183 | "dbg!(expr)", | ||
184 | &format!("dbg!({})", receiver_text), | ||
185 | ) | ||
186 | .add_to(acc); | ||
187 | |||
188 | postfix_snippet( | ||
189 | ctx, | ||
190 | cap, | ||
191 | &dot_receiver, | ||
192 | "call", | ||
193 | "function(expr)", | ||
194 | &format!("${{1}}({})", receiver_text), | ||
195 | ) | ||
196 | .add_to(acc); | ||
197 | } | ||
198 | |||
199 | fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String { | ||
200 | if receiver_is_ambiguous_float_literal { | ||
201 | let text = receiver.syntax().text(); | ||
202 | let without_dot = ..text.len() - TextSize::of('.'); | ||
203 | text.slice(without_dot).to_string() | ||
204 | } else { | ||
205 | receiver.to_string() | ||
206 | } | ||
207 | } | ||
208 | |||
209 | fn include_references(initial_element: &ast::Expr) -> ast::Expr { | ||
210 | let mut resulting_element = initial_element.clone(); | ||
211 | while let Some(parent_ref_element) = | ||
212 | resulting_element.syntax().parent().and_then(ast::RefExpr::cast) | ||
213 | { | ||
214 | resulting_element = ast::Expr::from(parent_ref_element); | ||
215 | } | ||
216 | resulting_element | ||
217 | } | ||
218 | |||
219 | fn postfix_snippet( | ||
220 | ctx: &CompletionContext, | ||
221 | cap: SnippetCap, | ||
222 | receiver: &ast::Expr, | ||
223 | label: &str, | ||
224 | detail: &str, | ||
225 | snippet: &str, | ||
226 | ) -> Builder { | ||
227 | let edit = { | ||
228 | let receiver_syntax = receiver.syntax(); | ||
229 | let receiver_range = ctx.sema.original_range(receiver_syntax).range; | ||
230 | let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end()); | ||
231 | TextEdit::replace(delete_range, snippet.to_string()) | ||
232 | }; | ||
233 | CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label) | ||
234 | .detail(detail) | ||
235 | .kind(CompletionItemKind::Snippet) | ||
236 | .snippet_edit(cap, edit) | ||
237 | } | ||
238 | |||
239 | #[cfg(test)] | ||
240 | mod tests { | ||
241 | use expect::{expect, Expect}; | ||
242 | |||
243 | use crate::completion::{ | ||
244 | test_utils::{check_edit, completion_list}, | ||
245 | CompletionKind, | ||
246 | }; | ||
247 | |||
248 | fn check(ra_fixture: &str, expect: Expect) { | ||
249 | let actual = completion_list(ra_fixture, CompletionKind::Postfix); | ||
250 | expect.assert_eq(&actual) | ||
251 | } | ||
252 | |||
253 | #[test] | ||
254 | fn postfix_completion_works_for_trivial_path_expression() { | ||
255 | check( | ||
256 | r#" | ||
257 | fn main() { | ||
258 | let bar = true; | ||
259 | bar.<|> | ||
260 | } | ||
261 | "#, | ||
262 | expect![[r#" | ||
263 | sn box Box::new(expr) | ||
264 | sn call function(expr) | ||
265 | sn dbg dbg!(expr) | ||
266 | sn if if expr {} | ||
267 | sn match match expr {} | ||
268 | sn not !expr | ||
269 | sn ref &expr | ||
270 | sn refm &mut expr | ||
271 | sn while while expr {} | ||
272 | "#]], | ||
273 | ); | ||
274 | } | ||
275 | |||
276 | #[test] | ||
277 | fn postfix_type_filtering() { | ||
278 | check( | ||
279 | r#" | ||
280 | fn main() { | ||
281 | let bar: u8 = 12; | ||
282 | bar.<|> | ||
283 | } | ||
284 | "#, | ||
285 | expect![[r#" | ||
286 | sn box Box::new(expr) | ||
287 | sn call function(expr) | ||
288 | sn dbg dbg!(expr) | ||
289 | sn match match expr {} | ||
290 | sn ref &expr | ||
291 | sn refm &mut expr | ||
292 | "#]], | ||
293 | ) | ||
294 | } | ||
295 | |||
296 | #[test] | ||
297 | fn option_iflet() { | ||
298 | check_edit( | ||
299 | "ifl", | ||
300 | r#" | ||
301 | enum Option<T> { Some(T), None } | ||
302 | |||
303 | fn main() { | ||
304 | let bar = Option::Some(true); | ||
305 | bar.<|> | ||
306 | } | ||
307 | "#, | ||
308 | r#" | ||
309 | enum Option<T> { Some(T), None } | ||
310 | |||
311 | fn main() { | ||
312 | let bar = Option::Some(true); | ||
313 | if let Some($1) = bar { | ||
314 | $0 | ||
315 | } | ||
316 | } | ||
317 | "#, | ||
318 | ); | ||
319 | } | ||
320 | |||
321 | #[test] | ||
322 | fn result_match() { | ||
323 | check_edit( | ||
324 | "match", | ||
325 | r#" | ||
326 | enum Result<T, E> { Ok(T), Err(E) } | ||
327 | |||
328 | fn main() { | ||
329 | let bar = Result::Ok(true); | ||
330 | bar.<|> | ||
331 | } | ||
332 | "#, | ||
333 | r#" | ||
334 | enum Result<T, E> { Ok(T), Err(E) } | ||
335 | |||
336 | fn main() { | ||
337 | let bar = Result::Ok(true); | ||
338 | match bar { | ||
339 | Ok(${1:_}) => {$2}, | ||
340 | Err(${3:_}) => {$0}, | ||
341 | } | ||
342 | } | ||
343 | "#, | ||
344 | ); | ||
345 | } | ||
346 | |||
347 | #[test] | ||
348 | fn postfix_completion_works_for_ambiguous_float_literal() { | ||
349 | check_edit("refm", r#"fn main() { 42.<|> }"#, r#"fn main() { &mut 42 }"#) | ||
350 | } | ||
351 | |||
352 | #[test] | ||
353 | fn works_in_simple_macro() { | ||
354 | check_edit( | ||
355 | "dbg", | ||
356 | r#" | ||
357 | macro_rules! m { ($e:expr) => { $e } } | ||
358 | fn main() { | ||
359 | let bar: u8 = 12; | ||
360 | m!(bar.d<|>) | ||
361 | } | ||
362 | "#, | ||
363 | r#" | ||
364 | macro_rules! m { ($e:expr) => { $e } } | ||
365 | fn main() { | ||
366 | let bar: u8 = 12; | ||
367 | m!(dbg!(bar)) | ||
368 | } | ||
369 | "#, | ||
370 | ); | ||
371 | } | ||
372 | |||
373 | #[test] | ||
374 | fn postfix_completion_for_references() { | ||
375 | check_edit("dbg", r#"fn main() { &&42.<|> }"#, r#"fn main() { dbg!(&&42) }"#); | ||
376 | check_edit("refm", r#"fn main() { &&42.<|> }"#, r#"fn main() { &&&mut 42 }"#); | ||
377 | } | ||
378 | } | ||
diff --git a/crates/ide/src/completion/complete_qualified_path.rs b/crates/ide/src/completion/complete_qualified_path.rs new file mode 100644 index 000000000..74794dc88 --- /dev/null +++ b/crates/ide/src/completion/complete_qualified_path.rs | |||
@@ -0,0 +1,733 @@ | |||
1 | //! Completion of paths, i.e. `some::prefix::<|>`. | ||
2 | |||
3 | use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; | ||
4 | use rustc_hash::FxHashSet; | ||
5 | use syntax::AstNode; | ||
6 | use test_utils::mark; | ||
7 | |||
8 | use crate::completion::{CompletionContext, Completions}; | ||
9 | |||
10 | pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) { | ||
11 | let path = match &ctx.path_qual { | ||
12 | Some(path) => path.clone(), | ||
13 | None => return, | ||
14 | }; | ||
15 | |||
16 | if ctx.attribute_under_caret.is_some() { | ||
17 | return; | ||
18 | } | ||
19 | |||
20 | let context_module = ctx.scope.module(); | ||
21 | |||
22 | let resolution = match ctx.sema.resolve_path(&path) { | ||
23 | Some(res) => res, | ||
24 | None => return, | ||
25 | }; | ||
26 | |||
27 | // Add associated types on type parameters and `Self`. | ||
28 | resolution.assoc_type_shorthand_candidates(ctx.db, |alias| { | ||
29 | acc.add_type_alias(ctx, alias); | ||
30 | None::<()> | ||
31 | }); | ||
32 | |||
33 | match resolution { | ||
34 | PathResolution::Def(hir::ModuleDef::Module(module)) => { | ||
35 | let module_scope = module.scope(ctx.db, context_module); | ||
36 | for (name, def) in module_scope { | ||
37 | if ctx.use_item_syntax.is_some() { | ||
38 | if let ScopeDef::Unknown = def { | ||
39 | if let Some(name_ref) = ctx.name_ref_syntax.as_ref() { | ||
40 | if name_ref.syntax().text() == name.to_string().as_str() { | ||
41 | // for `use self::foo<|>`, don't suggest `foo` as a completion | ||
42 | mark::hit!(dont_complete_current_use); | ||
43 | continue; | ||
44 | } | ||
45 | } | ||
46 | } | ||
47 | } | ||
48 | |||
49 | acc.add_resolution(ctx, name.to_string(), &def); | ||
50 | } | ||
51 | } | ||
52 | PathResolution::Def(def @ hir::ModuleDef::Adt(_)) | ||
53 | | PathResolution::Def(def @ hir::ModuleDef::TypeAlias(_)) => { | ||
54 | if let hir::ModuleDef::Adt(Adt::Enum(e)) = def { | ||
55 | for variant in e.variants(ctx.db) { | ||
56 | acc.add_enum_variant(ctx, variant, None); | ||
57 | } | ||
58 | } | ||
59 | let ty = match def { | ||
60 | hir::ModuleDef::Adt(adt) => adt.ty(ctx.db), | ||
61 | hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db), | ||
62 | _ => unreachable!(), | ||
63 | }; | ||
64 | |||
65 | // XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType. | ||
66 | // (where AssocType is defined on a trait, not an inherent impl) | ||
67 | |||
68 | let krate = ctx.krate; | ||
69 | if let Some(krate) = krate { | ||
70 | let traits_in_scope = ctx.scope.traits_in_scope(); | ||
71 | ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| { | ||
72 | if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { | ||
73 | return None; | ||
74 | } | ||
75 | match item { | ||
76 | hir::AssocItem::Function(func) => { | ||
77 | acc.add_function(ctx, func, None); | ||
78 | } | ||
79 | hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), | ||
80 | hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), | ||
81 | } | ||
82 | None::<()> | ||
83 | }); | ||
84 | |||
85 | // Iterate assoc types separately | ||
86 | ty.iterate_assoc_items(ctx.db, krate, |item| { | ||
87 | if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { | ||
88 | return None; | ||
89 | } | ||
90 | match item { | ||
91 | hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => {} | ||
92 | hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), | ||
93 | } | ||
94 | None::<()> | ||
95 | }); | ||
96 | } | ||
97 | } | ||
98 | PathResolution::Def(hir::ModuleDef::Trait(t)) => { | ||
99 | // Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`. | ||
100 | for item in t.items(ctx.db) { | ||
101 | if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { | ||
102 | continue; | ||
103 | } | ||
104 | match item { | ||
105 | hir::AssocItem::Function(func) => { | ||
106 | acc.add_function(ctx, func, None); | ||
107 | } | ||
108 | hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), | ||
109 | hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), | ||
110 | } | ||
111 | } | ||
112 | } | ||
113 | PathResolution::TypeParam(_) | PathResolution::SelfType(_) => { | ||
114 | if let Some(krate) = ctx.krate { | ||
115 | let ty = match resolution { | ||
116 | PathResolution::TypeParam(param) => param.ty(ctx.db), | ||
117 | PathResolution::SelfType(impl_def) => impl_def.target_ty(ctx.db), | ||
118 | _ => return, | ||
119 | }; | ||
120 | |||
121 | let traits_in_scope = ctx.scope.traits_in_scope(); | ||
122 | let mut seen = FxHashSet::default(); | ||
123 | ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| { | ||
124 | if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { | ||
125 | return None; | ||
126 | } | ||
127 | |||
128 | // We might iterate candidates of a trait multiple times here, so deduplicate | ||
129 | // them. | ||
130 | if seen.insert(item) { | ||
131 | match item { | ||
132 | hir::AssocItem::Function(func) => { | ||
133 | acc.add_function(ctx, func, None); | ||
134 | } | ||
135 | hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), | ||
136 | hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), | ||
137 | } | ||
138 | } | ||
139 | None::<()> | ||
140 | }); | ||
141 | } | ||
142 | } | ||
143 | _ => {} | ||
144 | } | ||
145 | } | ||
146 | |||
147 | #[cfg(test)] | ||
148 | mod tests { | ||
149 | use expect::{expect, Expect}; | ||
150 | use test_utils::mark; | ||
151 | |||
152 | use crate::completion::{ | ||
153 | test_utils::{check_edit, completion_list}, | ||
154 | CompletionKind, | ||
155 | }; | ||
156 | |||
157 | fn check(ra_fixture: &str, expect: Expect) { | ||
158 | let actual = completion_list(ra_fixture, CompletionKind::Reference); | ||
159 | expect.assert_eq(&actual); | ||
160 | } | ||
161 | |||
162 | fn check_builtin(ra_fixture: &str, expect: Expect) { | ||
163 | let actual = completion_list(ra_fixture, CompletionKind::BuiltinType); | ||
164 | expect.assert_eq(&actual); | ||
165 | } | ||
166 | |||
167 | #[test] | ||
168 | fn dont_complete_current_use() { | ||
169 | mark::check!(dont_complete_current_use); | ||
170 | check(r#"use self::foo<|>;"#, expect![[""]]); | ||
171 | } | ||
172 | |||
173 | #[test] | ||
174 | fn dont_complete_current_use_in_braces_with_glob() { | ||
175 | check( | ||
176 | r#" | ||
177 | mod foo { pub struct S; } | ||
178 | use self::{foo::*, bar<|>}; | ||
179 | "#, | ||
180 | expect![[r#" | ||
181 | st S | ||
182 | md foo | ||
183 | "#]], | ||
184 | ); | ||
185 | } | ||
186 | |||
187 | #[test] | ||
188 | fn dont_complete_primitive_in_use() { | ||
189 | check_builtin(r#"use self::<|>;"#, expect![[""]]); | ||
190 | } | ||
191 | |||
192 | #[test] | ||
193 | fn dont_complete_primitive_in_module_scope() { | ||
194 | check_builtin(r#"fn foo() { self::<|> }"#, expect![[""]]); | ||
195 | } | ||
196 | |||
197 | #[test] | ||
198 | fn completes_primitives() { | ||
199 | check_builtin( | ||
200 | r#"fn main() { let _: <|> = 92; }"#, | ||
201 | expect![[r#" | ||
202 | bt bool | ||
203 | bt char | ||
204 | bt f32 | ||
205 | bt f64 | ||
206 | bt i128 | ||
207 | bt i16 | ||
208 | bt i32 | ||
209 | bt i64 | ||
210 | bt i8 | ||
211 | bt isize | ||
212 | bt str | ||
213 | bt u128 | ||
214 | bt u16 | ||
215 | bt u32 | ||
216 | bt u64 | ||
217 | bt u8 | ||
218 | bt usize | ||
219 | "#]], | ||
220 | ); | ||
221 | } | ||
222 | |||
223 | #[test] | ||
224 | fn completes_mod_with_same_name_as_function() { | ||
225 | check( | ||
226 | r#" | ||
227 | use self::my::<|>; | ||
228 | |||
229 | mod my { pub struct Bar; } | ||
230 | fn my() {} | ||
231 | "#, | ||
232 | expect![[r#" | ||
233 | st Bar | ||
234 | "#]], | ||
235 | ); | ||
236 | } | ||
237 | |||
238 | #[test] | ||
239 | fn filters_visibility() { | ||
240 | check( | ||
241 | r#" | ||
242 | use self::my::<|>; | ||
243 | |||
244 | mod my { | ||
245 | struct Bar; | ||
246 | pub struct Foo; | ||
247 | pub use Bar as PublicBar; | ||
248 | } | ||
249 | "#, | ||
250 | expect![[r#" | ||
251 | st Foo | ||
252 | st PublicBar | ||
253 | "#]], | ||
254 | ); | ||
255 | } | ||
256 | |||
257 | #[test] | ||
258 | fn completes_use_item_starting_with_self() { | ||
259 | check( | ||
260 | r#" | ||
261 | use self::m::<|>; | ||
262 | |||
263 | mod m { pub struct Bar; } | ||
264 | "#, | ||
265 | expect![[r#" | ||
266 | st Bar | ||
267 | "#]], | ||
268 | ); | ||
269 | } | ||
270 | |||
271 | #[test] | ||
272 | fn completes_use_item_starting_with_crate() { | ||
273 | check( | ||
274 | r#" | ||
275 | //- /lib.rs | ||
276 | mod foo; | ||
277 | struct Spam; | ||
278 | //- /foo.rs | ||
279 | use crate::Sp<|> | ||
280 | "#, | ||
281 | expect![[r#" | ||
282 | st Spam | ||
283 | md foo | ||
284 | "#]], | ||
285 | ); | ||
286 | } | ||
287 | |||
288 | #[test] | ||
289 | fn completes_nested_use_tree() { | ||
290 | check( | ||
291 | r#" | ||
292 | //- /lib.rs | ||
293 | mod foo; | ||
294 | struct Spam; | ||
295 | //- /foo.rs | ||
296 | use crate::{Sp<|>}; | ||
297 | "#, | ||
298 | expect![[r#" | ||
299 | st Spam | ||
300 | md foo | ||
301 | "#]], | ||
302 | ); | ||
303 | } | ||
304 | |||
305 | #[test] | ||
306 | fn completes_deeply_nested_use_tree() { | ||
307 | check( | ||
308 | r#" | ||
309 | //- /lib.rs | ||
310 | mod foo; | ||
311 | pub mod bar { | ||
312 | pub mod baz { | ||
313 | pub struct Spam; | ||
314 | } | ||
315 | } | ||
316 | //- /foo.rs | ||
317 | use crate::{bar::{baz::Sp<|>}}; | ||
318 | "#, | ||
319 | expect![[r#" | ||
320 | st Spam | ||
321 | "#]], | ||
322 | ); | ||
323 | } | ||
324 | |||
325 | #[test] | ||
326 | fn completes_enum_variant() { | ||
327 | check( | ||
328 | r#" | ||
329 | enum E { Foo, Bar(i32) } | ||
330 | fn foo() { let _ = E::<|> } | ||
331 | "#, | ||
332 | expect![[r#" | ||
333 | ev Bar(…) (i32) | ||
334 | ev Foo () | ||
335 | "#]], | ||
336 | ); | ||
337 | } | ||
338 | |||
339 | #[test] | ||
340 | fn completes_struct_associated_items() { | ||
341 | check( | ||
342 | r#" | ||
343 | //- /lib.rs | ||
344 | struct S; | ||
345 | |||
346 | impl S { | ||
347 | fn a() {} | ||
348 | fn b(&self) {} | ||
349 | const C: i32 = 42; | ||
350 | type T = i32; | ||
351 | } | ||
352 | |||
353 | fn foo() { let _ = S::<|> } | ||
354 | "#, | ||
355 | expect![[r#" | ||
356 | ct C const C: i32 = 42; | ||
357 | ta T type T = i32; | ||
358 | fn a() fn a() | ||
359 | me b() fn b(&self) | ||
360 | "#]], | ||
361 | ); | ||
362 | } | ||
363 | |||
364 | #[test] | ||
365 | fn associated_item_visibility() { | ||
366 | check( | ||
367 | r#" | ||
368 | struct S; | ||
369 | |||
370 | mod m { | ||
371 | impl super::S { | ||
372 | pub(super) fn public_method() { } | ||
373 | fn private_method() { } | ||
374 | pub(super) type PublicType = u32; | ||
375 | type PrivateType = u32; | ||
376 | pub(super) const PUBLIC_CONST: u32 = 1; | ||
377 | const PRIVATE_CONST: u32 = 1; | ||
378 | } | ||
379 | } | ||
380 | |||
381 | fn foo() { let _ = S::<|> } | ||
382 | "#, | ||
383 | expect![[r#" | ||
384 | ct PUBLIC_CONST pub(super) const PUBLIC_CONST: u32 = 1; | ||
385 | ta PublicType pub(super) type PublicType = u32; | ||
386 | fn public_method() pub(super) fn public_method() | ||
387 | "#]], | ||
388 | ); | ||
389 | } | ||
390 | |||
391 | #[test] | ||
392 | fn completes_enum_associated_method() { | ||
393 | check( | ||
394 | r#" | ||
395 | enum E {}; | ||
396 | impl E { fn m() { } } | ||
397 | |||
398 | fn foo() { let _ = E::<|> } | ||
399 | "#, | ||
400 | expect![[r#" | ||
401 | fn m() fn m() | ||
402 | "#]], | ||
403 | ); | ||
404 | } | ||
405 | |||
406 | #[test] | ||
407 | fn completes_union_associated_method() { | ||
408 | check( | ||
409 | r#" | ||
410 | union U {}; | ||
411 | impl U { fn m() { } } | ||
412 | |||
413 | fn foo() { let _ = U::<|> } | ||
414 | "#, | ||
415 | expect![[r#" | ||
416 | fn m() fn m() | ||
417 | "#]], | ||
418 | ); | ||
419 | } | ||
420 | |||
421 | #[test] | ||
422 | fn completes_use_paths_across_crates() { | ||
423 | check( | ||
424 | r#" | ||
425 | //- /main.rs | ||
426 | use foo::<|>; | ||
427 | |||
428 | //- /foo/lib.rs | ||
429 | pub mod bar { pub struct S; } | ||
430 | "#, | ||
431 | expect![[r#" | ||
432 | md bar | ||
433 | "#]], | ||
434 | ); | ||
435 | } | ||
436 | |||
437 | #[test] | ||
438 | fn completes_trait_associated_method_1() { | ||
439 | check( | ||
440 | r#" | ||
441 | trait Trait { fn m(); } | ||
442 | |||
443 | fn foo() { let _ = Trait::<|> } | ||
444 | "#, | ||
445 | expect![[r#" | ||
446 | fn m() fn m() | ||
447 | "#]], | ||
448 | ); | ||
449 | } | ||
450 | |||
451 | #[test] | ||
452 | fn completes_trait_associated_method_2() { | ||
453 | check( | ||
454 | r#" | ||
455 | trait Trait { fn m(); } | ||
456 | |||
457 | struct S; | ||
458 | impl Trait for S {} | ||
459 | |||
460 | fn foo() { let _ = S::<|> } | ||
461 | "#, | ||
462 | expect![[r#" | ||
463 | fn m() fn m() | ||
464 | "#]], | ||
465 | ); | ||
466 | } | ||
467 | |||
468 | #[test] | ||
469 | fn completes_trait_associated_method_3() { | ||
470 | check( | ||
471 | r#" | ||
472 | trait Trait { fn m(); } | ||
473 | |||
474 | struct S; | ||
475 | impl Trait for S {} | ||
476 | |||
477 | fn foo() { let _ = <S as Trait>::<|> } | ||
478 | "#, | ||
479 | expect![[r#" | ||
480 | fn m() fn m() | ||
481 | "#]], | ||
482 | ); | ||
483 | } | ||
484 | |||
485 | #[test] | ||
486 | fn completes_ty_param_assoc_ty() { | ||
487 | check( | ||
488 | r#" | ||
489 | trait Super { | ||
490 | type Ty; | ||
491 | const CONST: u8; | ||
492 | fn func() {} | ||
493 | fn method(&self) {} | ||
494 | } | ||
495 | |||
496 | trait Sub: Super { | ||
497 | type SubTy; | ||
498 | const C2: (); | ||
499 | fn subfunc() {} | ||
500 | fn submethod(&self) {} | ||
501 | } | ||
502 | |||
503 | fn foo<T: Sub>() { T::<|> } | ||
504 | "#, | ||
505 | expect![[r#" | ||
506 | ct C2 const C2: (); | ||
507 | ct CONST const CONST: u8; | ||
508 | ta SubTy type SubTy; | ||
509 | ta Ty type Ty; | ||
510 | fn func() fn func() | ||
511 | me method() fn method(&self) | ||
512 | fn subfunc() fn subfunc() | ||
513 | me submethod() fn submethod(&self) | ||
514 | "#]], | ||
515 | ); | ||
516 | } | ||
517 | |||
518 | #[test] | ||
519 | fn completes_self_param_assoc_ty() { | ||
520 | check( | ||
521 | r#" | ||
522 | trait Super { | ||
523 | type Ty; | ||
524 | const CONST: u8 = 0; | ||
525 | fn func() {} | ||
526 | fn method(&self) {} | ||
527 | } | ||
528 | |||
529 | trait Sub: Super { | ||
530 | type SubTy; | ||
531 | const C2: () = (); | ||
532 | fn subfunc() {} | ||
533 | fn submethod(&self) {} | ||
534 | } | ||
535 | |||
536 | struct Wrap<T>(T); | ||
537 | impl<T> Super for Wrap<T> {} | ||
538 | impl<T> Sub for Wrap<T> { | ||
539 | fn subfunc() { | ||
540 | // Should be able to assume `Self: Sub + Super` | ||
541 | Self::<|> | ||
542 | } | ||
543 | } | ||
544 | "#, | ||
545 | expect![[r#" | ||
546 | ct C2 const C2: () = (); | ||
547 | ct CONST const CONST: u8 = 0; | ||
548 | ta SubTy type SubTy; | ||
549 | ta Ty type Ty; | ||
550 | fn func() fn func() | ||
551 | me method() fn method(&self) | ||
552 | fn subfunc() fn subfunc() | ||
553 | me submethod() fn submethod(&self) | ||
554 | "#]], | ||
555 | ); | ||
556 | } | ||
557 | |||
558 | #[test] | ||
559 | fn completes_type_alias() { | ||
560 | check( | ||
561 | r#" | ||
562 | struct S; | ||
563 | impl S { fn foo() {} } | ||
564 | type T = S; | ||
565 | impl T { fn bar() {} } | ||
566 | |||
567 | fn main() { T::<|>; } | ||
568 | "#, | ||
569 | expect![[r#" | ||
570 | fn bar() fn bar() | ||
571 | fn foo() fn foo() | ||
572 | "#]], | ||
573 | ); | ||
574 | } | ||
575 | |||
576 | #[test] | ||
577 | fn completes_qualified_macros() { | ||
578 | check( | ||
579 | r#" | ||
580 | #[macro_export] | ||
581 | macro_rules! foo { () => {} } | ||
582 | |||
583 | fn main() { let _ = crate::<|> } | ||
584 | "#, | ||
585 | expect![[r##" | ||
586 | ma foo!(…) #[macro_export] | ||
587 | macro_rules! foo | ||
588 | fn main() fn main() | ||
589 | "##]], | ||
590 | ); | ||
591 | } | ||
592 | |||
593 | #[test] | ||
594 | fn test_super_super_completion() { | ||
595 | check( | ||
596 | r#" | ||
597 | mod a { | ||
598 | const A: usize = 0; | ||
599 | mod b { | ||
600 | const B: usize = 0; | ||
601 | mod c { use super::super::<|> } | ||
602 | } | ||
603 | } | ||
604 | "#, | ||
605 | expect![[r#" | ||
606 | ct A | ||
607 | md b | ||
608 | "#]], | ||
609 | ); | ||
610 | } | ||
611 | |||
612 | #[test] | ||
613 | fn completes_reexported_items_under_correct_name() { | ||
614 | check( | ||
615 | r#" | ||
616 | fn foo() { self::m::<|> } | ||
617 | |||
618 | mod m { | ||
619 | pub use super::p::wrong_fn as right_fn; | ||
620 | pub use super::p::WRONG_CONST as RIGHT_CONST; | ||
621 | pub use super::p::WrongType as RightType; | ||
622 | } | ||
623 | mod p { | ||
624 | fn wrong_fn() {} | ||
625 | const WRONG_CONST: u32 = 1; | ||
626 | struct WrongType {}; | ||
627 | } | ||
628 | "#, | ||
629 | expect![[r#" | ||
630 | ct RIGHT_CONST | ||
631 | st RightType | ||
632 | fn right_fn() fn wrong_fn() | ||
633 | "#]], | ||
634 | ); | ||
635 | |||
636 | check_edit( | ||
637 | "RightType", | ||
638 | r#" | ||
639 | fn foo() { self::m::<|> } | ||
640 | |||
641 | mod m { | ||
642 | pub use super::p::wrong_fn as right_fn; | ||
643 | pub use super::p::WRONG_CONST as RIGHT_CONST; | ||
644 | pub use super::p::WrongType as RightType; | ||
645 | } | ||
646 | mod p { | ||
647 | fn wrong_fn() {} | ||
648 | const WRONG_CONST: u32 = 1; | ||
649 | struct WrongType {}; | ||
650 | } | ||
651 | "#, | ||
652 | r#" | ||
653 | fn foo() { self::m::RightType } | ||
654 | |||
655 | mod m { | ||
656 | pub use super::p::wrong_fn as right_fn; | ||
657 | pub use super::p::WRONG_CONST as RIGHT_CONST; | ||
658 | pub use super::p::WrongType as RightType; | ||
659 | } | ||
660 | mod p { | ||
661 | fn wrong_fn() {} | ||
662 | const WRONG_CONST: u32 = 1; | ||
663 | struct WrongType {}; | ||
664 | } | ||
665 | "#, | ||
666 | ); | ||
667 | } | ||
668 | |||
669 | #[test] | ||
670 | fn completes_in_simple_macro_call() { | ||
671 | check( | ||
672 | r#" | ||
673 | macro_rules! m { ($e:expr) => { $e } } | ||
674 | fn main() { m!(self::f<|>); } | ||
675 | fn foo() {} | ||
676 | "#, | ||
677 | expect![[r#" | ||
678 | fn foo() fn foo() | ||
679 | fn main() fn main() | ||
680 | "#]], | ||
681 | ); | ||
682 | } | ||
683 | |||
684 | #[test] | ||
685 | fn function_mod_share_name() { | ||
686 | check( | ||
687 | r#" | ||
688 | fn foo() { self::m::<|> } | ||
689 | |||
690 | mod m { | ||
691 | pub mod z {} | ||
692 | pub fn z() {} | ||
693 | } | ||
694 | "#, | ||
695 | expect![[r#" | ||
696 | md z | ||
697 | fn z() pub fn z() | ||
698 | "#]], | ||
699 | ); | ||
700 | } | ||
701 | |||
702 | #[test] | ||
703 | fn completes_hashmap_new() { | ||
704 | check( | ||
705 | r#" | ||
706 | struct RandomState; | ||
707 | struct HashMap<K, V, S = RandomState> {} | ||
708 | |||
709 | impl<K, V> HashMap<K, V, RandomState> { | ||
710 | pub fn new() -> HashMap<K, V, RandomState> { } | ||
711 | } | ||
712 | fn foo() { | ||
713 | HashMap::<|> | ||
714 | } | ||
715 | "#, | ||
716 | expect![[r#" | ||
717 | fn new() pub fn new() -> HashMap<K, V, RandomState> | ||
718 | "#]], | ||
719 | ); | ||
720 | } | ||
721 | |||
722 | #[test] | ||
723 | fn dont_complete_attr() { | ||
724 | check( | ||
725 | r#" | ||
726 | mod foo { pub struct Foo; } | ||
727 | #[foo::<|>] | ||
728 | fn f() {} | ||
729 | "#, | ||
730 | expect![[""]], | ||
731 | ); | ||
732 | } | ||
733 | } | ||
diff --git a/crates/ide/src/completion/complete_record.rs b/crates/ide/src/completion/complete_record.rs new file mode 100644 index 000000000..74b94594d --- /dev/null +++ b/crates/ide/src/completion/complete_record.rs | |||
@@ -0,0 +1,226 @@ | |||
1 | //! Complete fields in record literals and patterns. | ||
2 | use crate::completion::{CompletionContext, Completions}; | ||
3 | |||
4 | pub(super) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | ||
5 | let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) { | ||
6 | (None, None) => return None, | ||
7 | (Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"), | ||
8 | (Some(record_pat), _) => ctx.sema.record_pattern_missing_fields(record_pat), | ||
9 | (_, Some(record_lit)) => ctx.sema.record_literal_missing_fields(record_lit), | ||
10 | }; | ||
11 | |||
12 | for (field, ty) in missing_fields { | ||
13 | acc.add_field(ctx, field, &ty) | ||
14 | } | ||
15 | |||
16 | Some(()) | ||
17 | } | ||
18 | |||
19 | #[cfg(test)] | ||
20 | mod tests { | ||
21 | use expect::{expect, Expect}; | ||
22 | |||
23 | use crate::completion::{test_utils::completion_list, CompletionKind}; | ||
24 | |||
25 | fn check(ra_fixture: &str, expect: Expect) { | ||
26 | let actual = completion_list(ra_fixture, CompletionKind::Reference); | ||
27 | expect.assert_eq(&actual); | ||
28 | } | ||
29 | |||
30 | #[test] | ||
31 | fn test_record_pattern_field() { | ||
32 | check( | ||
33 | r#" | ||
34 | struct S { foo: u32 } | ||
35 | |||
36 | fn process(f: S) { | ||
37 | match f { | ||
38 | S { f<|>: 92 } => (), | ||
39 | } | ||
40 | } | ||
41 | "#, | ||
42 | expect![[r#" | ||
43 | fd foo u32 | ||
44 | "#]], | ||
45 | ); | ||
46 | } | ||
47 | |||
48 | #[test] | ||
49 | fn test_record_pattern_enum_variant() { | ||
50 | check( | ||
51 | r#" | ||
52 | enum E { S { foo: u32, bar: () } } | ||
53 | |||
54 | fn process(e: E) { | ||
55 | match e { | ||
56 | E::S { <|> } => (), | ||
57 | } | ||
58 | } | ||
59 | "#, | ||
60 | expect![[r#" | ||
61 | fd bar () | ||
62 | fd foo u32 | ||
63 | "#]], | ||
64 | ); | ||
65 | } | ||
66 | |||
67 | #[test] | ||
68 | fn test_record_pattern_field_in_simple_macro() { | ||
69 | check( | ||
70 | r" | ||
71 | macro_rules! m { ($e:expr) => { $e } } | ||
72 | struct S { foo: u32 } | ||
73 | |||
74 | fn process(f: S) { | ||
75 | m!(match f { | ||
76 | S { f<|>: 92 } => (), | ||
77 | }) | ||
78 | } | ||
79 | ", | ||
80 | expect![[r#" | ||
81 | fd foo u32 | ||
82 | "#]], | ||
83 | ); | ||
84 | } | ||
85 | |||
86 | #[test] | ||
87 | fn only_missing_fields_are_completed_in_destruct_pats() { | ||
88 | check( | ||
89 | r#" | ||
90 | struct S { | ||
91 | foo1: u32, foo2: u32, | ||
92 | bar: u32, baz: u32, | ||
93 | } | ||
94 | |||
95 | fn main() { | ||
96 | let s = S { | ||
97 | foo1: 1, foo2: 2, | ||
98 | bar: 3, baz: 4, | ||
99 | }; | ||
100 | if let S { foo1, foo2: a, <|> } = s {} | ||
101 | } | ||
102 | "#, | ||
103 | expect![[r#" | ||
104 | fd bar u32 | ||
105 | fd baz u32 | ||
106 | "#]], | ||
107 | ); | ||
108 | } | ||
109 | |||
110 | #[test] | ||
111 | fn test_record_literal_field() { | ||
112 | check( | ||
113 | r#" | ||
114 | struct A { the_field: u32 } | ||
115 | fn foo() { | ||
116 | A { the<|> } | ||
117 | } | ||
118 | "#, | ||
119 | expect![[r#" | ||
120 | fd the_field u32 | ||
121 | "#]], | ||
122 | ); | ||
123 | } | ||
124 | |||
125 | #[test] | ||
126 | fn test_record_literal_enum_variant() { | ||
127 | check( | ||
128 | r#" | ||
129 | enum E { A { a: u32 } } | ||
130 | fn foo() { | ||
131 | let _ = E::A { <|> } | ||
132 | } | ||
133 | "#, | ||
134 | expect![[r#" | ||
135 | fd a u32 | ||
136 | "#]], | ||
137 | ); | ||
138 | } | ||
139 | |||
140 | #[test] | ||
141 | fn test_record_literal_two_structs() { | ||
142 | check( | ||
143 | r#" | ||
144 | struct A { a: u32 } | ||
145 | struct B { b: u32 } | ||
146 | |||
147 | fn foo() { | ||
148 | let _: A = B { <|> } | ||
149 | } | ||
150 | "#, | ||
151 | expect![[r#" | ||
152 | fd b u32 | ||
153 | "#]], | ||
154 | ); | ||
155 | } | ||
156 | |||
157 | #[test] | ||
158 | fn test_record_literal_generic_struct() { | ||
159 | check( | ||
160 | r#" | ||
161 | struct A<T> { a: T } | ||
162 | |||
163 | fn foo() { | ||
164 | let _: A<u32> = A { <|> } | ||
165 | } | ||
166 | "#, | ||
167 | expect![[r#" | ||
168 | fd a u32 | ||
169 | "#]], | ||
170 | ); | ||
171 | } | ||
172 | |||
173 | #[test] | ||
174 | fn test_record_literal_field_in_simple_macro() { | ||
175 | check( | ||
176 | r#" | ||
177 | macro_rules! m { ($e:expr) => { $e } } | ||
178 | struct A { the_field: u32 } | ||
179 | fn foo() { | ||
180 | m!(A { the<|> }) | ||
181 | } | ||
182 | "#, | ||
183 | expect![[r#" | ||
184 | fd the_field u32 | ||
185 | "#]], | ||
186 | ); | ||
187 | } | ||
188 | |||
189 | #[test] | ||
190 | fn only_missing_fields_are_completed() { | ||
191 | check( | ||
192 | r#" | ||
193 | struct S { | ||
194 | foo1: u32, foo2: u32, | ||
195 | bar: u32, baz: u32, | ||
196 | } | ||
197 | |||
198 | fn main() { | ||
199 | let foo1 = 1; | ||
200 | let s = S { foo1, foo2: 5, <|> } | ||
201 | } | ||
202 | "#, | ||
203 | expect![[r#" | ||
204 | fd bar u32 | ||
205 | fd baz u32 | ||
206 | "#]], | ||
207 | ); | ||
208 | } | ||
209 | |||
210 | #[test] | ||
211 | fn completes_functional_update() { | ||
212 | check( | ||
213 | r#" | ||
214 | struct S { foo1: u32, foo2: u32 } | ||
215 | |||
216 | fn main() { | ||
217 | let foo1 = 1; | ||
218 | let s = S { foo1, <|> .. loop {} } | ||
219 | } | ||
220 | "#, | ||
221 | expect![[r#" | ||
222 | fd foo2 u32 | ||
223 | "#]], | ||
224 | ); | ||
225 | } | ||
226 | } | ||
diff --git a/crates/ide/src/completion/complete_snippet.rs b/crates/ide/src/completion/complete_snippet.rs new file mode 100644 index 000000000..4368e4eec --- /dev/null +++ b/crates/ide/src/completion/complete_snippet.rs | |||
@@ -0,0 +1,116 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::completion::{ | ||
4 | completion_config::SnippetCap, completion_item::Builder, CompletionContext, CompletionItem, | ||
5 | CompletionItemKind, CompletionKind, Completions, | ||
6 | }; | ||
7 | |||
8 | fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) -> Builder { | ||
9 | CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), label) | ||
10 | .insert_snippet(cap, snippet) | ||
11 | .kind(CompletionItemKind::Snippet) | ||
12 | } | ||
13 | |||
14 | pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) { | ||
15 | if !(ctx.is_trivial_path && ctx.function_syntax.is_some()) { | ||
16 | return; | ||
17 | } | ||
18 | let cap = match ctx.config.snippet_cap { | ||
19 | Some(it) => it, | ||
20 | None => return, | ||
21 | }; | ||
22 | |||
23 | snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc); | ||
24 | snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc); | ||
25 | } | ||
26 | |||
27 | pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) { | ||
28 | if !ctx.is_new_item { | ||
29 | return; | ||
30 | } | ||
31 | let cap = match ctx.config.snippet_cap { | ||
32 | Some(it) => it, | ||
33 | None => return, | ||
34 | }; | ||
35 | |||
36 | snippet( | ||
37 | ctx, | ||
38 | cap, | ||
39 | "tmod (Test module)", | ||
40 | "\ | ||
41 | #[cfg(test)] | ||
42 | mod tests { | ||
43 | use super::*; | ||
44 | |||
45 | #[test] | ||
46 | fn ${1:test_name}() { | ||
47 | $0 | ||
48 | } | ||
49 | }", | ||
50 | ) | ||
51 | .lookup_by("tmod") | ||
52 | .add_to(acc); | ||
53 | |||
54 | snippet( | ||
55 | ctx, | ||
56 | cap, | ||
57 | "tfn (Test function)", | ||
58 | "\ | ||
59 | #[test] | ||
60 | fn ${1:feature}() { | ||
61 | $0 | ||
62 | }", | ||
63 | ) | ||
64 | .lookup_by("tfn") | ||
65 | .add_to(acc); | ||
66 | |||
67 | snippet(ctx, cap, "macro_rules", "macro_rules! $1 {\n\t($2) => {\n\t\t$0\n\t};\n}").add_to(acc); | ||
68 | snippet(ctx, cap, "pub(crate)", "pub(crate) $0").add_to(acc); | ||
69 | } | ||
70 | |||
71 | #[cfg(test)] | ||
72 | mod tests { | ||
73 | use expect::{expect, Expect}; | ||
74 | |||
75 | use crate::completion::{test_utils::completion_list, CompletionKind}; | ||
76 | |||
77 | fn check(ra_fixture: &str, expect: Expect) { | ||
78 | let actual = completion_list(ra_fixture, CompletionKind::Snippet); | ||
79 | expect.assert_eq(&actual) | ||
80 | } | ||
81 | |||
82 | #[test] | ||
83 | fn completes_snippets_in_expressions() { | ||
84 | check( | ||
85 | r#"fn foo(x: i32) { <|> }"#, | ||
86 | expect![[r#" | ||
87 | sn pd | ||
88 | sn ppd | ||
89 | "#]], | ||
90 | ); | ||
91 | } | ||
92 | |||
93 | #[test] | ||
94 | fn should_not_complete_snippets_in_path() { | ||
95 | check(r#"fn foo(x: i32) { ::foo<|> }"#, expect![[""]]); | ||
96 | check(r#"fn foo(x: i32) { ::<|> }"#, expect![[""]]); | ||
97 | } | ||
98 | |||
99 | #[test] | ||
100 | fn completes_snippets_in_items() { | ||
101 | check( | ||
102 | r#" | ||
103 | #[cfg(test)] | ||
104 | mod tests { | ||
105 | <|> | ||
106 | } | ||
107 | "#, | ||
108 | expect![[r#" | ||
109 | sn macro_rules | ||
110 | sn pub(crate) | ||
111 | sn tfn (Test function) | ||
112 | sn tmod (Test module) | ||
113 | "#]], | ||
114 | ) | ||
115 | } | ||
116 | } | ||
diff --git a/crates/ide/src/completion/complete_trait_impl.rs b/crates/ide/src/completion/complete_trait_impl.rs new file mode 100644 index 000000000..478e31262 --- /dev/null +++ b/crates/ide/src/completion/complete_trait_impl.rs | |||
@@ -0,0 +1,488 @@ | |||
1 | //! Completion for associated items in a trait implementation. | ||
2 | //! | ||
3 | //! This module adds the completion items related to implementing associated | ||
4 | //! items within a `impl Trait for Struct` block. The current context node | ||
5 | //! must be within either a `FN`, `TYPE_ALIAS`, or `CONST` node | ||
6 | //! and an direct child of an `IMPL`. | ||
7 | //! | ||
8 | //! # Examples | ||
9 | //! | ||
10 | //! Considering the following trait `impl`: | ||
11 | //! | ||
12 | //! ```ignore | ||
13 | //! trait SomeTrait { | ||
14 | //! fn foo(); | ||
15 | //! } | ||
16 | //! | ||
17 | //! impl SomeTrait for () { | ||
18 | //! fn f<|> | ||
19 | //! } | ||
20 | //! ``` | ||
21 | //! | ||
22 | //! may result in the completion of the following method: | ||
23 | //! | ||
24 | //! ```ignore | ||
25 | //! # trait SomeTrait { | ||
26 | //! # fn foo(); | ||
27 | //! # } | ||
28 | //! | ||
29 | //! impl SomeTrait for () { | ||
30 | //! fn foo() {}<|> | ||
31 | //! } | ||
32 | //! ``` | ||
33 | |||
34 | use assists::utils::get_missing_assoc_items; | ||
35 | use hir::{self, Docs, HasSource}; | ||
36 | use syntax::{ | ||
37 | ast::{self, edit, Impl}, | ||
38 | AstNode, SyntaxKind, SyntaxNode, TextRange, T, | ||
39 | }; | ||
40 | use text_edit::TextEdit; | ||
41 | |||
42 | use crate::{ | ||
43 | completion::{ | ||
44 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, | ||
45 | }, | ||
46 | display::function_declaration, | ||
47 | }; | ||
48 | |||
49 | pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { | ||
50 | if let Some((trigger, impl_def)) = completion_match(ctx) { | ||
51 | match trigger.kind() { | ||
52 | SyntaxKind::NAME_REF => get_missing_assoc_items(&ctx.sema, &impl_def) | ||
53 | .into_iter() | ||
54 | .for_each(|item| match item { | ||
55 | hir::AssocItem::Function(fn_item) => { | ||
56 | add_function_impl(&trigger, acc, ctx, fn_item) | ||
57 | } | ||
58 | hir::AssocItem::TypeAlias(type_item) => { | ||
59 | add_type_alias_impl(&trigger, acc, ctx, type_item) | ||
60 | } | ||
61 | hir::AssocItem::Const(const_item) => { | ||
62 | add_const_impl(&trigger, acc, ctx, const_item) | ||
63 | } | ||
64 | }), | ||
65 | |||
66 | SyntaxKind::FN => { | ||
67 | for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) | ||
68 | .into_iter() | ||
69 | .filter_map(|item| match item { | ||
70 | hir::AssocItem::Function(fn_item) => Some(fn_item), | ||
71 | _ => None, | ||
72 | }) | ||
73 | { | ||
74 | add_function_impl(&trigger, acc, ctx, missing_fn); | ||
75 | } | ||
76 | } | ||
77 | |||
78 | SyntaxKind::TYPE_ALIAS => { | ||
79 | for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) | ||
80 | .into_iter() | ||
81 | .filter_map(|item| match item { | ||
82 | hir::AssocItem::TypeAlias(type_item) => Some(type_item), | ||
83 | _ => None, | ||
84 | }) | ||
85 | { | ||
86 | add_type_alias_impl(&trigger, acc, ctx, missing_fn); | ||
87 | } | ||
88 | } | ||
89 | |||
90 | SyntaxKind::CONST => { | ||
91 | for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) | ||
92 | .into_iter() | ||
93 | .filter_map(|item| match item { | ||
94 | hir::AssocItem::Const(const_item) => Some(const_item), | ||
95 | _ => None, | ||
96 | }) | ||
97 | { | ||
98 | add_const_impl(&trigger, acc, ctx, missing_fn); | ||
99 | } | ||
100 | } | ||
101 | |||
102 | _ => {} | ||
103 | } | ||
104 | } | ||
105 | } | ||
106 | |||
107 | fn completion_match(ctx: &CompletionContext) -> Option<(SyntaxNode, Impl)> { | ||
108 | let (trigger, impl_def_offset) = ctx.token.ancestors().find_map(|p| match p.kind() { | ||
109 | SyntaxKind::FN | SyntaxKind::TYPE_ALIAS | SyntaxKind::CONST | SyntaxKind::BLOCK_EXPR => { | ||
110 | Some((p, 2)) | ||
111 | } | ||
112 | SyntaxKind::NAME_REF => Some((p, 5)), | ||
113 | _ => None, | ||
114 | })?; | ||
115 | let impl_def = (0..impl_def_offset - 1) | ||
116 | .try_fold(trigger.parent()?, |t, _| t.parent()) | ||
117 | .and_then(ast::Impl::cast)?; | ||
118 | Some((trigger, impl_def)) | ||
119 | } | ||
120 | |||
121 | fn add_function_impl( | ||
122 | fn_def_node: &SyntaxNode, | ||
123 | acc: &mut Completions, | ||
124 | ctx: &CompletionContext, | ||
125 | func: hir::Function, | ||
126 | ) { | ||
127 | let fn_name = func.name(ctx.db).to_string(); | ||
128 | |||
129 | let label = if !func.params(ctx.db).is_empty() { | ||
130 | format!("fn {}(..)", fn_name) | ||
131 | } else { | ||
132 | format!("fn {}()", fn_name) | ||
133 | }; | ||
134 | |||
135 | let builder = CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label) | ||
136 | .lookup_by(fn_name) | ||
137 | .set_documentation(func.docs(ctx.db)); | ||
138 | |||
139 | let completion_kind = if func.has_self_param(ctx.db) { | ||
140 | CompletionItemKind::Method | ||
141 | } else { | ||
142 | CompletionItemKind::Function | ||
143 | }; | ||
144 | let range = TextRange::new(fn_def_node.text_range().start(), ctx.source_range().end()); | ||
145 | |||
146 | let function_decl = function_declaration(&func.source(ctx.db).value); | ||
147 | match ctx.config.snippet_cap { | ||
148 | Some(cap) => { | ||
149 | let snippet = format!("{} {{\n $0\n}}", function_decl); | ||
150 | builder.snippet_edit(cap, TextEdit::replace(range, snippet)) | ||
151 | } | ||
152 | None => { | ||
153 | let header = format!("{} {{", function_decl); | ||
154 | builder.text_edit(TextEdit::replace(range, header)) | ||
155 | } | ||
156 | } | ||
157 | .kind(completion_kind) | ||
158 | .add_to(acc); | ||
159 | } | ||
160 | |||
161 | fn add_type_alias_impl( | ||
162 | type_def_node: &SyntaxNode, | ||
163 | acc: &mut Completions, | ||
164 | ctx: &CompletionContext, | ||
165 | type_alias: hir::TypeAlias, | ||
166 | ) { | ||
167 | let alias_name = type_alias.name(ctx.db).to_string(); | ||
168 | |||
169 | let snippet = format!("type {} = ", alias_name); | ||
170 | |||
171 | let range = TextRange::new(type_def_node.text_range().start(), ctx.source_range().end()); | ||
172 | |||
173 | CompletionItem::new(CompletionKind::Magic, ctx.source_range(), snippet.clone()) | ||
174 | .text_edit(TextEdit::replace(range, snippet)) | ||
175 | .lookup_by(alias_name) | ||
176 | .kind(CompletionItemKind::TypeAlias) | ||
177 | .set_documentation(type_alias.docs(ctx.db)) | ||
178 | .add_to(acc); | ||
179 | } | ||
180 | |||
181 | fn add_const_impl( | ||
182 | const_def_node: &SyntaxNode, | ||
183 | acc: &mut Completions, | ||
184 | ctx: &CompletionContext, | ||
185 | const_: hir::Const, | ||
186 | ) { | ||
187 | let const_name = const_.name(ctx.db).map(|n| n.to_string()); | ||
188 | |||
189 | if let Some(const_name) = const_name { | ||
190 | let snippet = make_const_compl_syntax(&const_.source(ctx.db).value); | ||
191 | |||
192 | let range = TextRange::new(const_def_node.text_range().start(), ctx.source_range().end()); | ||
193 | |||
194 | CompletionItem::new(CompletionKind::Magic, ctx.source_range(), snippet.clone()) | ||
195 | .text_edit(TextEdit::replace(range, snippet)) | ||
196 | .lookup_by(const_name) | ||
197 | .kind(CompletionItemKind::Const) | ||
198 | .set_documentation(const_.docs(ctx.db)) | ||
199 | .add_to(acc); | ||
200 | } | ||
201 | } | ||
202 | |||
203 | fn make_const_compl_syntax(const_: &ast::Const) -> String { | ||
204 | let const_ = edit::remove_attrs_and_docs(const_); | ||
205 | |||
206 | let const_start = const_.syntax().text_range().start(); | ||
207 | let const_end = const_.syntax().text_range().end(); | ||
208 | |||
209 | let start = | ||
210 | const_.syntax().first_child_or_token().map_or(const_start, |f| f.text_range().start()); | ||
211 | |||
212 | let end = const_ | ||
213 | .syntax() | ||
214 | .children_with_tokens() | ||
215 | .find(|s| s.kind() == T![;] || s.kind() == T![=]) | ||
216 | .map_or(const_end, |f| f.text_range().start()); | ||
217 | |||
218 | let len = end - start; | ||
219 | let range = TextRange::new(0.into(), len); | ||
220 | |||
221 | let syntax = const_.syntax().text().slice(range).to_string(); | ||
222 | |||
223 | format!("{} = ", syntax.trim_end()) | ||
224 | } | ||
225 | |||
226 | #[cfg(test)] | ||
227 | mod tests { | ||
228 | use expect::{expect, Expect}; | ||
229 | |||
230 | use crate::completion::{ | ||
231 | test_utils::{check_edit, completion_list}, | ||
232 | CompletionKind, | ||
233 | }; | ||
234 | |||
235 | fn check(ra_fixture: &str, expect: Expect) { | ||
236 | let actual = completion_list(ra_fixture, CompletionKind::Magic); | ||
237 | expect.assert_eq(&actual) | ||
238 | } | ||
239 | |||
240 | #[test] | ||
241 | fn name_ref_function_type_const() { | ||
242 | check( | ||
243 | r#" | ||
244 | trait Test { | ||
245 | type TestType; | ||
246 | const TEST_CONST: u16; | ||
247 | fn test(); | ||
248 | } | ||
249 | struct T; | ||
250 | |||
251 | impl Test for T { | ||
252 | t<|> | ||
253 | } | ||
254 | "#, | ||
255 | expect![[" | ||
256 | ct const TEST_CONST: u16 = \n\ | ||
257 | fn fn test() | ||
258 | ta type TestType = \n\ | ||
259 | "]], | ||
260 | ); | ||
261 | } | ||
262 | |||
263 | #[test] | ||
264 | fn no_nested_fn_completions() { | ||
265 | check( | ||
266 | r" | ||
267 | trait Test { | ||
268 | fn test(); | ||
269 | fn test2(); | ||
270 | } | ||
271 | struct T; | ||
272 | |||
273 | impl Test for T { | ||
274 | fn test() { | ||
275 | t<|> | ||
276 | } | ||
277 | } | ||
278 | ", | ||
279 | expect![[""]], | ||
280 | ); | ||
281 | } | ||
282 | |||
283 | #[test] | ||
284 | fn name_ref_single_function() { | ||
285 | check_edit( | ||
286 | "test", | ||
287 | r#" | ||
288 | trait Test { | ||
289 | fn test(); | ||
290 | } | ||
291 | struct T; | ||
292 | |||
293 | impl Test for T { | ||
294 | t<|> | ||
295 | } | ||
296 | "#, | ||
297 | r#" | ||
298 | trait Test { | ||
299 | fn test(); | ||
300 | } | ||
301 | struct T; | ||
302 | |||
303 | impl Test for T { | ||
304 | fn test() { | ||
305 | $0 | ||
306 | } | ||
307 | } | ||
308 | "#, | ||
309 | ); | ||
310 | } | ||
311 | |||
312 | #[test] | ||
313 | fn single_function() { | ||
314 | check_edit( | ||
315 | "test", | ||
316 | r#" | ||
317 | trait Test { | ||
318 | fn test(); | ||
319 | } | ||
320 | struct T; | ||
321 | |||
322 | impl Test for T { | ||
323 | fn t<|> | ||
324 | } | ||
325 | "#, | ||
326 | r#" | ||
327 | trait Test { | ||
328 | fn test(); | ||
329 | } | ||
330 | struct T; | ||
331 | |||
332 | impl Test for T { | ||
333 | fn test() { | ||
334 | $0 | ||
335 | } | ||
336 | } | ||
337 | "#, | ||
338 | ); | ||
339 | } | ||
340 | |||
341 | #[test] | ||
342 | fn hide_implemented_fn() { | ||
343 | check( | ||
344 | r#" | ||
345 | trait Test { | ||
346 | fn foo(); | ||
347 | fn foo_bar(); | ||
348 | } | ||
349 | struct T; | ||
350 | |||
351 | impl Test for T { | ||
352 | fn foo() {} | ||
353 | fn f<|> | ||
354 | } | ||
355 | "#, | ||
356 | expect![[r#" | ||
357 | fn fn foo_bar() | ||
358 | "#]], | ||
359 | ); | ||
360 | } | ||
361 | |||
362 | #[test] | ||
363 | fn generic_fn() { | ||
364 | check_edit( | ||
365 | "foo", | ||
366 | r#" | ||
367 | trait Test { | ||
368 | fn foo<T>(); | ||
369 | } | ||
370 | struct T; | ||
371 | |||
372 | impl Test for T { | ||
373 | fn f<|> | ||
374 | } | ||
375 | "#, | ||
376 | r#" | ||
377 | trait Test { | ||
378 | fn foo<T>(); | ||
379 | } | ||
380 | struct T; | ||
381 | |||
382 | impl Test for T { | ||
383 | fn foo<T>() { | ||
384 | $0 | ||
385 | } | ||
386 | } | ||
387 | "#, | ||
388 | ); | ||
389 | check_edit( | ||
390 | "foo", | ||
391 | r#" | ||
392 | trait Test { | ||
393 | fn foo<T>() where T: Into<String>; | ||
394 | } | ||
395 | struct T; | ||
396 | |||
397 | impl Test for T { | ||
398 | fn f<|> | ||
399 | } | ||
400 | "#, | ||
401 | r#" | ||
402 | trait Test { | ||
403 | fn foo<T>() where T: Into<String>; | ||
404 | } | ||
405 | struct T; | ||
406 | |||
407 | impl Test for T { | ||
408 | fn foo<T>() | ||
409 | where T: Into<String> { | ||
410 | $0 | ||
411 | } | ||
412 | } | ||
413 | "#, | ||
414 | ); | ||
415 | } | ||
416 | |||
417 | #[test] | ||
418 | fn associated_type() { | ||
419 | check_edit( | ||
420 | "SomeType", | ||
421 | r#" | ||
422 | trait Test { | ||
423 | type SomeType; | ||
424 | } | ||
425 | |||
426 | impl Test for () { | ||
427 | type S<|> | ||
428 | } | ||
429 | "#, | ||
430 | " | ||
431 | trait Test { | ||
432 | type SomeType; | ||
433 | } | ||
434 | |||
435 | impl Test for () { | ||
436 | type SomeType = \n\ | ||
437 | } | ||
438 | ", | ||
439 | ); | ||
440 | } | ||
441 | |||
442 | #[test] | ||
443 | fn associated_const() { | ||
444 | check_edit( | ||
445 | "SOME_CONST", | ||
446 | r#" | ||
447 | trait Test { | ||
448 | const SOME_CONST: u16; | ||
449 | } | ||
450 | |||
451 | impl Test for () { | ||
452 | const S<|> | ||
453 | } | ||
454 | "#, | ||
455 | " | ||
456 | trait Test { | ||
457 | const SOME_CONST: u16; | ||
458 | } | ||
459 | |||
460 | impl Test for () { | ||
461 | const SOME_CONST: u16 = \n\ | ||
462 | } | ||
463 | ", | ||
464 | ); | ||
465 | |||
466 | check_edit( | ||
467 | "SOME_CONST", | ||
468 | r#" | ||
469 | trait Test { | ||
470 | const SOME_CONST: u16 = 92; | ||
471 | } | ||
472 | |||
473 | impl Test for () { | ||
474 | const S<|> | ||
475 | } | ||
476 | "#, | ||
477 | " | ||
478 | trait Test { | ||
479 | const SOME_CONST: u16 = 92; | ||
480 | } | ||
481 | |||
482 | impl Test for () { | ||
483 | const SOME_CONST: u16 = \n\ | ||
484 | } | ||
485 | ", | ||
486 | ); | ||
487 | } | ||
488 | } | ||
diff --git a/crates/ide/src/completion/complete_unqualified_path.rs b/crates/ide/src/completion/complete_unqualified_path.rs new file mode 100644 index 000000000..824227f31 --- /dev/null +++ b/crates/ide/src/completion/complete_unqualified_path.rs | |||
@@ -0,0 +1,658 @@ | |||
1 | //! Completion of names from the current scope, e.g. locals and imported items. | ||
2 | |||
3 | use hir::{Adt, ModuleDef, ScopeDef, Type}; | ||
4 | use syntax::AstNode; | ||
5 | use test_utils::mark; | ||
6 | |||
7 | use crate::completion::{CompletionContext, Completions}; | ||
8 | |||
9 | pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { | ||
10 | if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { | ||
11 | return; | ||
12 | } | ||
13 | if ctx.record_lit_syntax.is_some() | ||
14 | || ctx.record_pat_syntax.is_some() | ||
15 | || ctx.attribute_under_caret.is_some() | ||
16 | { | ||
17 | return; | ||
18 | } | ||
19 | |||
20 | if let Some(ty) = &ctx.expected_type { | ||
21 | complete_enum_variants(acc, ctx, ty); | ||
22 | } | ||
23 | |||
24 | if ctx.is_pat_binding_or_const { | ||
25 | return; | ||
26 | } | ||
27 | |||
28 | ctx.scope.process_all_names(&mut |name, res| { | ||
29 | if ctx.use_item_syntax.is_some() { | ||
30 | if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) { | ||
31 | if name_ref.syntax().text() == name.to_string().as_str() { | ||
32 | mark::hit!(self_fulfilling_completion); | ||
33 | return; | ||
34 | } | ||
35 | } | ||
36 | } | ||
37 | acc.add_resolution(ctx, name.to_string(), &res) | ||
38 | }); | ||
39 | } | ||
40 | |||
41 | fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) { | ||
42 | if let Some(Adt::Enum(enum_data)) = ty.as_adt() { | ||
43 | let variants = enum_data.variants(ctx.db); | ||
44 | |||
45 | let module = if let Some(module) = ctx.scope.module() { | ||
46 | // Compute path from the completion site if available. | ||
47 | module | ||
48 | } else { | ||
49 | // Otherwise fall back to the enum's definition site. | ||
50 | enum_data.module(ctx.db) | ||
51 | }; | ||
52 | |||
53 | for variant in variants { | ||
54 | if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) { | ||
55 | // Variants with trivial paths are already added by the existing completion logic, | ||
56 | // so we should avoid adding these twice | ||
57 | if path.segments.len() > 1 { | ||
58 | acc.add_qualified_enum_variant(ctx, variant, path); | ||
59 | } | ||
60 | } | ||
61 | } | ||
62 | } | ||
63 | } | ||
64 | |||
65 | #[cfg(test)] | ||
66 | mod tests { | ||
67 | use expect::{expect, Expect}; | ||
68 | use test_utils::mark; | ||
69 | |||
70 | use crate::completion::{ | ||
71 | test_utils::{check_edit, completion_list}, | ||
72 | CompletionKind, | ||
73 | }; | ||
74 | |||
75 | fn check(ra_fixture: &str, expect: Expect) { | ||
76 | let actual = completion_list(ra_fixture, CompletionKind::Reference); | ||
77 | expect.assert_eq(&actual) | ||
78 | } | ||
79 | |||
80 | #[test] | ||
81 | fn self_fulfilling_completion() { | ||
82 | mark::check!(self_fulfilling_completion); | ||
83 | check( | ||
84 | r#" | ||
85 | use foo<|> | ||
86 | use std::collections; | ||
87 | "#, | ||
88 | expect![[r#" | ||
89 | ?? collections | ||
90 | "#]], | ||
91 | ); | ||
92 | } | ||
93 | |||
94 | #[test] | ||
95 | fn bind_pat_and_path_ignore_at() { | ||
96 | check( | ||
97 | r#" | ||
98 | enum Enum { A, B } | ||
99 | fn quux(x: Option<Enum>) { | ||
100 | match x { | ||
101 | None => (), | ||
102 | Some(en<|> @ Enum::A) => (), | ||
103 | } | ||
104 | } | ||
105 | "#, | ||
106 | expect![[""]], | ||
107 | ); | ||
108 | } | ||
109 | |||
110 | #[test] | ||
111 | fn bind_pat_and_path_ignore_ref() { | ||
112 | check( | ||
113 | r#" | ||
114 | enum Enum { A, B } | ||
115 | fn quux(x: Option<Enum>) { | ||
116 | match x { | ||
117 | None => (), | ||
118 | Some(ref en<|>) => (), | ||
119 | } | ||
120 | } | ||
121 | "#, | ||
122 | expect![[""]], | ||
123 | ); | ||
124 | } | ||
125 | |||
126 | #[test] | ||
127 | fn bind_pat_and_path() { | ||
128 | check( | ||
129 | r#" | ||
130 | enum Enum { A, B } | ||
131 | fn quux(x: Option<Enum>) { | ||
132 | match x { | ||
133 | None => (), | ||
134 | Some(En<|>) => (), | ||
135 | } | ||
136 | } | ||
137 | "#, | ||
138 | expect![[r#" | ||
139 | en Enum | ||
140 | "#]], | ||
141 | ); | ||
142 | } | ||
143 | |||
144 | #[test] | ||
145 | fn completes_bindings_from_let() { | ||
146 | check( | ||
147 | r#" | ||
148 | fn quux(x: i32) { | ||
149 | let y = 92; | ||
150 | 1 + <|>; | ||
151 | let z = (); | ||
152 | } | ||
153 | "#, | ||
154 | expect![[r#" | ||
155 | fn quux(…) fn quux(x: i32) | ||
156 | bn x i32 | ||
157 | bn y i32 | ||
158 | "#]], | ||
159 | ); | ||
160 | } | ||
161 | |||
162 | #[test] | ||
163 | fn completes_bindings_from_if_let() { | ||
164 | check( | ||
165 | r#" | ||
166 | fn quux() { | ||
167 | if let Some(x) = foo() { | ||
168 | let y = 92; | ||
169 | }; | ||
170 | if let Some(a) = bar() { | ||
171 | let b = 62; | ||
172 | 1 + <|> | ||
173 | } | ||
174 | } | ||
175 | "#, | ||
176 | expect![[r#" | ||
177 | bn a | ||
178 | bn b i32 | ||
179 | fn quux() fn quux() | ||
180 | "#]], | ||
181 | ); | ||
182 | } | ||
183 | |||
184 | #[test] | ||
185 | fn completes_bindings_from_for() { | ||
186 | check( | ||
187 | r#" | ||
188 | fn quux() { | ||
189 | for x in &[1, 2, 3] { <|> } | ||
190 | } | ||
191 | "#, | ||
192 | expect![[r#" | ||
193 | fn quux() fn quux() | ||
194 | bn x | ||
195 | "#]], | ||
196 | ); | ||
197 | } | ||
198 | |||
199 | #[test] | ||
200 | fn completes_if_prefix_is_keyword() { | ||
201 | mark::check!(completes_if_prefix_is_keyword); | ||
202 | check_edit( | ||
203 | "wherewolf", | ||
204 | r#" | ||
205 | fn main() { | ||
206 | let wherewolf = 92; | ||
207 | drop(where<|>) | ||
208 | } | ||
209 | "#, | ||
210 | r#" | ||
211 | fn main() { | ||
212 | let wherewolf = 92; | ||
213 | drop(wherewolf) | ||
214 | } | ||
215 | "#, | ||
216 | ) | ||
217 | } | ||
218 | |||
219 | #[test] | ||
220 | fn completes_generic_params() { | ||
221 | check( | ||
222 | r#"fn quux<T>() { <|> }"#, | ||
223 | expect![[r#" | ||
224 | tp T | ||
225 | fn quux() fn quux<T>() | ||
226 | "#]], | ||
227 | ); | ||
228 | } | ||
229 | |||
230 | #[test] | ||
231 | fn completes_generic_params_in_struct() { | ||
232 | check( | ||
233 | r#"struct S<T> { x: <|>}"#, | ||
234 | expect![[r#" | ||
235 | st S<…> | ||
236 | tp Self | ||
237 | tp T | ||
238 | "#]], | ||
239 | ); | ||
240 | } | ||
241 | |||
242 | #[test] | ||
243 | fn completes_self_in_enum() { | ||
244 | check( | ||
245 | r#"enum X { Y(<|>) }"#, | ||
246 | expect![[r#" | ||
247 | tp Self | ||
248 | en X | ||
249 | "#]], | ||
250 | ); | ||
251 | } | ||
252 | |||
253 | #[test] | ||
254 | fn completes_module_items() { | ||
255 | check( | ||
256 | r#" | ||
257 | struct S; | ||
258 | enum E {} | ||
259 | fn quux() { <|> } | ||
260 | "#, | ||
261 | expect![[r#" | ||
262 | en E | ||
263 | st S | ||
264 | fn quux() fn quux() | ||
265 | "#]], | ||
266 | ); | ||
267 | } | ||
268 | |||
269 | #[test] | ||
270 | fn completes_extern_prelude() { | ||
271 | check( | ||
272 | r#" | ||
273 | //- /lib.rs | ||
274 | use <|>; | ||
275 | |||
276 | //- /other_crate/lib.rs | ||
277 | // nothing here | ||
278 | "#, | ||
279 | expect![[r#" | ||
280 | md other_crate | ||
281 | "#]], | ||
282 | ); | ||
283 | } | ||
284 | |||
285 | #[test] | ||
286 | fn completes_module_items_in_nested_modules() { | ||
287 | check( | ||
288 | r#" | ||
289 | struct Foo; | ||
290 | mod m { | ||
291 | struct Bar; | ||
292 | fn quux() { <|> } | ||
293 | } | ||
294 | "#, | ||
295 | expect![[r#" | ||
296 | st Bar | ||
297 | fn quux() fn quux() | ||
298 | "#]], | ||
299 | ); | ||
300 | } | ||
301 | |||
302 | #[test] | ||
303 | fn completes_return_type() { | ||
304 | check( | ||
305 | r#" | ||
306 | struct Foo; | ||
307 | fn x() -> <|> | ||
308 | "#, | ||
309 | expect![[r#" | ||
310 | st Foo | ||
311 | fn x() fn x() | ||
312 | "#]], | ||
313 | ); | ||
314 | } | ||
315 | |||
316 | #[test] | ||
317 | fn dont_show_both_completions_for_shadowing() { | ||
318 | check( | ||
319 | r#" | ||
320 | fn foo() { | ||
321 | let bar = 92; | ||
322 | { | ||
323 | let bar = 62; | ||
324 | drop(<|>) | ||
325 | } | ||
326 | } | ||
327 | "#, | ||
328 | // FIXME: should be only one bar here | ||
329 | expect![[r#" | ||
330 | bn bar i32 | ||
331 | bn bar i32 | ||
332 | fn foo() fn foo() | ||
333 | "#]], | ||
334 | ); | ||
335 | } | ||
336 | |||
337 | #[test] | ||
338 | fn completes_self_in_methods() { | ||
339 | check( | ||
340 | r#"impl S { fn foo(&self) { <|> } }"#, | ||
341 | expect![[r#" | ||
342 | tp Self | ||
343 | bn self &{unknown} | ||
344 | "#]], | ||
345 | ); | ||
346 | } | ||
347 | |||
348 | #[test] | ||
349 | fn completes_prelude() { | ||
350 | check( | ||
351 | r#" | ||
352 | //- /main.rs | ||
353 | fn foo() { let x: <|> } | ||
354 | |||
355 | //- /std/lib.rs | ||
356 | #[prelude_import] | ||
357 | use prelude::*; | ||
358 | |||
359 | mod prelude { struct Option; } | ||
360 | "#, | ||
361 | expect![[r#" | ||
362 | st Option | ||
363 | fn foo() fn foo() | ||
364 | md std | ||
365 | "#]], | ||
366 | ); | ||
367 | } | ||
368 | |||
369 | #[test] | ||
370 | fn completes_std_prelude_if_core_is_defined() { | ||
371 | check( | ||
372 | r#" | ||
373 | //- /main.rs | ||
374 | fn foo() { let x: <|> } | ||
375 | |||
376 | //- /core/lib.rs | ||
377 | #[prelude_import] | ||
378 | use prelude::*; | ||
379 | |||
380 | mod prelude { struct Option; } | ||
381 | |||
382 | //- /std/lib.rs | ||
383 | #[prelude_import] | ||
384 | use prelude::*; | ||
385 | |||
386 | mod prelude { struct String; } | ||
387 | "#, | ||
388 | expect![[r#" | ||
389 | st String | ||
390 | md core | ||
391 | fn foo() fn foo() | ||
392 | md std | ||
393 | "#]], | ||
394 | ); | ||
395 | } | ||
396 | |||
397 | #[test] | ||
398 | fn completes_macros_as_value() { | ||
399 | check( | ||
400 | r#" | ||
401 | macro_rules! foo { () => {} } | ||
402 | |||
403 | #[macro_use] | ||
404 | mod m1 { | ||
405 | macro_rules! bar { () => {} } | ||
406 | } | ||
407 | |||
408 | mod m2 { | ||
409 | macro_rules! nope { () => {} } | ||
410 | |||
411 | #[macro_export] | ||
412 | macro_rules! baz { () => {} } | ||
413 | } | ||
414 | |||
415 | fn main() { let v = <|> } | ||
416 | "#, | ||
417 | expect![[r##" | ||
418 | ma bar!(…) macro_rules! bar | ||
419 | ma baz!(…) #[macro_export] | ||
420 | macro_rules! baz | ||
421 | ma foo!(…) macro_rules! foo | ||
422 | md m1 | ||
423 | md m2 | ||
424 | fn main() fn main() | ||
425 | "##]], | ||
426 | ); | ||
427 | } | ||
428 | |||
429 | #[test] | ||
430 | fn completes_both_macro_and_value() { | ||
431 | check( | ||
432 | r#" | ||
433 | macro_rules! foo { () => {} } | ||
434 | fn foo() { <|> } | ||
435 | "#, | ||
436 | expect![[r#" | ||
437 | ma foo!(…) macro_rules! foo | ||
438 | fn foo() fn foo() | ||
439 | "#]], | ||
440 | ); | ||
441 | } | ||
442 | |||
443 | #[test] | ||
444 | fn completes_macros_as_type() { | ||
445 | check( | ||
446 | r#" | ||
447 | macro_rules! foo { () => {} } | ||
448 | fn main() { let x: <|> } | ||
449 | "#, | ||
450 | expect![[r#" | ||
451 | ma foo!(…) macro_rules! foo | ||
452 | fn main() fn main() | ||
453 | "#]], | ||
454 | ); | ||
455 | } | ||
456 | |||
457 | #[test] | ||
458 | fn completes_macros_as_stmt() { | ||
459 | check( | ||
460 | r#" | ||
461 | macro_rules! foo { () => {} } | ||
462 | fn main() { <|> } | ||
463 | "#, | ||
464 | expect![[r#" | ||
465 | ma foo!(…) macro_rules! foo | ||
466 | fn main() fn main() | ||
467 | "#]], | ||
468 | ); | ||
469 | } | ||
470 | |||
471 | #[test] | ||
472 | fn completes_local_item() { | ||
473 | check( | ||
474 | r#" | ||
475 | fn main() { | ||
476 | return f<|>; | ||
477 | fn frobnicate() {} | ||
478 | } | ||
479 | "#, | ||
480 | expect![[r#" | ||
481 | fn frobnicate() fn frobnicate() | ||
482 | fn main() fn main() | ||
483 | "#]], | ||
484 | ); | ||
485 | } | ||
486 | |||
487 | #[test] | ||
488 | fn completes_in_simple_macro_1() { | ||
489 | check( | ||
490 | r#" | ||
491 | macro_rules! m { ($e:expr) => { $e } } | ||
492 | fn quux(x: i32) { | ||
493 | let y = 92; | ||
494 | m!(<|>); | ||
495 | } | ||
496 | "#, | ||
497 | expect![[r#" | ||
498 | ma m!(…) macro_rules! m | ||
499 | fn quux(…) fn quux(x: i32) | ||
500 | bn x i32 | ||
501 | bn y i32 | ||
502 | "#]], | ||
503 | ); | ||
504 | } | ||
505 | |||
506 | #[test] | ||
507 | fn completes_in_simple_macro_2() { | ||
508 | check( | ||
509 | r" | ||
510 | macro_rules! m { ($e:expr) => { $e } } | ||
511 | fn quux(x: i32) { | ||
512 | let y = 92; | ||
513 | m!(x<|>); | ||
514 | } | ||
515 | ", | ||
516 | expect![[r#" | ||
517 | ma m!(…) macro_rules! m | ||
518 | fn quux(…) fn quux(x: i32) | ||
519 | bn x i32 | ||
520 | bn y i32 | ||
521 | "#]], | ||
522 | ); | ||
523 | } | ||
524 | |||
525 | #[test] | ||
526 | fn completes_in_simple_macro_without_closing_parens() { | ||
527 | check( | ||
528 | r#" | ||
529 | macro_rules! m { ($e:expr) => { $e } } | ||
530 | fn quux(x: i32) { | ||
531 | let y = 92; | ||
532 | m!(x<|> | ||
533 | } | ||
534 | "#, | ||
535 | expect![[r#" | ||
536 | ma m!(…) macro_rules! m | ||
537 | fn quux(…) fn quux(x: i32) | ||
538 | bn x i32 | ||
539 | bn y i32 | ||
540 | "#]], | ||
541 | ); | ||
542 | } | ||
543 | |||
544 | #[test] | ||
545 | fn completes_unresolved_uses() { | ||
546 | check( | ||
547 | r#" | ||
548 | use spam::Quux; | ||
549 | |||
550 | fn main() { <|> } | ||
551 | "#, | ||
552 | expect![[r#" | ||
553 | ?? Quux | ||
554 | fn main() fn main() | ||
555 | "#]], | ||
556 | ); | ||
557 | } | ||
558 | #[test] | ||
559 | fn completes_enum_variant_matcharm() { | ||
560 | check( | ||
561 | r#" | ||
562 | enum Foo { Bar, Baz, Quux } | ||
563 | |||
564 | fn main() { | ||
565 | let foo = Foo::Quux; | ||
566 | match foo { Qu<|> } | ||
567 | } | ||
568 | "#, | ||
569 | expect![[r#" | ||
570 | en Foo | ||
571 | ev Foo::Bar () | ||
572 | ev Foo::Baz () | ||
573 | ev Foo::Quux () | ||
574 | "#]], | ||
575 | ) | ||
576 | } | ||
577 | |||
578 | #[test] | ||
579 | fn completes_enum_variant_iflet() { | ||
580 | check( | ||
581 | r#" | ||
582 | enum Foo { Bar, Baz, Quux } | ||
583 | |||
584 | fn main() { | ||
585 | let foo = Foo::Quux; | ||
586 | if let Qu<|> = foo { } | ||
587 | } | ||
588 | "#, | ||
589 | expect![[r#" | ||
590 | en Foo | ||
591 | ev Foo::Bar () | ||
592 | ev Foo::Baz () | ||
593 | ev Foo::Quux () | ||
594 | "#]], | ||
595 | ) | ||
596 | } | ||
597 | |||
598 | #[test] | ||
599 | fn completes_enum_variant_basic_expr() { | ||
600 | check( | ||
601 | r#" | ||
602 | enum Foo { Bar, Baz, Quux } | ||
603 | fn main() { let foo: Foo = Q<|> } | ||
604 | "#, | ||
605 | expect![[r#" | ||
606 | en Foo | ||
607 | ev Foo::Bar () | ||
608 | ev Foo::Baz () | ||
609 | ev Foo::Quux () | ||
610 | fn main() fn main() | ||
611 | "#]], | ||
612 | ) | ||
613 | } | ||
614 | |||
615 | #[test] | ||
616 | fn completes_enum_variant_from_module() { | ||
617 | check( | ||
618 | r#" | ||
619 | mod m { pub enum E { V } } | ||
620 | fn f() -> m::E { V<|> } | ||
621 | "#, | ||
622 | expect![[r#" | ||
623 | fn f() fn f() -> m::E | ||
624 | md m | ||
625 | ev m::E::V () | ||
626 | "#]], | ||
627 | ) | ||
628 | } | ||
629 | |||
630 | #[test] | ||
631 | fn dont_complete_attr() { | ||
632 | check( | ||
633 | r#" | ||
634 | struct Foo; | ||
635 | #[<|>] | ||
636 | fn f() {} | ||
637 | "#, | ||
638 | expect![[""]], | ||
639 | ) | ||
640 | } | ||
641 | |||
642 | #[test] | ||
643 | fn completes_type_or_trait_in_impl_block() { | ||
644 | check( | ||
645 | r#" | ||
646 | trait MyTrait {} | ||
647 | struct MyStruct {} | ||
648 | |||
649 | impl My<|> | ||
650 | "#, | ||
651 | expect![[r#" | ||
652 | st MyStruct | ||
653 | tt MyTrait | ||
654 | tp Self | ||
655 | "#]], | ||
656 | ) | ||
657 | } | ||
658 | } | ||
diff --git a/crates/ide/src/completion/completion_config.rs b/crates/ide/src/completion/completion_config.rs new file mode 100644 index 000000000..71b49ace8 --- /dev/null +++ b/crates/ide/src/completion/completion_config.rs | |||
@@ -0,0 +1,35 @@ | |||
1 | //! Settings for tweaking completion. | ||
2 | //! | ||
3 | //! The fun thing here is `SnippetCap` -- this type can only be created in this | ||
4 | //! module, and we use to statically check that we only produce snippet | ||
5 | //! completions if we are allowed to. | ||
6 | |||
7 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
8 | pub struct CompletionConfig { | ||
9 | pub enable_postfix_completions: bool, | ||
10 | pub add_call_parenthesis: bool, | ||
11 | pub add_call_argument_snippets: bool, | ||
12 | pub snippet_cap: Option<SnippetCap>, | ||
13 | } | ||
14 | |||
15 | impl CompletionConfig { | ||
16 | pub fn allow_snippets(&mut self, yes: bool) { | ||
17 | self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None } | ||
18 | } | ||
19 | } | ||
20 | |||
21 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
22 | pub struct SnippetCap { | ||
23 | _private: (), | ||
24 | } | ||
25 | |||
26 | impl Default for CompletionConfig { | ||
27 | fn default() -> Self { | ||
28 | CompletionConfig { | ||
29 | enable_postfix_completions: true, | ||
30 | add_call_parenthesis: true, | ||
31 | add_call_argument_snippets: true, | ||
32 | snippet_cap: Some(SnippetCap { _private: () }), | ||
33 | } | ||
34 | } | ||
35 | } | ||
diff --git a/crates/ide/src/completion/completion_context.rs b/crates/ide/src/completion/completion_context.rs new file mode 100644 index 000000000..85456a66f --- /dev/null +++ b/crates/ide/src/completion/completion_context.rs | |||
@@ -0,0 +1,486 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use base_db::SourceDatabase; | ||
4 | use hir::{Semantics, SemanticsScope, Type}; | ||
5 | use ide_db::RootDatabase; | ||
6 | use syntax::{ | ||
7 | algo::{find_covering_element, find_node_at_offset}, | ||
8 | ast, match_ast, AstNode, NodeOrToken, | ||
9 | SyntaxKind::*, | ||
10 | SyntaxNode, SyntaxToken, TextRange, TextSize, | ||
11 | }; | ||
12 | use test_utils::mark; | ||
13 | use text_edit::Indel; | ||
14 | |||
15 | use crate::{ | ||
16 | call_info::ActiveParameter, | ||
17 | completion::{ | ||
18 | patterns::{ | ||
19 | has_bind_pat_parent, has_block_expr_parent, has_impl_as_prev_sibling, has_impl_parent, | ||
20 | has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling, | ||
21 | has_trait_parent, if_is_prev, is_in_loop_body, is_match_arm, unsafe_is_prev, | ||
22 | }, | ||
23 | CompletionConfig, | ||
24 | }, | ||
25 | FilePosition, | ||
26 | }; | ||
27 | |||
28 | /// `CompletionContext` is created early during completion to figure out, where | ||
29 | /// exactly is the cursor, syntax-wise. | ||
30 | #[derive(Debug)] | ||
31 | pub(crate) struct CompletionContext<'a> { | ||
32 | pub(super) sema: Semantics<'a, RootDatabase>, | ||
33 | pub(super) scope: SemanticsScope<'a>, | ||
34 | pub(super) db: &'a RootDatabase, | ||
35 | pub(super) config: &'a CompletionConfig, | ||
36 | pub(super) position: FilePosition, | ||
37 | /// The token before the cursor, in the original file. | ||
38 | pub(super) original_token: SyntaxToken, | ||
39 | /// The token before the cursor, in the macro-expanded file. | ||
40 | pub(super) token: SyntaxToken, | ||
41 | pub(super) krate: Option<hir::Crate>, | ||
42 | pub(super) expected_type: Option<Type>, | ||
43 | pub(super) name_ref_syntax: Option<ast::NameRef>, | ||
44 | pub(super) function_syntax: Option<ast::Fn>, | ||
45 | pub(super) use_item_syntax: Option<ast::Use>, | ||
46 | pub(super) record_lit_syntax: Option<ast::RecordExpr>, | ||
47 | pub(super) record_pat_syntax: Option<ast::RecordPat>, | ||
48 | pub(super) record_field_syntax: Option<ast::RecordExprField>, | ||
49 | pub(super) impl_def: Option<ast::Impl>, | ||
50 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong | ||
51 | pub(super) active_parameter: Option<ActiveParameter>, | ||
52 | pub(super) is_param: bool, | ||
53 | /// If a name-binding or reference to a const in a pattern. | ||
54 | /// Irrefutable patterns (like let) are excluded. | ||
55 | pub(super) is_pat_binding_or_const: bool, | ||
56 | /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. | ||
57 | pub(super) is_trivial_path: bool, | ||
58 | /// If not a trivial path, the prefix (qualifier). | ||
59 | pub(super) path_qual: Option<ast::Path>, | ||
60 | pub(super) after_if: bool, | ||
61 | /// `true` if we are a statement or a last expr in the block. | ||
62 | pub(super) can_be_stmt: bool, | ||
63 | /// `true` if we expect an expression at the cursor position. | ||
64 | pub(super) is_expr: bool, | ||
65 | /// Something is typed at the "top" level, in module or impl/trait. | ||
66 | pub(super) is_new_item: bool, | ||
67 | /// The receiver if this is a field or method access, i.e. writing something.<|> | ||
68 | pub(super) dot_receiver: Option<ast::Expr>, | ||
69 | pub(super) dot_receiver_is_ambiguous_float_literal: bool, | ||
70 | /// If this is a call (method or function) in particular, i.e. the () are already there. | ||
71 | pub(super) is_call: bool, | ||
72 | /// Like `is_call`, but for tuple patterns. | ||
73 | pub(super) is_pattern_call: bool, | ||
74 | /// If this is a macro call, i.e. the () are already there. | ||
75 | pub(super) is_macro_call: bool, | ||
76 | pub(super) is_path_type: bool, | ||
77 | pub(super) has_type_args: bool, | ||
78 | pub(super) attribute_under_caret: Option<ast::Attr>, | ||
79 | pub(super) unsafe_is_prev: bool, | ||
80 | pub(super) if_is_prev: bool, | ||
81 | pub(super) block_expr_parent: bool, | ||
82 | pub(super) bind_pat_parent: bool, | ||
83 | pub(super) ref_pat_parent: bool, | ||
84 | pub(super) in_loop_body: bool, | ||
85 | pub(super) has_trait_parent: bool, | ||
86 | pub(super) has_impl_parent: bool, | ||
87 | pub(super) trait_as_prev_sibling: bool, | ||
88 | pub(super) impl_as_prev_sibling: bool, | ||
89 | pub(super) is_match_arm: bool, | ||
90 | pub(super) has_item_list_or_source_file_parent: bool, | ||
91 | } | ||
92 | |||
93 | impl<'a> CompletionContext<'a> { | ||
94 | pub(super) fn new( | ||
95 | db: &'a RootDatabase, | ||
96 | position: FilePosition, | ||
97 | config: &'a CompletionConfig, | ||
98 | ) -> Option<CompletionContext<'a>> { | ||
99 | let sema = Semantics::new(db); | ||
100 | |||
101 | let original_file = sema.parse(position.file_id); | ||
102 | |||
103 | // Insert a fake ident to get a valid parse tree. We will use this file | ||
104 | // to determine context, though the original_file will be used for | ||
105 | // actual completion. | ||
106 | let file_with_fake_ident = { | ||
107 | let parse = db.parse(position.file_id); | ||
108 | let edit = Indel::insert(position.offset, "intellijRulezz".to_string()); | ||
109 | parse.reparse(&edit).tree() | ||
110 | }; | ||
111 | let fake_ident_token = | ||
112 | file_with_fake_ident.syntax().token_at_offset(position.offset).right_biased().unwrap(); | ||
113 | |||
114 | let krate = sema.to_module_def(position.file_id).map(|m| m.krate()); | ||
115 | let original_token = | ||
116 | original_file.syntax().token_at_offset(position.offset).left_biased()?; | ||
117 | let token = sema.descend_into_macros(original_token.clone()); | ||
118 | let scope = sema.scope_at_offset(&token.parent(), position.offset); | ||
119 | let mut ctx = CompletionContext { | ||
120 | sema, | ||
121 | scope, | ||
122 | db, | ||
123 | config, | ||
124 | original_token, | ||
125 | token, | ||
126 | position, | ||
127 | krate, | ||
128 | expected_type: None, | ||
129 | name_ref_syntax: None, | ||
130 | function_syntax: None, | ||
131 | use_item_syntax: None, | ||
132 | record_lit_syntax: None, | ||
133 | record_pat_syntax: None, | ||
134 | record_field_syntax: None, | ||
135 | impl_def: None, | ||
136 | active_parameter: ActiveParameter::at(db, position), | ||
137 | is_param: false, | ||
138 | is_pat_binding_or_const: false, | ||
139 | is_trivial_path: false, | ||
140 | path_qual: None, | ||
141 | after_if: false, | ||
142 | can_be_stmt: false, | ||
143 | is_expr: false, | ||
144 | is_new_item: false, | ||
145 | dot_receiver: None, | ||
146 | is_call: false, | ||
147 | is_pattern_call: false, | ||
148 | is_macro_call: false, | ||
149 | is_path_type: false, | ||
150 | has_type_args: false, | ||
151 | dot_receiver_is_ambiguous_float_literal: false, | ||
152 | attribute_under_caret: None, | ||
153 | unsafe_is_prev: false, | ||
154 | in_loop_body: false, | ||
155 | ref_pat_parent: false, | ||
156 | bind_pat_parent: false, | ||
157 | block_expr_parent: false, | ||
158 | has_trait_parent: false, | ||
159 | has_impl_parent: false, | ||
160 | trait_as_prev_sibling: false, | ||
161 | impl_as_prev_sibling: false, | ||
162 | if_is_prev: false, | ||
163 | is_match_arm: false, | ||
164 | has_item_list_or_source_file_parent: false, | ||
165 | }; | ||
166 | |||
167 | let mut original_file = original_file.syntax().clone(); | ||
168 | let mut hypothetical_file = file_with_fake_ident.syntax().clone(); | ||
169 | let mut offset = position.offset; | ||
170 | let mut fake_ident_token = fake_ident_token; | ||
171 | |||
172 | // Are we inside a macro call? | ||
173 | while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = ( | ||
174 | find_node_at_offset::<ast::MacroCall>(&original_file, offset), | ||
175 | find_node_at_offset::<ast::MacroCall>(&hypothetical_file, offset), | ||
176 | ) { | ||
177 | if actual_macro_call.path().as_ref().map(|s| s.syntax().text()) | ||
178 | != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text()) | ||
179 | { | ||
180 | break; | ||
181 | } | ||
182 | let hypothetical_args = match macro_call_with_fake_ident.token_tree() { | ||
183 | Some(tt) => tt, | ||
184 | None => break, | ||
185 | }; | ||
186 | if let (Some(actual_expansion), Some(hypothetical_expansion)) = ( | ||
187 | ctx.sema.expand(&actual_macro_call), | ||
188 | ctx.sema.speculative_expand( | ||
189 | &actual_macro_call, | ||
190 | &hypothetical_args, | ||
191 | fake_ident_token, | ||
192 | ), | ||
193 | ) { | ||
194 | let new_offset = hypothetical_expansion.1.text_range().start(); | ||
195 | if new_offset > actual_expansion.text_range().end() { | ||
196 | break; | ||
197 | } | ||
198 | original_file = actual_expansion; | ||
199 | hypothetical_file = hypothetical_expansion.0; | ||
200 | fake_ident_token = hypothetical_expansion.1; | ||
201 | offset = new_offset; | ||
202 | } else { | ||
203 | break; | ||
204 | } | ||
205 | } | ||
206 | ctx.fill_keyword_patterns(&hypothetical_file, offset); | ||
207 | ctx.fill(&original_file, hypothetical_file, offset); | ||
208 | Some(ctx) | ||
209 | } | ||
210 | |||
211 | // The range of the identifier that is being completed. | ||
212 | pub(crate) fn source_range(&self) -> TextRange { | ||
213 | // check kind of macro-expanded token, but use range of original token | ||
214 | if self.token.kind() == IDENT || self.token.kind().is_keyword() { | ||
215 | mark::hit!(completes_if_prefix_is_keyword); | ||
216 | self.original_token.text_range() | ||
217 | } else { | ||
218 | TextRange::empty(self.position.offset) | ||
219 | } | ||
220 | } | ||
221 | |||
222 | fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { | ||
223 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); | ||
224 | let syntax_element = NodeOrToken::Token(fake_ident_token); | ||
225 | self.block_expr_parent = has_block_expr_parent(syntax_element.clone()); | ||
226 | self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone()); | ||
227 | self.if_is_prev = if_is_prev(syntax_element.clone()); | ||
228 | self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone()); | ||
229 | self.ref_pat_parent = has_ref_parent(syntax_element.clone()); | ||
230 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); | ||
231 | self.has_trait_parent = has_trait_parent(syntax_element.clone()); | ||
232 | self.has_impl_parent = has_impl_parent(syntax_element.clone()); | ||
233 | self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone()); | ||
234 | self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); | ||
235 | self.is_match_arm = is_match_arm(syntax_element.clone()); | ||
236 | self.has_item_list_or_source_file_parent = | ||
237 | has_item_list_or_source_file_parent(syntax_element); | ||
238 | } | ||
239 | |||
240 | fn fill( | ||
241 | &mut self, | ||
242 | original_file: &SyntaxNode, | ||
243 | file_with_fake_ident: SyntaxNode, | ||
244 | offset: TextSize, | ||
245 | ) { | ||
246 | // FIXME: this is wrong in at least two cases: | ||
247 | // * when there's no token `foo(<|>)` | ||
248 | // * when there is a token, but it happens to have type of it's own | ||
249 | self.expected_type = self | ||
250 | .token | ||
251 | .ancestors() | ||
252 | .find_map(|node| { | ||
253 | let ty = match_ast! { | ||
254 | match node { | ||
255 | ast::Pat(it) => self.sema.type_of_pat(&it), | ||
256 | ast::Expr(it) => self.sema.type_of_expr(&it), | ||
257 | _ => return None, | ||
258 | } | ||
259 | }; | ||
260 | Some(ty) | ||
261 | }) | ||
262 | .flatten(); | ||
263 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); | ||
264 | |||
265 | // First, let's try to complete a reference to some declaration. | ||
266 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { | ||
267 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. | ||
268 | // See RFC#1685. | ||
269 | if is_node::<ast::Param>(name_ref.syntax()) { | ||
270 | self.is_param = true; | ||
271 | return; | ||
272 | } | ||
273 | // FIXME: remove this (V) duplication and make the check more precise | ||
274 | if name_ref.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() { | ||
275 | self.record_pat_syntax = | ||
276 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | ||
277 | } | ||
278 | self.classify_name_ref(original_file, name_ref, offset); | ||
279 | } | ||
280 | |||
281 | // Otherwise, see if this is a declaration. We can use heuristics to | ||
282 | // suggest declaration names, see `CompletionKind::Magic`. | ||
283 | if let Some(name) = find_node_at_offset::<ast::Name>(&file_with_fake_ident, offset) { | ||
284 | if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::IdentPat::cast) { | ||
285 | self.is_pat_binding_or_const = true; | ||
286 | if bind_pat.at_token().is_some() | ||
287 | || bind_pat.ref_token().is_some() | ||
288 | || bind_pat.mut_token().is_some() | ||
289 | { | ||
290 | self.is_pat_binding_or_const = false; | ||
291 | } | ||
292 | if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() { | ||
293 | self.is_pat_binding_or_const = false; | ||
294 | } | ||
295 | if let Some(let_stmt) = bind_pat.syntax().ancestors().find_map(ast::LetStmt::cast) { | ||
296 | if let Some(pat) = let_stmt.pat() { | ||
297 | if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) | ||
298 | { | ||
299 | self.is_pat_binding_or_const = false; | ||
300 | } | ||
301 | } | ||
302 | } | ||
303 | } | ||
304 | if is_node::<ast::Param>(name.syntax()) { | ||
305 | self.is_param = true; | ||
306 | return; | ||
307 | } | ||
308 | // FIXME: remove this (^) duplication and make the check more precise | ||
309 | if name.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() { | ||
310 | self.record_pat_syntax = | ||
311 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | ||
312 | } | ||
313 | } | ||
314 | } | ||
315 | |||
316 | fn classify_name_ref( | ||
317 | &mut self, | ||
318 | original_file: &SyntaxNode, | ||
319 | name_ref: ast::NameRef, | ||
320 | offset: TextSize, | ||
321 | ) { | ||
322 | self.name_ref_syntax = | ||
323 | find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); | ||
324 | let name_range = name_ref.syntax().text_range(); | ||
325 | if ast::RecordExprField::for_field_name(&name_ref).is_some() { | ||
326 | self.record_lit_syntax = | ||
327 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | ||
328 | } | ||
329 | |||
330 | self.impl_def = self | ||
331 | .sema | ||
332 | .ancestors_with_macros(self.token.parent()) | ||
333 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | ||
334 | .find_map(ast::Impl::cast); | ||
335 | |||
336 | let top_node = name_ref | ||
337 | .syntax() | ||
338 | .ancestors() | ||
339 | .take_while(|it| it.text_range() == name_range) | ||
340 | .last() | ||
341 | .unwrap(); | ||
342 | |||
343 | match top_node.parent().map(|it| it.kind()) { | ||
344 | Some(SOURCE_FILE) | Some(ITEM_LIST) => { | ||
345 | self.is_new_item = true; | ||
346 | return; | ||
347 | } | ||
348 | _ => (), | ||
349 | } | ||
350 | |||
351 | self.use_item_syntax = | ||
352 | self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::Use::cast); | ||
353 | |||
354 | self.function_syntax = self | ||
355 | .sema | ||
356 | .ancestors_with_macros(self.token.parent()) | ||
357 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | ||
358 | .find_map(ast::Fn::cast); | ||
359 | |||
360 | self.record_field_syntax = self | ||
361 | .sema | ||
362 | .ancestors_with_macros(self.token.parent()) | ||
363 | .take_while(|it| { | ||
364 | it.kind() != SOURCE_FILE && it.kind() != MODULE && it.kind() != CALL_EXPR | ||
365 | }) | ||
366 | .find_map(ast::RecordExprField::cast); | ||
367 | |||
368 | let parent = match name_ref.syntax().parent() { | ||
369 | Some(it) => it, | ||
370 | None => return, | ||
371 | }; | ||
372 | |||
373 | if let Some(segment) = ast::PathSegment::cast(parent.clone()) { | ||
374 | let path = segment.parent_path(); | ||
375 | self.is_call = path | ||
376 | .syntax() | ||
377 | .parent() | ||
378 | .and_then(ast::PathExpr::cast) | ||
379 | .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) | ||
380 | .is_some(); | ||
381 | self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some(); | ||
382 | self.is_pattern_call = | ||
383 | path.syntax().parent().and_then(ast::TupleStructPat::cast).is_some(); | ||
384 | |||
385 | self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); | ||
386 | self.has_type_args = segment.generic_arg_list().is_some(); | ||
387 | |||
388 | if let Some(path) = path_or_use_tree_qualifier(&path) { | ||
389 | self.path_qual = path | ||
390 | .segment() | ||
391 | .and_then(|it| { | ||
392 | find_node_with_range::<ast::PathSegment>( | ||
393 | original_file, | ||
394 | it.syntax().text_range(), | ||
395 | ) | ||
396 | }) | ||
397 | .map(|it| it.parent_path()); | ||
398 | return; | ||
399 | } | ||
400 | |||
401 | if let Some(segment) = path.segment() { | ||
402 | if segment.coloncolon_token().is_some() { | ||
403 | return; | ||
404 | } | ||
405 | } | ||
406 | |||
407 | self.is_trivial_path = true; | ||
408 | |||
409 | // Find either enclosing expr statement (thing with `;`) or a | ||
410 | // block. If block, check that we are the last expr. | ||
411 | self.can_be_stmt = name_ref | ||
412 | .syntax() | ||
413 | .ancestors() | ||
414 | .find_map(|node| { | ||
415 | if let Some(stmt) = ast::ExprStmt::cast(node.clone()) { | ||
416 | return Some(stmt.syntax().text_range() == name_ref.syntax().text_range()); | ||
417 | } | ||
418 | if let Some(block) = ast::BlockExpr::cast(node) { | ||
419 | return Some( | ||
420 | block.expr().map(|e| e.syntax().text_range()) | ||
421 | == Some(name_ref.syntax().text_range()), | ||
422 | ); | ||
423 | } | ||
424 | None | ||
425 | }) | ||
426 | .unwrap_or(false); | ||
427 | self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); | ||
428 | |||
429 | if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { | ||
430 | if let Some(if_expr) = | ||
431 | self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off) | ||
432 | { | ||
433 | if if_expr.syntax().text_range().end() < name_ref.syntax().text_range().start() | ||
434 | { | ||
435 | self.after_if = true; | ||
436 | } | ||
437 | } | ||
438 | } | ||
439 | } | ||
440 | if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { | ||
441 | // The receiver comes before the point of insertion of the fake | ||
442 | // ident, so it should have the same range in the non-modified file | ||
443 | self.dot_receiver = field_expr | ||
444 | .expr() | ||
445 | .map(|e| e.syntax().text_range()) | ||
446 | .and_then(|r| find_node_with_range(original_file, r)); | ||
447 | self.dot_receiver_is_ambiguous_float_literal = | ||
448 | if let Some(ast::Expr::Literal(l)) = &self.dot_receiver { | ||
449 | match l.kind() { | ||
450 | ast::LiteralKind::FloatNumber { .. } => l.token().text().ends_with('.'), | ||
451 | _ => false, | ||
452 | } | ||
453 | } else { | ||
454 | false | ||
455 | } | ||
456 | } | ||
457 | if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) { | ||
458 | // As above | ||
459 | self.dot_receiver = method_call_expr | ||
460 | .expr() | ||
461 | .map(|e| e.syntax().text_range()) | ||
462 | .and_then(|r| find_node_with_range(original_file, r)); | ||
463 | self.is_call = true; | ||
464 | } | ||
465 | } | ||
466 | } | ||
467 | |||
468 | fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> { | ||
469 | find_covering_element(syntax, range).ancestors().find_map(N::cast) | ||
470 | } | ||
471 | |||
472 | fn is_node<N: AstNode>(node: &SyntaxNode) -> bool { | ||
473 | match node.ancestors().find_map(N::cast) { | ||
474 | None => false, | ||
475 | Some(n) => n.syntax().text_range() == node.text_range(), | ||
476 | } | ||
477 | } | ||
478 | |||
479 | fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<ast::Path> { | ||
480 | if let Some(qual) = path.qualifier() { | ||
481 | return Some(qual); | ||
482 | } | ||
483 | let use_tree_list = path.syntax().ancestors().find_map(ast::UseTreeList::cast)?; | ||
484 | let use_tree = use_tree_list.syntax().parent().and_then(ast::UseTree::cast)?; | ||
485 | use_tree.path() | ||
486 | } | ||
diff --git a/crates/ide/src/completion/completion_item.rs b/crates/ide/src/completion/completion_item.rs new file mode 100644 index 000000000..9377cdc57 --- /dev/null +++ b/crates/ide/src/completion/completion_item.rs | |||
@@ -0,0 +1,384 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use std::fmt; | ||
4 | |||
5 | use hir::Documentation; | ||
6 | use syntax::TextRange; | ||
7 | use text_edit::TextEdit; | ||
8 | |||
9 | use crate::completion::completion_config::SnippetCap; | ||
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 | pub struct CompletionItem { | ||
15 | /// Used only internally in tests, to check only specific kind of | ||
16 | /// completion (postfix, keyword, reference, etc). | ||
17 | #[allow(unused)] | ||
18 | pub(crate) completion_kind: CompletionKind, | ||
19 | /// Label in the completion pop up which identifies completion. | ||
20 | label: String, | ||
21 | /// Range of identifier that is being completed. | ||
22 | /// | ||
23 | /// It should be used primarily for UI, but we also use this to convert | ||
24 | /// genetic TextEdit into LSP's completion edit (see conv.rs). | ||
25 | /// | ||
26 | /// `source_range` must contain the completion offset. `insert_text` should | ||
27 | /// start with what `source_range` points to, or VSCode will filter out the | ||
28 | /// completion silently. | ||
29 | source_range: TextRange, | ||
30 | /// What happens when user selects this item. | ||
31 | /// | ||
32 | /// Typically, replaces `source_range` with new identifier. | ||
33 | text_edit: TextEdit, | ||
34 | insert_text_format: InsertTextFormat, | ||
35 | |||
36 | /// What item (struct, function, etc) are we completing. | ||
37 | kind: Option<CompletionItemKind>, | ||
38 | |||
39 | /// Lookup is used to check if completion item indeed can complete current | ||
40 | /// ident. | ||
41 | /// | ||
42 | /// That is, in `foo.bar<|>` lookup of `abracadabra` will be accepted (it | ||
43 | /// contains `bar` sub sequence), and `quux` will rejected. | ||
44 | lookup: Option<String>, | ||
45 | |||
46 | /// Additional info to show in the UI pop up. | ||
47 | detail: Option<String>, | ||
48 | documentation: Option<Documentation>, | ||
49 | |||
50 | /// Whether this item is marked as deprecated | ||
51 | deprecated: bool, | ||
52 | |||
53 | /// If completing a function call, ask the editor to show parameter popup | ||
54 | /// after completion. | ||
55 | trigger_call_info: bool, | ||
56 | |||
57 | /// Score is useful to pre select or display in better order completion items | ||
58 | score: Option<CompletionScore>, | ||
59 | } | ||
60 | |||
61 | // We use custom debug for CompletionItem to make snapshot tests more readable. | ||
62 | impl fmt::Debug for CompletionItem { | ||
63 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
64 | let mut s = f.debug_struct("CompletionItem"); | ||
65 | s.field("label", &self.label()).field("source_range", &self.source_range()); | ||
66 | if self.text_edit().len() == 1 { | ||
67 | let atom = &self.text_edit().iter().next().unwrap(); | ||
68 | s.field("delete", &atom.delete); | ||
69 | s.field("insert", &atom.insert); | ||
70 | } else { | ||
71 | s.field("text_edit", &self.text_edit); | ||
72 | } | ||
73 | if let Some(kind) = self.kind().as_ref() { | ||
74 | s.field("kind", kind); | ||
75 | } | ||
76 | if self.lookup() != self.label() { | ||
77 | s.field("lookup", &self.lookup()); | ||
78 | } | ||
79 | if let Some(detail) = self.detail() { | ||
80 | s.field("detail", &detail); | ||
81 | } | ||
82 | if let Some(documentation) = self.documentation() { | ||
83 | s.field("documentation", &documentation); | ||
84 | } | ||
85 | if self.deprecated { | ||
86 | s.field("deprecated", &true); | ||
87 | } | ||
88 | if let Some(score) = &self.score { | ||
89 | s.field("score", score); | ||
90 | } | ||
91 | if self.trigger_call_info { | ||
92 | s.field("trigger_call_info", &true); | ||
93 | } | ||
94 | s.finish() | ||
95 | } | ||
96 | } | ||
97 | |||
98 | #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)] | ||
99 | pub enum CompletionScore { | ||
100 | /// If only type match | ||
101 | TypeMatch, | ||
102 | /// If type and name match | ||
103 | TypeAndNameMatch, | ||
104 | } | ||
105 | |||
106 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
107 | pub enum CompletionItemKind { | ||
108 | Snippet, | ||
109 | Keyword, | ||
110 | Module, | ||
111 | Function, | ||
112 | BuiltinType, | ||
113 | Struct, | ||
114 | Enum, | ||
115 | EnumVariant, | ||
116 | Binding, | ||
117 | Field, | ||
118 | Static, | ||
119 | Const, | ||
120 | Trait, | ||
121 | TypeAlias, | ||
122 | Method, | ||
123 | TypeParam, | ||
124 | Macro, | ||
125 | Attribute, | ||
126 | UnresolvedReference, | ||
127 | } | ||
128 | |||
129 | impl CompletionItemKind { | ||
130 | #[cfg(test)] | ||
131 | pub(crate) fn tag(&self) -> &'static str { | ||
132 | match self { | ||
133 | CompletionItemKind::Attribute => "at", | ||
134 | CompletionItemKind::Binding => "bn", | ||
135 | CompletionItemKind::BuiltinType => "bt", | ||
136 | CompletionItemKind::Const => "ct", | ||
137 | CompletionItemKind::Enum => "en", | ||
138 | CompletionItemKind::EnumVariant => "ev", | ||
139 | CompletionItemKind::Field => "fd", | ||
140 | CompletionItemKind::Function => "fn", | ||
141 | CompletionItemKind::Keyword => "kw", | ||
142 | CompletionItemKind::Macro => "ma", | ||
143 | CompletionItemKind::Method => "me", | ||
144 | CompletionItemKind::Module => "md", | ||
145 | CompletionItemKind::Snippet => "sn", | ||
146 | CompletionItemKind::Static => "sc", | ||
147 | CompletionItemKind::Struct => "st", | ||
148 | CompletionItemKind::Trait => "tt", | ||
149 | CompletionItemKind::TypeAlias => "ta", | ||
150 | CompletionItemKind::TypeParam => "tp", | ||
151 | CompletionItemKind::UnresolvedReference => "??", | ||
152 | } | ||
153 | } | ||
154 | } | ||
155 | |||
156 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] | ||
157 | pub(crate) enum CompletionKind { | ||
158 | /// Parser-based keyword completion. | ||
159 | Keyword, | ||
160 | /// Your usual "complete all valid identifiers". | ||
161 | Reference, | ||
162 | /// "Secret sauce" completions. | ||
163 | Magic, | ||
164 | Snippet, | ||
165 | Postfix, | ||
166 | BuiltinType, | ||
167 | Attribute, | ||
168 | } | ||
169 | |||
170 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] | ||
171 | pub enum InsertTextFormat { | ||
172 | PlainText, | ||
173 | Snippet, | ||
174 | } | ||
175 | |||
176 | impl CompletionItem { | ||
177 | pub(crate) fn new( | ||
178 | completion_kind: CompletionKind, | ||
179 | source_range: TextRange, | ||
180 | label: impl Into<String>, | ||
181 | ) -> Builder { | ||
182 | let label = label.into(); | ||
183 | Builder { | ||
184 | source_range, | ||
185 | completion_kind, | ||
186 | label, | ||
187 | insert_text: None, | ||
188 | insert_text_format: InsertTextFormat::PlainText, | ||
189 | detail: None, | ||
190 | documentation: None, | ||
191 | lookup: None, | ||
192 | kind: None, | ||
193 | text_edit: None, | ||
194 | deprecated: None, | ||
195 | trigger_call_info: None, | ||
196 | score: None, | ||
197 | } | ||
198 | } | ||
199 | /// What user sees in pop-up in the UI. | ||
200 | pub fn label(&self) -> &str { | ||
201 | &self.label | ||
202 | } | ||
203 | pub fn source_range(&self) -> TextRange { | ||
204 | self.source_range | ||
205 | } | ||
206 | |||
207 | pub fn insert_text_format(&self) -> InsertTextFormat { | ||
208 | self.insert_text_format | ||
209 | } | ||
210 | |||
211 | pub fn text_edit(&self) -> &TextEdit { | ||
212 | &self.text_edit | ||
213 | } | ||
214 | |||
215 | /// Short one-line additional information, like a type | ||
216 | pub fn detail(&self) -> Option<&str> { | ||
217 | self.detail.as_deref() | ||
218 | } | ||
219 | /// A doc-comment | ||
220 | pub fn documentation(&self) -> Option<Documentation> { | ||
221 | self.documentation.clone() | ||
222 | } | ||
223 | /// What string is used for filtering. | ||
224 | pub fn lookup(&self) -> &str { | ||
225 | self.lookup.as_deref().unwrap_or(&self.label) | ||
226 | } | ||
227 | |||
228 | pub fn kind(&self) -> Option<CompletionItemKind> { | ||
229 | self.kind | ||
230 | } | ||
231 | |||
232 | pub fn deprecated(&self) -> bool { | ||
233 | self.deprecated | ||
234 | } | ||
235 | |||
236 | pub fn score(&self) -> Option<CompletionScore> { | ||
237 | self.score | ||
238 | } | ||
239 | |||
240 | pub fn trigger_call_info(&self) -> bool { | ||
241 | self.trigger_call_info | ||
242 | } | ||
243 | } | ||
244 | |||
245 | /// A helper to make `CompletionItem`s. | ||
246 | #[must_use] | ||
247 | pub(crate) struct Builder { | ||
248 | source_range: TextRange, | ||
249 | completion_kind: CompletionKind, | ||
250 | label: String, | ||
251 | insert_text: Option<String>, | ||
252 | insert_text_format: InsertTextFormat, | ||
253 | detail: Option<String>, | ||
254 | documentation: Option<Documentation>, | ||
255 | lookup: Option<String>, | ||
256 | kind: Option<CompletionItemKind>, | ||
257 | text_edit: Option<TextEdit>, | ||
258 | deprecated: Option<bool>, | ||
259 | trigger_call_info: Option<bool>, | ||
260 | score: Option<CompletionScore>, | ||
261 | } | ||
262 | |||
263 | impl Builder { | ||
264 | pub(crate) fn add_to(self, acc: &mut Completions) { | ||
265 | acc.add(self.build()) | ||
266 | } | ||
267 | |||
268 | pub(crate) fn build(self) -> CompletionItem { | ||
269 | let label = self.label; | ||
270 | let text_edit = match self.text_edit { | ||
271 | Some(it) => it, | ||
272 | None => TextEdit::replace( | ||
273 | self.source_range, | ||
274 | self.insert_text.unwrap_or_else(|| label.clone()), | ||
275 | ), | ||
276 | }; | ||
277 | |||
278 | CompletionItem { | ||
279 | source_range: self.source_range, | ||
280 | label, | ||
281 | insert_text_format: self.insert_text_format, | ||
282 | text_edit, | ||
283 | detail: self.detail, | ||
284 | documentation: self.documentation, | ||
285 | lookup: self.lookup, | ||
286 | kind: self.kind, | ||
287 | completion_kind: self.completion_kind, | ||
288 | deprecated: self.deprecated.unwrap_or(false), | ||
289 | trigger_call_info: self.trigger_call_info.unwrap_or(false), | ||
290 | score: self.score, | ||
291 | } | ||
292 | } | ||
293 | pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { | ||
294 | self.lookup = Some(lookup.into()); | ||
295 | self | ||
296 | } | ||
297 | pub(crate) fn label(mut self, label: impl Into<String>) -> Builder { | ||
298 | self.label = label.into(); | ||
299 | self | ||
300 | } | ||
301 | pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder { | ||
302 | self.insert_text = Some(insert_text.into()); | ||
303 | self | ||
304 | } | ||
305 | pub(crate) fn insert_snippet( | ||
306 | mut self, | ||
307 | _cap: SnippetCap, | ||
308 | snippet: impl Into<String>, | ||
309 | ) -> Builder { | ||
310 | self.insert_text_format = InsertTextFormat::Snippet; | ||
311 | self.insert_text(snippet) | ||
312 | } | ||
313 | pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder { | ||
314 | self.kind = Some(kind); | ||
315 | self | ||
316 | } | ||
317 | pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder { | ||
318 | self.text_edit = Some(edit); | ||
319 | self | ||
320 | } | ||
321 | pub(crate) fn snippet_edit(mut self, _cap: SnippetCap, edit: TextEdit) -> Builder { | ||
322 | self.insert_text_format = InsertTextFormat::Snippet; | ||
323 | self.text_edit(edit) | ||
324 | } | ||
325 | #[allow(unused)] | ||
326 | pub(crate) fn detail(self, detail: impl Into<String>) -> Builder { | ||
327 | self.set_detail(Some(detail)) | ||
328 | } | ||
329 | pub(crate) fn set_detail(mut self, detail: Option<impl Into<String>>) -> Builder { | ||
330 | self.detail = detail.map(Into::into); | ||
331 | self | ||
332 | } | ||
333 | #[allow(unused)] | ||
334 | pub(crate) fn documentation(self, docs: Documentation) -> Builder { | ||
335 | self.set_documentation(Some(docs)) | ||
336 | } | ||
337 | pub(crate) fn set_documentation(mut self, docs: Option<Documentation>) -> Builder { | ||
338 | self.documentation = docs.map(Into::into); | ||
339 | self | ||
340 | } | ||
341 | pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder { | ||
342 | self.deprecated = Some(deprecated); | ||
343 | self | ||
344 | } | ||
345 | pub(crate) fn set_score(mut self, score: CompletionScore) -> Builder { | ||
346 | self.score = Some(score); | ||
347 | self | ||
348 | } | ||
349 | pub(crate) fn trigger_call_info(mut self) -> Builder { | ||
350 | self.trigger_call_info = Some(true); | ||
351 | self | ||
352 | } | ||
353 | } | ||
354 | |||
355 | impl<'a> Into<CompletionItem> for Builder { | ||
356 | fn into(self) -> CompletionItem { | ||
357 | self.build() | ||
358 | } | ||
359 | } | ||
360 | |||
361 | /// Represents an in-progress set of completions being built. | ||
362 | #[derive(Debug, Default)] | ||
363 | pub(crate) struct Completions { | ||
364 | buf: Vec<CompletionItem>, | ||
365 | } | ||
366 | |||
367 | impl Completions { | ||
368 | pub(crate) fn add(&mut self, item: impl Into<CompletionItem>) { | ||
369 | self.buf.push(item.into()) | ||
370 | } | ||
371 | pub(crate) fn add_all<I>(&mut self, items: I) | ||
372 | where | ||
373 | I: IntoIterator, | ||
374 | I::Item: Into<CompletionItem>, | ||
375 | { | ||
376 | items.into_iter().for_each(|item| self.add(item.into())) | ||
377 | } | ||
378 | } | ||
379 | |||
380 | impl Into<Vec<CompletionItem>> for Completions { | ||
381 | fn into(self) -> Vec<CompletionItem> { | ||
382 | self.buf | ||
383 | } | ||
384 | } | ||
diff --git a/crates/ide/src/completion/patterns.rs b/crates/ide/src/completion/patterns.rs new file mode 100644 index 000000000..ffc97c076 --- /dev/null +++ b/crates/ide/src/completion/patterns.rs | |||
@@ -0,0 +1,194 @@ | |||
1 | //! Patterns telling us certain facts about current syntax element, they are used in completion context | ||
2 | |||
3 | use syntax::{ | ||
4 | algo::non_trivia_sibling, | ||
5 | ast::{self, LoopBodyOwner}, | ||
6 | match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, | ||
7 | SyntaxKind::*, | ||
8 | SyntaxNode, SyntaxToken, | ||
9 | }; | ||
10 | |||
11 | #[cfg(test)] | ||
12 | use crate::completion::test_utils::check_pattern_is_applicable; | ||
13 | |||
14 | pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool { | ||
15 | not_same_range_ancestor(element) | ||
16 | .filter(|it| it.kind() == ASSOC_ITEM_LIST) | ||
17 | .and_then(|it| it.parent()) | ||
18 | .filter(|it| it.kind() == TRAIT) | ||
19 | .is_some() | ||
20 | } | ||
21 | #[test] | ||
22 | fn test_has_trait_parent() { | ||
23 | check_pattern_is_applicable(r"trait A { f<|> }", has_trait_parent); | ||
24 | } | ||
25 | |||
26 | pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool { | ||
27 | not_same_range_ancestor(element) | ||
28 | .filter(|it| it.kind() == ASSOC_ITEM_LIST) | ||
29 | .and_then(|it| it.parent()) | ||
30 | .filter(|it| it.kind() == IMPL) | ||
31 | .is_some() | ||
32 | } | ||
33 | #[test] | ||
34 | fn test_has_impl_parent() { | ||
35 | check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent); | ||
36 | } | ||
37 | |||
38 | pub(crate) fn has_block_expr_parent(element: SyntaxElement) -> bool { | ||
39 | not_same_range_ancestor(element).filter(|it| it.kind() == BLOCK_EXPR).is_some() | ||
40 | } | ||
41 | #[test] | ||
42 | fn test_has_block_expr_parent() { | ||
43 | check_pattern_is_applicable(r"fn my_fn() { let a = 2; f<|> }", has_block_expr_parent); | ||
44 | } | ||
45 | |||
46 | pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool { | ||
47 | element.ancestors().find(|it| it.kind() == IDENT_PAT).is_some() | ||
48 | } | ||
49 | #[test] | ||
50 | fn test_has_bind_pat_parent() { | ||
51 | check_pattern_is_applicable(r"fn my_fn(m<|>) {}", has_bind_pat_parent); | ||
52 | check_pattern_is_applicable(r"fn my_fn() { let m<|> }", has_bind_pat_parent); | ||
53 | } | ||
54 | |||
55 | pub(crate) fn has_ref_parent(element: SyntaxElement) -> bool { | ||
56 | not_same_range_ancestor(element) | ||
57 | .filter(|it| it.kind() == REF_PAT || it.kind() == REF_EXPR) | ||
58 | .is_some() | ||
59 | } | ||
60 | #[test] | ||
61 | fn test_has_ref_parent() { | ||
62 | check_pattern_is_applicable(r"fn my_fn(&m<|>) {}", has_ref_parent); | ||
63 | check_pattern_is_applicable(r"fn my() { let &m<|> }", has_ref_parent); | ||
64 | } | ||
65 | |||
66 | pub(crate) fn has_item_list_or_source_file_parent(element: SyntaxElement) -> bool { | ||
67 | let ancestor = not_same_range_ancestor(element); | ||
68 | if !ancestor.is_some() { | ||
69 | return true; | ||
70 | } | ||
71 | ancestor.filter(|it| it.kind() == SOURCE_FILE || it.kind() == ITEM_LIST).is_some() | ||
72 | } | ||
73 | #[test] | ||
74 | fn test_has_item_list_or_source_file_parent() { | ||
75 | check_pattern_is_applicable(r"i<|>", has_item_list_or_source_file_parent); | ||
76 | check_pattern_is_applicable(r"mod foo { f<|> }", has_item_list_or_source_file_parent); | ||
77 | } | ||
78 | |||
79 | pub(crate) fn is_match_arm(element: SyntaxElement) -> bool { | ||
80 | not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some() | ||
81 | && previous_sibling_or_ancestor_sibling(element) | ||
82 | .and_then(|it| it.into_token()) | ||
83 | .filter(|it| it.kind() == FAT_ARROW) | ||
84 | .is_some() | ||
85 | } | ||
86 | #[test] | ||
87 | fn test_is_match_arm() { | ||
88 | check_pattern_is_applicable(r"fn my_fn() { match () { () => m<|> } }", is_match_arm); | ||
89 | } | ||
90 | |||
91 | pub(crate) fn unsafe_is_prev(element: SyntaxElement) -> bool { | ||
92 | element | ||
93 | .into_token() | ||
94 | .and_then(|it| previous_non_trivia_token(it)) | ||
95 | .filter(|it| it.kind() == UNSAFE_KW) | ||
96 | .is_some() | ||
97 | } | ||
98 | #[test] | ||
99 | fn test_unsafe_is_prev() { | ||
100 | check_pattern_is_applicable(r"unsafe i<|>", unsafe_is_prev); | ||
101 | } | ||
102 | |||
103 | pub(crate) fn if_is_prev(element: SyntaxElement) -> bool { | ||
104 | element | ||
105 | .into_token() | ||
106 | .and_then(|it| previous_non_trivia_token(it)) | ||
107 | .filter(|it| it.kind() == IF_KW) | ||
108 | .is_some() | ||
109 | } | ||
110 | #[test] | ||
111 | fn test_if_is_prev() { | ||
112 | check_pattern_is_applicable(r"if l<|>", if_is_prev); | ||
113 | } | ||
114 | |||
115 | pub(crate) fn has_trait_as_prev_sibling(element: SyntaxElement) -> bool { | ||
116 | previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == TRAIT).is_some() | ||
117 | } | ||
118 | #[test] | ||
119 | fn test_has_trait_as_prev_sibling() { | ||
120 | check_pattern_is_applicable(r"trait A w<|> {}", has_trait_as_prev_sibling); | ||
121 | } | ||
122 | |||
123 | pub(crate) fn has_impl_as_prev_sibling(element: SyntaxElement) -> bool { | ||
124 | previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == IMPL).is_some() | ||
125 | } | ||
126 | #[test] | ||
127 | fn test_has_impl_as_prev_sibling() { | ||
128 | check_pattern_is_applicable(r"impl A w<|> {}", has_impl_as_prev_sibling); | ||
129 | } | ||
130 | |||
131 | pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { | ||
132 | let leaf = match element { | ||
133 | NodeOrToken::Node(node) => node, | ||
134 | NodeOrToken::Token(token) => token.parent(), | ||
135 | }; | ||
136 | for node in leaf.ancestors() { | ||
137 | if node.kind() == FN || node.kind() == CLOSURE_EXPR { | ||
138 | break; | ||
139 | } | ||
140 | let loop_body = match_ast! { | ||
141 | match node { | ||
142 | ast::ForExpr(it) => it.loop_body(), | ||
143 | ast::WhileExpr(it) => it.loop_body(), | ||
144 | ast::LoopExpr(it) => it.loop_body(), | ||
145 | _ => None, | ||
146 | } | ||
147 | }; | ||
148 | if let Some(body) = loop_body { | ||
149 | if body.syntax().text_range().contains_range(leaf.text_range()) { | ||
150 | return true; | ||
151 | } | ||
152 | } | ||
153 | } | ||
154 | false | ||
155 | } | ||
156 | |||
157 | fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> { | ||
158 | element | ||
159 | .ancestors() | ||
160 | .take_while(|it| it.text_range() == element.text_range()) | ||
161 | .last() | ||
162 | .and_then(|it| it.parent()) | ||
163 | } | ||
164 | |||
165 | fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { | ||
166 | let mut token = token.prev_token(); | ||
167 | while let Some(inner) = token.clone() { | ||
168 | if !inner.kind().is_trivia() { | ||
169 | return Some(inner); | ||
170 | } else { | ||
171 | token = inner.prev_token(); | ||
172 | } | ||
173 | } | ||
174 | None | ||
175 | } | ||
176 | |||
177 | fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<SyntaxElement> { | ||
178 | let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev); | ||
179 | if let Some(sibling) = token_sibling { | ||
180 | Some(sibling) | ||
181 | } else { | ||
182 | // if not trying to find first ancestor which has such a sibling | ||
183 | let node = match element { | ||
184 | NodeOrToken::Node(node) => node, | ||
185 | NodeOrToken::Token(token) => token.parent(), | ||
186 | }; | ||
187 | let range = node.text_range(); | ||
188 | let top_node = node.ancestors().take_while(|it| it.text_range() == range).last()?; | ||
189 | let prev_sibling_node = top_node.ancestors().find(|it| { | ||
190 | non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some() | ||
191 | })?; | ||
192 | non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev) | ||
193 | } | ||
194 | } | ||
diff --git a/crates/ide/src/completion/presentation.rs b/crates/ide/src/completion/presentation.rs new file mode 100644 index 000000000..e1b1ea4ce --- /dev/null +++ b/crates/ide/src/completion/presentation.rs | |||
@@ -0,0 +1,1229 @@ | |||
1 | //! This modules takes care of rendering various definitions as completion items. | ||
2 | //! It also handles scoring (sorting) completions. | ||
3 | |||
4 | use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; | ||
5 | use itertools::Itertools; | ||
6 | use syntax::ast::NameOwner; | ||
7 | use test_utils::mark; | ||
8 | |||
9 | use crate::{ | ||
10 | completion::{ | ||
11 | completion_item::Builder, CompletionContext, CompletionItem, CompletionItemKind, | ||
12 | CompletionKind, Completions, | ||
13 | }, | ||
14 | display::{const_label, function_declaration, macro_label, type_label}, | ||
15 | CompletionScore, RootDatabase, | ||
16 | }; | ||
17 | |||
18 | impl Completions { | ||
19 | pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) { | ||
20 | let is_deprecated = is_deprecated(field, ctx.db); | ||
21 | let name = field.name(ctx.db); | ||
22 | let mut completion_item = | ||
23 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string()) | ||
24 | .kind(CompletionItemKind::Field) | ||
25 | .detail(ty.display(ctx.db).to_string()) | ||
26 | .set_documentation(field.docs(ctx.db)) | ||
27 | .set_deprecated(is_deprecated); | ||
28 | |||
29 | if let Some(score) = compute_score(ctx, &ty, &name.to_string()) { | ||
30 | completion_item = completion_item.set_score(score); | ||
31 | } | ||
32 | |||
33 | completion_item.add_to(self); | ||
34 | } | ||
35 | |||
36 | pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) { | ||
37 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), field.to_string()) | ||
38 | .kind(CompletionItemKind::Field) | ||
39 | .detail(ty.display(ctx.db).to_string()) | ||
40 | .add_to(self); | ||
41 | } | ||
42 | |||
43 | pub(crate) fn add_resolution( | ||
44 | &mut self, | ||
45 | ctx: &CompletionContext, | ||
46 | local_name: String, | ||
47 | resolution: &ScopeDef, | ||
48 | ) { | ||
49 | use hir::ModuleDef::*; | ||
50 | |||
51 | let completion_kind = match resolution { | ||
52 | ScopeDef::ModuleDef(BuiltinType(..)) => CompletionKind::BuiltinType, | ||
53 | _ => CompletionKind::Reference, | ||
54 | }; | ||
55 | |||
56 | let kind = match resolution { | ||
57 | ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::Module, | ||
58 | ScopeDef::ModuleDef(Function(func)) => { | ||
59 | return self.add_function(ctx, *func, Some(local_name)); | ||
60 | } | ||
61 | ScopeDef::ModuleDef(Adt(hir::Adt::Struct(_))) => CompletionItemKind::Struct, | ||
62 | // FIXME: add CompletionItemKind::Union | ||
63 | ScopeDef::ModuleDef(Adt(hir::Adt::Union(_))) => CompletionItemKind::Struct, | ||
64 | ScopeDef::ModuleDef(Adt(hir::Adt::Enum(_))) => CompletionItemKind::Enum, | ||
65 | |||
66 | ScopeDef::ModuleDef(EnumVariant(var)) => { | ||
67 | return self.add_enum_variant(ctx, *var, Some(local_name)); | ||
68 | } | ||
69 | ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::Const, | ||
70 | ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::Static, | ||
71 | ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::Trait, | ||
72 | ScopeDef::ModuleDef(TypeAlias(..)) => CompletionItemKind::TypeAlias, | ||
73 | ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType, | ||
74 | ScopeDef::GenericParam(..) => CompletionItemKind::TypeParam, | ||
75 | ScopeDef::Local(..) => CompletionItemKind::Binding, | ||
76 | // (does this need its own kind?) | ||
77 | ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => CompletionItemKind::TypeParam, | ||
78 | ScopeDef::MacroDef(mac) => { | ||
79 | return self.add_macro(ctx, Some(local_name), *mac); | ||
80 | } | ||
81 | ScopeDef::Unknown => { | ||
82 | return self.add( | ||
83 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), local_name) | ||
84 | .kind(CompletionItemKind::UnresolvedReference), | ||
85 | ); | ||
86 | } | ||
87 | }; | ||
88 | |||
89 | let docs = match resolution { | ||
90 | ScopeDef::ModuleDef(Module(it)) => it.docs(ctx.db), | ||
91 | ScopeDef::ModuleDef(Adt(it)) => it.docs(ctx.db), | ||
92 | ScopeDef::ModuleDef(EnumVariant(it)) => it.docs(ctx.db), | ||
93 | ScopeDef::ModuleDef(Const(it)) => it.docs(ctx.db), | ||
94 | ScopeDef::ModuleDef(Static(it)) => it.docs(ctx.db), | ||
95 | ScopeDef::ModuleDef(Trait(it)) => it.docs(ctx.db), | ||
96 | ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(ctx.db), | ||
97 | _ => None, | ||
98 | }; | ||
99 | |||
100 | let mut completion_item = | ||
101 | CompletionItem::new(completion_kind, ctx.source_range(), local_name.clone()); | ||
102 | if let ScopeDef::Local(local) = resolution { | ||
103 | let ty = local.ty(ctx.db); | ||
104 | if !ty.is_unknown() { | ||
105 | completion_item = completion_item.detail(ty.display(ctx.db).to_string()); | ||
106 | } | ||
107 | }; | ||
108 | |||
109 | if let ScopeDef::Local(local) = resolution { | ||
110 | if let Some(score) = compute_score(ctx, &local.ty(ctx.db), &local_name) { | ||
111 | completion_item = completion_item.set_score(score); | ||
112 | } | ||
113 | } | ||
114 | |||
115 | // Add `<>` for generic types | ||
116 | if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis { | ||
117 | if let Some(cap) = ctx.config.snippet_cap { | ||
118 | let has_non_default_type_params = match resolution { | ||
119 | ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db), | ||
120 | ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db), | ||
121 | _ => false, | ||
122 | }; | ||
123 | if has_non_default_type_params { | ||
124 | mark::hit!(inserts_angle_brackets_for_generics); | ||
125 | completion_item = completion_item | ||
126 | .lookup_by(local_name.clone()) | ||
127 | .label(format!("{}<…>", local_name)) | ||
128 | .insert_snippet(cap, format!("{}<$0>", local_name)); | ||
129 | } | ||
130 | } | ||
131 | } | ||
132 | |||
133 | completion_item.kind(kind).set_documentation(docs).add_to(self) | ||
134 | } | ||
135 | |||
136 | pub(crate) fn add_macro( | ||
137 | &mut self, | ||
138 | ctx: &CompletionContext, | ||
139 | name: Option<String>, | ||
140 | macro_: hir::MacroDef, | ||
141 | ) { | ||
142 | // FIXME: Currently proc-macro do not have ast-node, | ||
143 | // such that it does not have source | ||
144 | if macro_.is_proc_macro() { | ||
145 | return; | ||
146 | } | ||
147 | |||
148 | let name = match name { | ||
149 | Some(it) => it, | ||
150 | None => return, | ||
151 | }; | ||
152 | |||
153 | let ast_node = macro_.source(ctx.db).value; | ||
154 | let detail = macro_label(&ast_node); | ||
155 | |||
156 | let docs = macro_.docs(ctx.db); | ||
157 | |||
158 | let mut builder = CompletionItem::new( | ||
159 | CompletionKind::Reference, | ||
160 | ctx.source_range(), | ||
161 | &format!("{}!", name), | ||
162 | ) | ||
163 | .kind(CompletionItemKind::Macro) | ||
164 | .set_documentation(docs.clone()) | ||
165 | .set_deprecated(is_deprecated(macro_, ctx.db)) | ||
166 | .detail(detail); | ||
167 | |||
168 | let needs_bang = ctx.use_item_syntax.is_none() && !ctx.is_macro_call; | ||
169 | builder = match ctx.config.snippet_cap { | ||
170 | Some(cap) if needs_bang => { | ||
171 | let docs = docs.as_ref().map_or("", |s| s.as_str()); | ||
172 | let (bra, ket) = guess_macro_braces(&name, docs); | ||
173 | builder | ||
174 | .insert_snippet(cap, format!("{}!{}$0{}", name, bra, ket)) | ||
175 | .label(format!("{}!{}…{}", name, bra, ket)) | ||
176 | .lookup_by(format!("{}!", name)) | ||
177 | } | ||
178 | None if needs_bang => builder.insert_text(format!("{}!", name)), | ||
179 | _ => { | ||
180 | mark::hit!(dont_insert_macro_call_parens_unncessary); | ||
181 | builder.insert_text(name) | ||
182 | } | ||
183 | }; | ||
184 | |||
185 | self.add(builder); | ||
186 | } | ||
187 | |||
188 | pub(crate) fn add_function( | ||
189 | &mut self, | ||
190 | ctx: &CompletionContext, | ||
191 | func: hir::Function, | ||
192 | local_name: Option<String>, | ||
193 | ) { | ||
194 | let has_self_param = func.has_self_param(ctx.db); | ||
195 | |||
196 | let name = local_name.unwrap_or_else(|| func.name(ctx.db).to_string()); | ||
197 | let ast_node = func.source(ctx.db).value; | ||
198 | |||
199 | let mut builder = | ||
200 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.clone()) | ||
201 | .kind(if has_self_param { | ||
202 | CompletionItemKind::Method | ||
203 | } else { | ||
204 | CompletionItemKind::Function | ||
205 | }) | ||
206 | .set_documentation(func.docs(ctx.db)) | ||
207 | .set_deprecated(is_deprecated(func, ctx.db)) | ||
208 | .detail(function_declaration(&ast_node)); | ||
209 | |||
210 | let params = ast_node | ||
211 | .param_list() | ||
212 | .into_iter() | ||
213 | .flat_map(|it| it.params()) | ||
214 | .flat_map(|it| it.pat()) | ||
215 | .map(|pat| pat.to_string().trim_start_matches('_').into()) | ||
216 | .collect(); | ||
217 | |||
218 | builder = builder.add_call_parens(ctx, name, Params::Named(params)); | ||
219 | |||
220 | self.add(builder) | ||
221 | } | ||
222 | |||
223 | pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { | ||
224 | let ast_node = constant.source(ctx.db).value; | ||
225 | let name = match ast_node.name() { | ||
226 | Some(name) => name, | ||
227 | _ => return, | ||
228 | }; | ||
229 | let detail = const_label(&ast_node); | ||
230 | |||
231 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string()) | ||
232 | .kind(CompletionItemKind::Const) | ||
233 | .set_documentation(constant.docs(ctx.db)) | ||
234 | .set_deprecated(is_deprecated(constant, ctx.db)) | ||
235 | .detail(detail) | ||
236 | .add_to(self); | ||
237 | } | ||
238 | |||
239 | pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) { | ||
240 | let type_def = type_alias.source(ctx.db).value; | ||
241 | let name = match type_def.name() { | ||
242 | Some(name) => name, | ||
243 | _ => return, | ||
244 | }; | ||
245 | let detail = type_label(&type_def); | ||
246 | |||
247 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string()) | ||
248 | .kind(CompletionItemKind::TypeAlias) | ||
249 | .set_documentation(type_alias.docs(ctx.db)) | ||
250 | .set_deprecated(is_deprecated(type_alias, ctx.db)) | ||
251 | .detail(detail) | ||
252 | .add_to(self); | ||
253 | } | ||
254 | |||
255 | pub(crate) fn add_qualified_enum_variant( | ||
256 | &mut self, | ||
257 | ctx: &CompletionContext, | ||
258 | variant: hir::EnumVariant, | ||
259 | path: ModPath, | ||
260 | ) { | ||
261 | self.add_enum_variant_impl(ctx, variant, None, Some(path)) | ||
262 | } | ||
263 | |||
264 | pub(crate) fn add_enum_variant( | ||
265 | &mut self, | ||
266 | ctx: &CompletionContext, | ||
267 | variant: hir::EnumVariant, | ||
268 | local_name: Option<String>, | ||
269 | ) { | ||
270 | self.add_enum_variant_impl(ctx, variant, local_name, None) | ||
271 | } | ||
272 | |||
273 | fn add_enum_variant_impl( | ||
274 | &mut self, | ||
275 | ctx: &CompletionContext, | ||
276 | variant: hir::EnumVariant, | ||
277 | local_name: Option<String>, | ||
278 | path: Option<ModPath>, | ||
279 | ) { | ||
280 | let is_deprecated = is_deprecated(variant, ctx.db); | ||
281 | let name = local_name.unwrap_or_else(|| variant.name(ctx.db).to_string()); | ||
282 | let qualified_name = match &path { | ||
283 | Some(it) => it.to_string(), | ||
284 | None => name.to_string(), | ||
285 | }; | ||
286 | let detail_types = variant | ||
287 | .fields(ctx.db) | ||
288 | .into_iter() | ||
289 | .map(|field| (field.name(ctx.db), field.signature_ty(ctx.db))); | ||
290 | let variant_kind = variant.kind(ctx.db); | ||
291 | let detail = match variant_kind { | ||
292 | StructKind::Tuple | StructKind::Unit => format!( | ||
293 | "({})", | ||
294 | detail_types.map(|(_, t)| t.display(ctx.db).to_string()).format(", ") | ||
295 | ), | ||
296 | StructKind::Record => format!( | ||
297 | "{{ {} }}", | ||
298 | detail_types | ||
299 | .map(|(n, t)| format!("{}: {}", n, t.display(ctx.db).to_string())) | ||
300 | .format(", ") | ||
301 | ), | ||
302 | }; | ||
303 | let mut res = CompletionItem::new( | ||
304 | CompletionKind::Reference, | ||
305 | ctx.source_range(), | ||
306 | qualified_name.clone(), | ||
307 | ) | ||
308 | .kind(CompletionItemKind::EnumVariant) | ||
309 | .set_documentation(variant.docs(ctx.db)) | ||
310 | .set_deprecated(is_deprecated) | ||
311 | .detail(detail); | ||
312 | |||
313 | if path.is_some() { | ||
314 | res = res.lookup_by(name); | ||
315 | } | ||
316 | |||
317 | if variant_kind == StructKind::Tuple { | ||
318 | mark::hit!(inserts_parens_for_tuple_enums); | ||
319 | let params = Params::Anonymous(variant.fields(ctx.db).len()); | ||
320 | res = res.add_call_parens(ctx, qualified_name, params) | ||
321 | } | ||
322 | |||
323 | res.add_to(self); | ||
324 | } | ||
325 | } | ||
326 | |||
327 | pub(crate) fn compute_score( | ||
328 | ctx: &CompletionContext, | ||
329 | ty: &Type, | ||
330 | name: &str, | ||
331 | ) -> Option<CompletionScore> { | ||
332 | let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax { | ||
333 | mark::hit!(record_field_type_match); | ||
334 | let (struct_field, _local) = ctx.sema.resolve_record_field(record_field)?; | ||
335 | (struct_field.name(ctx.db).to_string(), struct_field.signature_ty(ctx.db)) | ||
336 | } else if let Some(active_parameter) = &ctx.active_parameter { | ||
337 | mark::hit!(active_param_type_match); | ||
338 | (active_parameter.name.clone(), active_parameter.ty.clone()) | ||
339 | } else { | ||
340 | return None; | ||
341 | }; | ||
342 | |||
343 | // Compute score | ||
344 | // For the same type | ||
345 | if &active_type != ty { | ||
346 | return None; | ||
347 | } | ||
348 | |||
349 | let mut res = CompletionScore::TypeMatch; | ||
350 | |||
351 | // If same type + same name then go top position | ||
352 | if active_name == name { | ||
353 | res = CompletionScore::TypeAndNameMatch | ||
354 | } | ||
355 | |||
356 | Some(res) | ||
357 | } | ||
358 | |||
359 | enum Params { | ||
360 | Named(Vec<String>), | ||
361 | Anonymous(usize), | ||
362 | } | ||
363 | |||
364 | impl Params { | ||
365 | fn len(&self) -> usize { | ||
366 | match self { | ||
367 | Params::Named(xs) => xs.len(), | ||
368 | Params::Anonymous(len) => *len, | ||
369 | } | ||
370 | } | ||
371 | |||
372 | fn is_empty(&self) -> bool { | ||
373 | self.len() == 0 | ||
374 | } | ||
375 | } | ||
376 | |||
377 | impl Builder { | ||
378 | fn add_call_parens(mut self, ctx: &CompletionContext, name: String, params: Params) -> Builder { | ||
379 | if !ctx.config.add_call_parenthesis { | ||
380 | return self; | ||
381 | } | ||
382 | if ctx.use_item_syntax.is_some() { | ||
383 | mark::hit!(no_parens_in_use_item); | ||
384 | return self; | ||
385 | } | ||
386 | if ctx.is_pattern_call { | ||
387 | mark::hit!(dont_duplicate_pattern_parens); | ||
388 | return self; | ||
389 | } | ||
390 | if ctx.is_call { | ||
391 | return self; | ||
392 | } | ||
393 | |||
394 | // Don't add parentheses if the expected type is some function reference. | ||
395 | if let Some(ty) = &ctx.expected_type { | ||
396 | if ty.is_fn() { | ||
397 | mark::hit!(no_call_parens_if_fn_ptr_needed); | ||
398 | return self; | ||
399 | } | ||
400 | } | ||
401 | |||
402 | let cap = match ctx.config.snippet_cap { | ||
403 | Some(it) => it, | ||
404 | None => return self, | ||
405 | }; | ||
406 | // If not an import, add parenthesis automatically. | ||
407 | mark::hit!(inserts_parens_for_function_calls); | ||
408 | |||
409 | let (snippet, label) = if params.is_empty() { | ||
410 | (format!("{}()$0", name), format!("{}()", name)) | ||
411 | } else { | ||
412 | self = self.trigger_call_info(); | ||
413 | let snippet = match (ctx.config.add_call_argument_snippets, params) { | ||
414 | (true, Params::Named(params)) => { | ||
415 | let function_params_snippet = | ||
416 | params.iter().enumerate().format_with(", ", |(index, param_name), f| { | ||
417 | f(&format_args!("${{{}:{}}}", index + 1, param_name)) | ||
418 | }); | ||
419 | format!("{}({})$0", name, function_params_snippet) | ||
420 | } | ||
421 | _ => { | ||
422 | mark::hit!(suppress_arg_snippets); | ||
423 | format!("{}($0)", name) | ||
424 | } | ||
425 | }; | ||
426 | |||
427 | (snippet, format!("{}(…)", name)) | ||
428 | }; | ||
429 | self.lookup_by(name).label(label).insert_snippet(cap, snippet) | ||
430 | } | ||
431 | } | ||
432 | |||
433 | fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool { | ||
434 | node.attrs(db).by_key("deprecated").exists() | ||
435 | } | ||
436 | |||
437 | fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) { | ||
438 | let mut votes = [0, 0, 0]; | ||
439 | for (idx, s) in docs.match_indices(¯o_name) { | ||
440 | let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); | ||
441 | // Ensure to match the full word | ||
442 | if after.starts_with('!') | ||
443 | && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric()) | ||
444 | { | ||
445 | // It may have spaces before the braces like `foo! {}` | ||
446 | match after[1..].chars().find(|&c| !c.is_whitespace()) { | ||
447 | Some('{') => votes[0] += 1, | ||
448 | Some('[') => votes[1] += 1, | ||
449 | Some('(') => votes[2] += 1, | ||
450 | _ => {} | ||
451 | } | ||
452 | } | ||
453 | } | ||
454 | |||
455 | // Insert a space before `{}`. | ||
456 | // We prefer the last one when some votes equal. | ||
457 | let (_vote, (bra, ket)) = votes | ||
458 | .iter() | ||
459 | .zip(&[(" {", "}"), ("[", "]"), ("(", ")")]) | ||
460 | .max_by_key(|&(&vote, _)| vote) | ||
461 | .unwrap(); | ||
462 | (*bra, *ket) | ||
463 | } | ||
464 | |||
465 | #[cfg(test)] | ||
466 | mod tests { | ||
467 | use std::cmp::Reverse; | ||
468 | |||
469 | use expect::{expect, Expect}; | ||
470 | use test_utils::mark; | ||
471 | |||
472 | use crate::{ | ||
473 | completion::{ | ||
474 | test_utils::{ | ||
475 | check_edit, check_edit_with_config, do_completion, get_all_completion_items, | ||
476 | }, | ||
477 | CompletionConfig, CompletionKind, | ||
478 | }, | ||
479 | CompletionScore, | ||
480 | }; | ||
481 | |||
482 | fn check(ra_fixture: &str, expect: Expect) { | ||
483 | let actual = do_completion(ra_fixture, CompletionKind::Reference); | ||
484 | expect.assert_debug_eq(&actual); | ||
485 | } | ||
486 | |||
487 | fn check_scores(ra_fixture: &str, expect: Expect) { | ||
488 | fn display_score(score: Option<CompletionScore>) -> &'static str { | ||
489 | match score { | ||
490 | Some(CompletionScore::TypeMatch) => "[type]", | ||
491 | Some(CompletionScore::TypeAndNameMatch) => "[type+name]", | ||
492 | None => "[]".into(), | ||
493 | } | ||
494 | } | ||
495 | |||
496 | let mut completions = get_all_completion_items(CompletionConfig::default(), ra_fixture); | ||
497 | completions.sort_by_key(|it| (Reverse(it.score()), it.label().to_string())); | ||
498 | let actual = completions | ||
499 | .into_iter() | ||
500 | .filter(|it| it.completion_kind == CompletionKind::Reference) | ||
501 | .map(|it| { | ||
502 | let tag = it.kind().unwrap().tag(); | ||
503 | let score = display_score(it.score()); | ||
504 | format!("{} {} {}\n", tag, it.label(), score) | ||
505 | }) | ||
506 | .collect::<String>(); | ||
507 | expect.assert_eq(&actual); | ||
508 | } | ||
509 | |||
510 | #[test] | ||
511 | fn enum_detail_includes_record_fields() { | ||
512 | check( | ||
513 | r#" | ||
514 | enum Foo { Foo { x: i32, y: i32 } } | ||
515 | |||
516 | fn main() { Foo::Fo<|> } | ||
517 | "#, | ||
518 | expect![[r#" | ||
519 | [ | ||
520 | CompletionItem { | ||
521 | label: "Foo", | ||
522 | source_range: 54..56, | ||
523 | delete: 54..56, | ||
524 | insert: "Foo", | ||
525 | kind: EnumVariant, | ||
526 | detail: "{ x: i32, y: i32 }", | ||
527 | }, | ||
528 | ] | ||
529 | "#]], | ||
530 | ); | ||
531 | } | ||
532 | |||
533 | #[test] | ||
534 | fn enum_detail_doesnt_include_tuple_fields() { | ||
535 | check( | ||
536 | r#" | ||
537 | enum Foo { Foo (i32, i32) } | ||
538 | |||
539 | fn main() { Foo::Fo<|> } | ||
540 | "#, | ||
541 | expect![[r#" | ||
542 | [ | ||
543 | CompletionItem { | ||
544 | label: "Foo(…)", | ||
545 | source_range: 46..48, | ||
546 | delete: 46..48, | ||
547 | insert: "Foo($0)", | ||
548 | kind: EnumVariant, | ||
549 | lookup: "Foo", | ||
550 | detail: "(i32, i32)", | ||
551 | trigger_call_info: true, | ||
552 | }, | ||
553 | ] | ||
554 | "#]], | ||
555 | ); | ||
556 | } | ||
557 | |||
558 | #[test] | ||
559 | fn enum_detail_just_parentheses_for_unit() { | ||
560 | check( | ||
561 | r#" | ||
562 | enum Foo { Foo } | ||
563 | |||
564 | fn main() { Foo::Fo<|> } | ||
565 | "#, | ||
566 | expect![[r#" | ||
567 | [ | ||
568 | CompletionItem { | ||
569 | label: "Foo", | ||
570 | source_range: 35..37, | ||
571 | delete: 35..37, | ||
572 | insert: "Foo", | ||
573 | kind: EnumVariant, | ||
574 | detail: "()", | ||
575 | }, | ||
576 | ] | ||
577 | "#]], | ||
578 | ); | ||
579 | } | ||
580 | |||
581 | #[test] | ||
582 | fn sets_deprecated_flag_in_completion_items() { | ||
583 | check( | ||
584 | r#" | ||
585 | #[deprecated] | ||
586 | fn something_deprecated() {} | ||
587 | #[deprecated(since = "1.0.0")] | ||
588 | fn something_else_deprecated() {} | ||
589 | |||
590 | fn main() { som<|> } | ||
591 | "#, | ||
592 | expect![[r#" | ||
593 | [ | ||
594 | CompletionItem { | ||
595 | label: "main()", | ||
596 | source_range: 121..124, | ||
597 | delete: 121..124, | ||
598 | insert: "main()$0", | ||
599 | kind: Function, | ||
600 | lookup: "main", | ||
601 | detail: "fn main()", | ||
602 | }, | ||
603 | CompletionItem { | ||
604 | label: "something_deprecated()", | ||
605 | source_range: 121..124, | ||
606 | delete: 121..124, | ||
607 | insert: "something_deprecated()$0", | ||
608 | kind: Function, | ||
609 | lookup: "something_deprecated", | ||
610 | detail: "fn something_deprecated()", | ||
611 | deprecated: true, | ||
612 | }, | ||
613 | CompletionItem { | ||
614 | label: "something_else_deprecated()", | ||
615 | source_range: 121..124, | ||
616 | delete: 121..124, | ||
617 | insert: "something_else_deprecated()$0", | ||
618 | kind: Function, | ||
619 | lookup: "something_else_deprecated", | ||
620 | detail: "fn something_else_deprecated()", | ||
621 | deprecated: true, | ||
622 | }, | ||
623 | ] | ||
624 | "#]], | ||
625 | ); | ||
626 | |||
627 | check( | ||
628 | r#" | ||
629 | struct A { #[deprecated] the_field: u32 } | ||
630 | fn foo() { A { the<|> } } | ||
631 | "#, | ||
632 | expect![[r#" | ||
633 | [ | ||
634 | CompletionItem { | ||
635 | label: "the_field", | ||
636 | source_range: 57..60, | ||
637 | delete: 57..60, | ||
638 | insert: "the_field", | ||
639 | kind: Field, | ||
640 | detail: "u32", | ||
641 | deprecated: true, | ||
642 | }, | ||
643 | ] | ||
644 | "#]], | ||
645 | ); | ||
646 | } | ||
647 | |||
648 | #[test] | ||
649 | fn renders_docs() { | ||
650 | check( | ||
651 | r#" | ||
652 | struct S { | ||
653 | /// Field docs | ||
654 | foo: | ||
655 | } | ||
656 | impl S { | ||
657 | /// Method docs | ||
658 | fn bar(self) { self.<|> } | ||
659 | }"#, | ||
660 | expect![[r#" | ||
661 | [ | ||
662 | CompletionItem { | ||
663 | label: "bar()", | ||
664 | source_range: 94..94, | ||
665 | delete: 94..94, | ||
666 | insert: "bar()$0", | ||
667 | kind: Method, | ||
668 | lookup: "bar", | ||
669 | detail: "fn bar(self)", | ||
670 | documentation: Documentation( | ||
671 | "Method docs", | ||
672 | ), | ||
673 | }, | ||
674 | CompletionItem { | ||
675 | label: "foo", | ||
676 | source_range: 94..94, | ||
677 | delete: 94..94, | ||
678 | insert: "foo", | ||
679 | kind: Field, | ||
680 | detail: "{unknown}", | ||
681 | documentation: Documentation( | ||
682 | "Field docs", | ||
683 | ), | ||
684 | }, | ||
685 | ] | ||
686 | "#]], | ||
687 | ); | ||
688 | |||
689 | check( | ||
690 | r#" | ||
691 | use self::my<|>; | ||
692 | |||
693 | /// mod docs | ||
694 | mod my { } | ||
695 | |||
696 | /// enum docs | ||
697 | enum E { | ||
698 | /// variant docs | ||
699 | V | ||
700 | } | ||
701 | use self::E::*; | ||
702 | "#, | ||
703 | expect![[r#" | ||
704 | [ | ||
705 | CompletionItem { | ||
706 | label: "E", | ||
707 | source_range: 10..12, | ||
708 | delete: 10..12, | ||
709 | insert: "E", | ||
710 | kind: Enum, | ||
711 | documentation: Documentation( | ||
712 | "enum docs", | ||
713 | ), | ||
714 | }, | ||
715 | CompletionItem { | ||
716 | label: "V", | ||
717 | source_range: 10..12, | ||
718 | delete: 10..12, | ||
719 | insert: "V", | ||
720 | kind: EnumVariant, | ||
721 | detail: "()", | ||
722 | documentation: Documentation( | ||
723 | "variant docs", | ||
724 | ), | ||
725 | }, | ||
726 | CompletionItem { | ||
727 | label: "my", | ||
728 | source_range: 10..12, | ||
729 | delete: 10..12, | ||
730 | insert: "my", | ||
731 | kind: Module, | ||
732 | documentation: Documentation( | ||
733 | "mod docs", | ||
734 | ), | ||
735 | }, | ||
736 | ] | ||
737 | "#]], | ||
738 | ) | ||
739 | } | ||
740 | |||
741 | #[test] | ||
742 | fn dont_render_attrs() { | ||
743 | check( | ||
744 | r#" | ||
745 | struct S; | ||
746 | impl S { | ||
747 | #[inline] | ||
748 | fn the_method(&self) { } | ||
749 | } | ||
750 | fn foo(s: S) { s.<|> } | ||
751 | "#, | ||
752 | expect![[r#" | ||
753 | [ | ||
754 | CompletionItem { | ||
755 | label: "the_method()", | ||
756 | source_range: 81..81, | ||
757 | delete: 81..81, | ||
758 | insert: "the_method()$0", | ||
759 | kind: Method, | ||
760 | lookup: "the_method", | ||
761 | detail: "fn the_method(&self)", | ||
762 | }, | ||
763 | ] | ||
764 | "#]], | ||
765 | ) | ||
766 | } | ||
767 | |||
768 | #[test] | ||
769 | fn inserts_parens_for_function_calls() { | ||
770 | mark::check!(inserts_parens_for_function_calls); | ||
771 | check_edit( | ||
772 | "no_args", | ||
773 | r#" | ||
774 | fn no_args() {} | ||
775 | fn main() { no_<|> } | ||
776 | "#, | ||
777 | r#" | ||
778 | fn no_args() {} | ||
779 | fn main() { no_args()$0 } | ||
780 | "#, | ||
781 | ); | ||
782 | |||
783 | check_edit( | ||
784 | "with_args", | ||
785 | r#" | ||
786 | fn with_args(x: i32, y: String) {} | ||
787 | fn main() { with_<|> } | ||
788 | "#, | ||
789 | r#" | ||
790 | fn with_args(x: i32, y: String) {} | ||
791 | fn main() { with_args(${1:x}, ${2:y})$0 } | ||
792 | "#, | ||
793 | ); | ||
794 | |||
795 | check_edit( | ||
796 | "foo", | ||
797 | r#" | ||
798 | struct S; | ||
799 | impl S { | ||
800 | fn foo(&self) {} | ||
801 | } | ||
802 | fn bar(s: &S) { s.f<|> } | ||
803 | "#, | ||
804 | r#" | ||
805 | struct S; | ||
806 | impl S { | ||
807 | fn foo(&self) {} | ||
808 | } | ||
809 | fn bar(s: &S) { s.foo()$0 } | ||
810 | "#, | ||
811 | ); | ||
812 | |||
813 | check_edit( | ||
814 | "foo", | ||
815 | r#" | ||
816 | struct S {} | ||
817 | impl S { | ||
818 | fn foo(&self, x: i32) {} | ||
819 | } | ||
820 | fn bar(s: &S) { | ||
821 | s.f<|> | ||
822 | } | ||
823 | "#, | ||
824 | r#" | ||
825 | struct S {} | ||
826 | impl S { | ||
827 | fn foo(&self, x: i32) {} | ||
828 | } | ||
829 | fn bar(s: &S) { | ||
830 | s.foo(${1:x})$0 | ||
831 | } | ||
832 | "#, | ||
833 | ); | ||
834 | } | ||
835 | |||
836 | #[test] | ||
837 | fn suppress_arg_snippets() { | ||
838 | mark::check!(suppress_arg_snippets); | ||
839 | check_edit_with_config( | ||
840 | CompletionConfig { add_call_argument_snippets: false, ..CompletionConfig::default() }, | ||
841 | "with_args", | ||
842 | r#" | ||
843 | fn with_args(x: i32, y: String) {} | ||
844 | fn main() { with_<|> } | ||
845 | "#, | ||
846 | r#" | ||
847 | fn with_args(x: i32, y: String) {} | ||
848 | fn main() { with_args($0) } | ||
849 | "#, | ||
850 | ); | ||
851 | } | ||
852 | |||
853 | #[test] | ||
854 | fn strips_underscores_from_args() { | ||
855 | check_edit( | ||
856 | "foo", | ||
857 | r#" | ||
858 | fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {} | ||
859 | fn main() { f<|> } | ||
860 | "#, | ||
861 | r#" | ||
862 | fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {} | ||
863 | fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 } | ||
864 | "#, | ||
865 | ); | ||
866 | } | ||
867 | |||
868 | #[test] | ||
869 | fn inserts_parens_for_tuple_enums() { | ||
870 | mark::check!(inserts_parens_for_tuple_enums); | ||
871 | check_edit( | ||
872 | "Some", | ||
873 | r#" | ||
874 | enum Option<T> { Some(T), None } | ||
875 | use Option::*; | ||
876 | fn main() -> Option<i32> { | ||
877 | Som<|> | ||
878 | } | ||
879 | "#, | ||
880 | r#" | ||
881 | enum Option<T> { Some(T), None } | ||
882 | use Option::*; | ||
883 | fn main() -> Option<i32> { | ||
884 | Some($0) | ||
885 | } | ||
886 | "#, | ||
887 | ); | ||
888 | check_edit( | ||
889 | "Some", | ||
890 | r#" | ||
891 | enum Option<T> { Some(T), None } | ||
892 | use Option::*; | ||
893 | fn main(value: Option<i32>) { | ||
894 | match value { | ||
895 | Som<|> | ||
896 | } | ||
897 | } | ||
898 | "#, | ||
899 | r#" | ||
900 | enum Option<T> { Some(T), None } | ||
901 | use Option::*; | ||
902 | fn main(value: Option<i32>) { | ||
903 | match value { | ||
904 | Some($0) | ||
905 | } | ||
906 | } | ||
907 | "#, | ||
908 | ); | ||
909 | } | ||
910 | |||
911 | #[test] | ||
912 | fn dont_duplicate_pattern_parens() { | ||
913 | mark::check!(dont_duplicate_pattern_parens); | ||
914 | check_edit( | ||
915 | "Var", | ||
916 | r#" | ||
917 | enum E { Var(i32) } | ||
918 | fn main() { | ||
919 | match E::Var(92) { | ||
920 | E::<|>(92) => (), | ||
921 | } | ||
922 | } | ||
923 | "#, | ||
924 | r#" | ||
925 | enum E { Var(i32) } | ||
926 | fn main() { | ||
927 | match E::Var(92) { | ||
928 | E::Var(92) => (), | ||
929 | } | ||
930 | } | ||
931 | "#, | ||
932 | ); | ||
933 | } | ||
934 | |||
935 | #[test] | ||
936 | fn no_call_parens_if_fn_ptr_needed() { | ||
937 | mark::check!(no_call_parens_if_fn_ptr_needed); | ||
938 | check_edit( | ||
939 | "foo", | ||
940 | r#" | ||
941 | fn foo(foo: u8, bar: u8) {} | ||
942 | struct ManualVtable { f: fn(u8, u8) } | ||
943 | |||
944 | fn main() -> ManualVtable { | ||
945 | ManualVtable { f: f<|> } | ||
946 | } | ||
947 | "#, | ||
948 | r#" | ||
949 | fn foo(foo: u8, bar: u8) {} | ||
950 | struct ManualVtable { f: fn(u8, u8) } | ||
951 | |||
952 | fn main() -> ManualVtable { | ||
953 | ManualVtable { f: foo } | ||
954 | } | ||
955 | "#, | ||
956 | ); | ||
957 | } | ||
958 | |||
959 | #[test] | ||
960 | fn no_parens_in_use_item() { | ||
961 | mark::check!(no_parens_in_use_item); | ||
962 | check_edit( | ||
963 | "foo", | ||
964 | r#" | ||
965 | mod m { pub fn foo() {} } | ||
966 | use crate::m::f<|>; | ||
967 | "#, | ||
968 | r#" | ||
969 | mod m { pub fn foo() {} } | ||
970 | use crate::m::foo; | ||
971 | "#, | ||
972 | ); | ||
973 | } | ||
974 | |||
975 | #[test] | ||
976 | fn no_parens_in_call() { | ||
977 | check_edit( | ||
978 | "foo", | ||
979 | r#" | ||
980 | fn foo(x: i32) {} | ||
981 | fn main() { f<|>(); } | ||
982 | "#, | ||
983 | r#" | ||
984 | fn foo(x: i32) {} | ||
985 | fn main() { foo(); } | ||
986 | "#, | ||
987 | ); | ||
988 | check_edit( | ||
989 | "foo", | ||
990 | r#" | ||
991 | struct Foo; | ||
992 | impl Foo { fn foo(&self){} } | ||
993 | fn f(foo: &Foo) { foo.f<|>(); } | ||
994 | "#, | ||
995 | r#" | ||
996 | struct Foo; | ||
997 | impl Foo { fn foo(&self){} } | ||
998 | fn f(foo: &Foo) { foo.foo(); } | ||
999 | "#, | ||
1000 | ); | ||
1001 | } | ||
1002 | |||
1003 | #[test] | ||
1004 | fn inserts_angle_brackets_for_generics() { | ||
1005 | mark::check!(inserts_angle_brackets_for_generics); | ||
1006 | check_edit( | ||
1007 | "Vec", | ||
1008 | r#" | ||
1009 | struct Vec<T> {} | ||
1010 | fn foo(xs: Ve<|>) | ||
1011 | "#, | ||
1012 | r#" | ||
1013 | struct Vec<T> {} | ||
1014 | fn foo(xs: Vec<$0>) | ||
1015 | "#, | ||
1016 | ); | ||
1017 | check_edit( | ||
1018 | "Vec", | ||
1019 | r#" | ||
1020 | type Vec<T> = (T,); | ||
1021 | fn foo(xs: Ve<|>) | ||
1022 | "#, | ||
1023 | r#" | ||
1024 | type Vec<T> = (T,); | ||
1025 | fn foo(xs: Vec<$0>) | ||
1026 | "#, | ||
1027 | ); | ||
1028 | check_edit( | ||
1029 | "Vec", | ||
1030 | r#" | ||
1031 | struct Vec<T = i128> {} | ||
1032 | fn foo(xs: Ve<|>) | ||
1033 | "#, | ||
1034 | r#" | ||
1035 | struct Vec<T = i128> {} | ||
1036 | fn foo(xs: Vec) | ||
1037 | "#, | ||
1038 | ); | ||
1039 | check_edit( | ||
1040 | "Vec", | ||
1041 | r#" | ||
1042 | struct Vec<T> {} | ||
1043 | fn foo(xs: Ve<|><i128>) | ||
1044 | "#, | ||
1045 | r#" | ||
1046 | struct Vec<T> {} | ||
1047 | fn foo(xs: Vec<i128>) | ||
1048 | "#, | ||
1049 | ); | ||
1050 | } | ||
1051 | |||
1052 | #[test] | ||
1053 | fn dont_insert_macro_call_parens_unncessary() { | ||
1054 | mark::check!(dont_insert_macro_call_parens_unncessary); | ||
1055 | check_edit( | ||
1056 | "frobnicate!", | ||
1057 | r#" | ||
1058 | //- /main.rs | ||
1059 | use foo::<|>; | ||
1060 | //- /foo/lib.rs | ||
1061 | #[macro_export] | ||
1062 | macro_rules frobnicate { () => () } | ||
1063 | "#, | ||
1064 | r#" | ||
1065 | use foo::frobnicate; | ||
1066 | "#, | ||
1067 | ); | ||
1068 | |||
1069 | check_edit( | ||
1070 | "frobnicate!", | ||
1071 | r#" | ||
1072 | macro_rules frobnicate { () => () } | ||
1073 | fn main() { frob<|>!(); } | ||
1074 | "#, | ||
1075 | r#" | ||
1076 | macro_rules frobnicate { () => () } | ||
1077 | fn main() { frobnicate!(); } | ||
1078 | "#, | ||
1079 | ); | ||
1080 | } | ||
1081 | |||
1082 | #[test] | ||
1083 | fn active_param_score() { | ||
1084 | mark::check!(active_param_type_match); | ||
1085 | check_scores( | ||
1086 | r#" | ||
1087 | struct S { foo: i64, bar: u32, baz: u32 } | ||
1088 | fn test(bar: u32) { } | ||
1089 | fn foo(s: S) { test(s.<|>) } | ||
1090 | "#, | ||
1091 | expect![[r#" | ||
1092 | fd bar [type+name] | ||
1093 | fd baz [type] | ||
1094 | fd foo [] | ||
1095 | "#]], | ||
1096 | ); | ||
1097 | } | ||
1098 | |||
1099 | #[test] | ||
1100 | fn record_field_scores() { | ||
1101 | mark::check!(record_field_type_match); | ||
1102 | check_scores( | ||
1103 | r#" | ||
1104 | struct A { foo: i64, bar: u32, baz: u32 } | ||
1105 | struct B { x: (), y: f32, bar: u32 } | ||
1106 | fn foo(a: A) { B { bar: a.<|> }; } | ||
1107 | "#, | ||
1108 | expect![[r#" | ||
1109 | fd bar [type+name] | ||
1110 | fd baz [type] | ||
1111 | fd foo [] | ||
1112 | "#]], | ||
1113 | ) | ||
1114 | } | ||
1115 | |||
1116 | #[test] | ||
1117 | fn record_field_and_call_scores() { | ||
1118 | check_scores( | ||
1119 | r#" | ||
1120 | struct A { foo: i64, bar: u32, baz: u32 } | ||
1121 | struct B { x: (), y: f32, bar: u32 } | ||
1122 | fn f(foo: i64) { } | ||
1123 | fn foo(a: A) { B { bar: f(a.<|>) }; } | ||
1124 | "#, | ||
1125 | expect![[r#" | ||
1126 | fd foo [type+name] | ||
1127 | fd bar [] | ||
1128 | fd baz [] | ||
1129 | "#]], | ||
1130 | ); | ||
1131 | check_scores( | ||
1132 | r#" | ||
1133 | struct A { foo: i64, bar: u32, baz: u32 } | ||
1134 | struct B { x: (), y: f32, bar: u32 } | ||
1135 | fn f(foo: i64) { } | ||
1136 | fn foo(a: A) { f(B { bar: a.<|> }); } | ||
1137 | "#, | ||
1138 | expect![[r#" | ||
1139 | fd bar [type+name] | ||
1140 | fd baz [type] | ||
1141 | fd foo [] | ||
1142 | "#]], | ||
1143 | ); | ||
1144 | } | ||
1145 | |||
1146 | #[test] | ||
1147 | fn prioritize_exact_ref_match() { | ||
1148 | check_scores( | ||
1149 | r#" | ||
1150 | struct WorldSnapshot { _f: () }; | ||
1151 | fn go(world: &WorldSnapshot) { go(w<|>) } | ||
1152 | "#, | ||
1153 | expect![[r#" | ||
1154 | bn world [type+name] | ||
1155 | st WorldSnapshot [] | ||
1156 | fn go(…) [] | ||
1157 | "#]], | ||
1158 | ); | ||
1159 | } | ||
1160 | |||
1161 | #[test] | ||
1162 | fn too_many_arguments() { | ||
1163 | mark::check!(too_many_arguments); | ||
1164 | check_scores( | ||
1165 | r#" | ||
1166 | struct Foo; | ||
1167 | fn f(foo: &Foo) { f(foo, w<|>) } | ||
1168 | "#, | ||
1169 | expect![[r#" | ||
1170 | st Foo [] | ||
1171 | fn f(…) [] | ||
1172 | bn foo [] | ||
1173 | "#]], | ||
1174 | ); | ||
1175 | } | ||
1176 | |||
1177 | #[test] | ||
1178 | fn guesses_macro_braces() { | ||
1179 | check_edit( | ||
1180 | "vec!", | ||
1181 | r#" | ||
1182 | /// Creates a [`Vec`] containing the arguments. | ||
1183 | /// | ||
1184 | /// ``` | ||
1185 | /// let v = vec![1, 2, 3]; | ||
1186 | /// assert_eq!(v[0], 1); | ||
1187 | /// assert_eq!(v[1], 2); | ||
1188 | /// assert_eq!(v[2], 3); | ||
1189 | /// ``` | ||
1190 | macro_rules! vec { () => {} } | ||
1191 | |||
1192 | fn fn main() { v<|> } | ||
1193 | "#, | ||
1194 | r#" | ||
1195 | /// Creates a [`Vec`] containing the arguments. | ||
1196 | /// | ||
1197 | /// ``` | ||
1198 | /// let v = vec![1, 2, 3]; | ||
1199 | /// assert_eq!(v[0], 1); | ||
1200 | /// assert_eq!(v[1], 2); | ||
1201 | /// assert_eq!(v[2], 3); | ||
1202 | /// ``` | ||
1203 | macro_rules! vec { () => {} } | ||
1204 | |||
1205 | fn fn main() { vec![$0] } | ||
1206 | "#, | ||
1207 | ); | ||
1208 | |||
1209 | check_edit( | ||
1210 | "foo!", | ||
1211 | r#" | ||
1212 | /// Foo | ||
1213 | /// | ||
1214 | /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`, | ||
1215 | /// call as `let _=foo! { hello world };` | ||
1216 | macro_rules! foo { () => {} } | ||
1217 | fn main() { <|> } | ||
1218 | "#, | ||
1219 | r#" | ||
1220 | /// Foo | ||
1221 | /// | ||
1222 | /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`, | ||
1223 | /// call as `let _=foo! { hello world };` | ||
1224 | macro_rules! foo { () => {} } | ||
1225 | fn main() { foo! {$0} } | ||
1226 | "#, | ||
1227 | ) | ||
1228 | } | ||
1229 | } | ||
diff --git a/crates/ide/src/completion/test_utils.rs b/crates/ide/src/completion/test_utils.rs new file mode 100644 index 000000000..1452d7e9e --- /dev/null +++ b/crates/ide/src/completion/test_utils.rs | |||
@@ -0,0 +1,114 @@ | |||
1 | //! Runs completion for testing purposes. | ||
2 | |||
3 | use hir::Semantics; | ||
4 | use itertools::Itertools; | ||
5 | use stdx::{format_to, trim_indent}; | ||
6 | use syntax::{AstNode, NodeOrToken, SyntaxElement}; | ||
7 | use test_utils::assert_eq_text; | ||
8 | |||
9 | use crate::{ | ||
10 | completion::{completion_item::CompletionKind, CompletionConfig}, | ||
11 | mock_analysis::analysis_and_position, | ||
12 | CompletionItem, | ||
13 | }; | ||
14 | |||
15 | pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { | ||
16 | do_completion_with_config(CompletionConfig::default(), code, kind) | ||
17 | } | ||
18 | |||
19 | pub(crate) fn do_completion_with_config( | ||
20 | config: CompletionConfig, | ||
21 | code: &str, | ||
22 | kind: CompletionKind, | ||
23 | ) -> Vec<CompletionItem> { | ||
24 | let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(config, code) | ||
25 | .into_iter() | ||
26 | .filter(|c| c.completion_kind == kind) | ||
27 | .collect(); | ||
28 | kind_completions.sort_by(|l, r| l.label().cmp(r.label())); | ||
29 | kind_completions | ||
30 | } | ||
31 | |||
32 | pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String { | ||
33 | completion_list_with_config(CompletionConfig::default(), code, kind) | ||
34 | } | ||
35 | |||
36 | pub(crate) fn completion_list_with_config( | ||
37 | config: CompletionConfig, | ||
38 | code: &str, | ||
39 | kind: CompletionKind, | ||
40 | ) -> String { | ||
41 | let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(config, code) | ||
42 | .into_iter() | ||
43 | .filter(|c| c.completion_kind == kind) | ||
44 | .collect(); | ||
45 | kind_completions.sort_by_key(|c| c.label().to_owned()); | ||
46 | let label_width = kind_completions | ||
47 | .iter() | ||
48 | .map(|it| monospace_width(it.label())) | ||
49 | .max() | ||
50 | .unwrap_or_default() | ||
51 | .min(16); | ||
52 | kind_completions | ||
53 | .into_iter() | ||
54 | .map(|it| { | ||
55 | let tag = it.kind().unwrap().tag(); | ||
56 | let var_name = format!("{} {}", tag, it.label()); | ||
57 | let mut buf = var_name; | ||
58 | if let Some(detail) = it.detail() { | ||
59 | let width = label_width.saturating_sub(monospace_width(it.label())); | ||
60 | format_to!(buf, "{:width$} {}", "", detail, width = width); | ||
61 | } | ||
62 | format_to!(buf, "\n"); | ||
63 | buf | ||
64 | }) | ||
65 | .collect() | ||
66 | } | ||
67 | |||
68 | fn monospace_width(s: &str) -> usize { | ||
69 | s.chars().count() | ||
70 | } | ||
71 | |||
72 | pub(crate) fn check_edit(what: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | ||
73 | check_edit_with_config(CompletionConfig::default(), what, ra_fixture_before, ra_fixture_after) | ||
74 | } | ||
75 | |||
76 | pub(crate) fn check_edit_with_config( | ||
77 | config: CompletionConfig, | ||
78 | what: &str, | ||
79 | ra_fixture_before: &str, | ||
80 | ra_fixture_after: &str, | ||
81 | ) { | ||
82 | let ra_fixture_after = trim_indent(ra_fixture_after); | ||
83 | let (analysis, position) = analysis_and_position(ra_fixture_before); | ||
84 | let completions: Vec<CompletionItem> = | ||
85 | analysis.completions(&config, position).unwrap().unwrap().into(); | ||
86 | let (completion,) = completions | ||
87 | .iter() | ||
88 | .filter(|it| it.lookup() == what) | ||
89 | .collect_tuple() | ||
90 | .unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions)); | ||
91 | let mut actual = analysis.file_text(position.file_id).unwrap().to_string(); | ||
92 | completion.text_edit().apply(&mut actual); | ||
93 | assert_eq_text!(&ra_fixture_after, &actual) | ||
94 | } | ||
95 | |||
96 | pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) { | ||
97 | let (analysis, pos) = analysis_and_position(code); | ||
98 | analysis | ||
99 | .with_db(|db| { | ||
100 | let sema = Semantics::new(db); | ||
101 | let original_file = sema.parse(pos.file_id); | ||
102 | let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap(); | ||
103 | assert!(check(NodeOrToken::Token(token))); | ||
104 | }) | ||
105 | .unwrap(); | ||
106 | } | ||
107 | |||
108 | pub(crate) fn get_all_completion_items( | ||
109 | config: CompletionConfig, | ||
110 | code: &str, | ||
111 | ) -> Vec<CompletionItem> { | ||
112 | let (analysis, position) = analysis_and_position(code); | ||
113 | analysis.completions(&config, position).unwrap().unwrap().into() | ||
114 | } | ||