diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2018-12-21 22:04:32 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2018-12-21 22:04:32 +0000 |
commit | 184665ff9b7b64730ecf481c1914a74e7191a6dd (patch) | |
tree | 4f5e97a0832821a4b06b784591d6cb3a417f1198 /crates/ra_analysis/src/completion.rs | |
parent | 2351308d92f4c785d98cc24026a818d28945513e (diff) | |
parent | 2ae87ffc9afe67945e2ad655c3577b589ff640ab (diff) |
Merge #315
315: Split completion into manageable components r=matklad a=matklad
The main idea here is to do completion in two phases:
* first, we figure out surrounding context
* then, we run a series of completers on the given context.
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_analysis/src/completion.rs')
-rw-r--r-- | crates/ra_analysis/src/completion.rs | 164 |
1 files changed, 27 insertions, 137 deletions
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index a11e98ac0..2d61a3aef 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs | |||
@@ -1,112 +1,50 @@ | |||
1 | mod completion_item; | 1 | mod completion_item; |
2 | mod reference_completion; | 2 | mod completion_context; |
3 | |||
4 | mod complete_fn_param; | ||
5 | mod complete_keyword; | ||
6 | mod complete_snippet; | ||
7 | mod complete_path; | ||
8 | mod complete_scope; | ||
3 | 9 | ||
4 | use ra_editor::find_node_at_offset; | ||
5 | use ra_text_edit::AtomTextEdit; | ||
6 | use ra_syntax::{ | ||
7 | algo::visit::{visitor_ctx, VisitorCtx}, | ||
8 | ast, | ||
9 | AstNode, | ||
10 | SyntaxNodeRef, | ||
11 | }; | ||
12 | use ra_db::SyntaxDatabase; | 10 | use ra_db::SyntaxDatabase; |
13 | use rustc_hash::{FxHashMap}; | ||
14 | use hir::source_binder; | ||
15 | 11 | ||
16 | use crate::{ | 12 | use crate::{ |
17 | db, | 13 | db, |
18 | Cancelable, FilePosition, | 14 | Cancelable, FilePosition, |
19 | completion::completion_item::{Completions, CompletionKind}, | 15 | completion::{ |
16 | completion_item::{Completions, CompletionKind}, | ||
17 | completion_context::CompletionContext, | ||
18 | }, | ||
20 | }; | 19 | }; |
21 | 20 | ||
22 | pub use crate::completion::completion_item::{CompletionItem, InsertText}; | 21 | pub use crate::completion::completion_item::{CompletionItem, InsertText}; |
23 | 22 | ||
23 | /// Main entry point for copmletion. We run comletion as a two-phase process. | ||
24 | /// | ||
25 | /// First, we look at the position and collect a so-called `CompletionContext. | ||
26 | /// This is a somewhat messy process, because, during completion, syntax tree is | ||
27 | /// incomplete and can look readlly weired. | ||
28 | /// | ||
29 | /// Once the context is collected, we run a series of completion routines whihc | ||
30 | /// look at the context and produce completion items. | ||
24 | pub(crate) fn completions( | 31 | pub(crate) fn completions( |
25 | db: &db::RootDatabase, | 32 | db: &db::RootDatabase, |
26 | position: FilePosition, | 33 | position: FilePosition, |
27 | ) -> Cancelable<Option<Completions>> { | 34 | ) -> Cancelable<Option<Completions>> { |
28 | let original_file = db.source_file(position.file_id); | 35 | let original_file = db.source_file(position.file_id); |
29 | // Insert a fake ident to get a valid parse tree | 36 | let ctx = ctry!(CompletionContext::new(db, &original_file, position)?); |
30 | let file = { | ||
31 | let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); | ||
32 | original_file.reparse(&edit) | ||
33 | }; | ||
34 | |||
35 | let module = ctry!(source_binder::module_from_position(db, position)?); | ||
36 | 37 | ||
37 | let mut acc = Completions::default(); | 38 | let mut acc = Completions::default(); |
38 | let mut has_completions = false; | ||
39 | // First, let's try to complete a reference to some declaration. | ||
40 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) { | ||
41 | has_completions = true; | ||
42 | reference_completion::completions(&mut acc, db, &module, &file, name_ref)?; | ||
43 | // special case, `trait T { fn foo(i_am_a_name_ref) {} }` | ||
44 | if is_node::<ast::Param>(name_ref.syntax()) { | ||
45 | param_completions(&mut acc, name_ref.syntax()); | ||
46 | } | ||
47 | } | ||
48 | 39 | ||
49 | // Otherwise, if this is a declaration, use heuristics to suggest a name. | 40 | complete_fn_param::complete_fn_param(&mut acc, &ctx); |
50 | if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) { | 41 | complete_keyword::complete_expr_keyword(&mut acc, &ctx); |
51 | if is_node::<ast::Param>(name.syntax()) { | 42 | complete_snippet::complete_expr_snippet(&mut acc, &ctx); |
52 | has_completions = true; | 43 | complete_snippet::complete_item_snippet(&mut acc, &ctx); |
53 | param_completions(&mut acc, name.syntax()); | 44 | complete_path::complete_path(&mut acc, &ctx)?; |
54 | } | 45 | complete_scope::complete_scope(&mut acc, &ctx)?; |
55 | } | ||
56 | if !has_completions { | ||
57 | return Ok(None); | ||
58 | } | ||
59 | Ok(Some(acc)) | ||
60 | } | ||
61 | |||
62 | /// Complete repeated parametes, both name and type. For example, if all | ||
63 | /// functions in a file have a `spam: &mut Spam` parameter, a completion with | ||
64 | /// `spam: &mut Spam` insert text/label and `spam` lookup string will be | ||
65 | /// suggested. | ||
66 | fn param_completions(acc: &mut Completions, ctx: SyntaxNodeRef) { | ||
67 | let mut params = FxHashMap::default(); | ||
68 | for node in ctx.ancestors() { | ||
69 | let _ = visitor_ctx(&mut params) | ||
70 | .visit::<ast::SourceFile, _>(process) | ||
71 | .visit::<ast::ItemList, _>(process) | ||
72 | .accept(node); | ||
73 | } | ||
74 | params | ||
75 | .into_iter() | ||
76 | .filter_map(|(label, (count, param))| { | ||
77 | let lookup = param.pat()?.syntax().text().to_string(); | ||
78 | if count < 2 { | ||
79 | None | ||
80 | } else { | ||
81 | Some((label, lookup)) | ||
82 | } | ||
83 | }) | ||
84 | .for_each(|(label, lookup)| { | ||
85 | CompletionItem::new(label) | ||
86 | .lookup_by(lookup) | ||
87 | .kind(CompletionKind::Magic) | ||
88 | .add_to(acc) | ||
89 | }); | ||
90 | |||
91 | fn process<'a, N: ast::FnDefOwner<'a>>( | ||
92 | node: N, | ||
93 | params: &mut FxHashMap<String, (u32, ast::Param<'a>)>, | ||
94 | ) { | ||
95 | node.functions() | ||
96 | .filter_map(|it| it.param_list()) | ||
97 | .flat_map(|it| it.params()) | ||
98 | .for_each(|param| { | ||
99 | let text = param.syntax().text().to_string(); | ||
100 | params.entry(text).or_insert((0, param)).0 += 1; | ||
101 | }) | ||
102 | } | ||
103 | } | ||
104 | 46 | ||
105 | fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { | 47 | Ok(Some(acc)) |
106 | match node.ancestors().filter_map(N::cast).next() { | ||
107 | None => false, | ||
108 | Some(n) => n.syntax().range() == node.range(), | ||
109 | } | ||
110 | } | 48 | } |
111 | 49 | ||
112 | #[cfg(test)] | 50 | #[cfg(test)] |
@@ -120,51 +58,3 @@ fn check_completion(code: &str, expected_completions: &str, kind: CompletionKind | |||
120 | let completions = completions(&analysis.imp.db, position).unwrap().unwrap(); | 58 | let completions = completions(&analysis.imp.db, position).unwrap().unwrap(); |
121 | completions.assert_match(expected_completions, kind); | 59 | completions.assert_match(expected_completions, kind); |
122 | } | 60 | } |
123 | |||
124 | #[cfg(test)] | ||
125 | mod tests { | ||
126 | use super::*; | ||
127 | |||
128 | fn check_magic_completion(code: &str, expected_completions: &str) { | ||
129 | check_completion(code, expected_completions, CompletionKind::Magic); | ||
130 | } | ||
131 | |||
132 | #[test] | ||
133 | fn test_param_completion_last_param() { | ||
134 | check_magic_completion( | ||
135 | r" | ||
136 | fn foo(file_id: FileId) {} | ||
137 | fn bar(file_id: FileId) {} | ||
138 | fn baz(file<|>) {} | ||
139 | ", | ||
140 | r#"file_id "file_id: FileId""#, | ||
141 | ); | ||
142 | } | ||
143 | |||
144 | #[test] | ||
145 | fn test_param_completion_nth_param() { | ||
146 | check_magic_completion( | ||
147 | r" | ||
148 | fn foo(file_id: FileId) {} | ||
149 | fn bar(file_id: FileId) {} | ||
150 | fn baz(file<|>, x: i32) {} | ||
151 | ", | ||
152 | r#"file_id "file_id: FileId""#, | ||
153 | ); | ||
154 | } | ||
155 | |||
156 | #[test] | ||
157 | fn test_param_completion_trait_param() { | ||
158 | check_magic_completion( | ||
159 | r" | ||
160 | pub(crate) trait SourceRoot { | ||
161 | pub fn contains(&self, file_id: FileId) -> bool; | ||
162 | pub fn module_map(&self) -> &ModuleMap; | ||
163 | pub fn lines(&self, file_id: FileId) -> &LineIndex; | ||
164 | pub fn syntax(&self, file<|>) | ||
165 | } | ||
166 | ", | ||
167 | r#"file_id "file_id: FileId""#, | ||
168 | ); | ||
169 | } | ||
170 | } | ||