diff options
Diffstat (limited to 'crates/ra_analysis/src/completion.rs')
-rw-r--r-- | crates/ra_analysis/src/completion.rs | 225 |
1 files changed, 108 insertions, 117 deletions
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index ae1280256..39066d51f 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs | |||
@@ -1,18 +1,23 @@ | |||
1 | mod completion_item; | 1 | mod completion_item; |
2 | mod reference_completion; | 2 | mod reference_completion; |
3 | 3 | ||
4 | mod complete_fn_param; | ||
5 | mod complete_keywords; | ||
6 | |||
4 | use ra_editor::find_node_at_offset; | 7 | use ra_editor::find_node_at_offset; |
5 | use ra_text_edit::AtomTextEdit; | 8 | use ra_text_edit::AtomTextEdit; |
6 | use ra_syntax::{ | 9 | use ra_syntax::{ |
7 | algo::visit::{visitor_ctx, VisitorCtx}, | 10 | algo::{ |
11 | find_leaf_at_offset, | ||
12 | }, | ||
8 | ast, | 13 | ast, |
9 | AstNode, | 14 | AstNode, |
10 | SyntaxNodeRef, | 15 | SyntaxNodeRef, |
11 | SourceFileNode, | 16 | SourceFileNode, |
12 | TextUnit, | 17 | TextUnit, |
18 | SyntaxKind::*, | ||
13 | }; | 19 | }; |
14 | use ra_db::SyntaxDatabase; | 20 | use ra_db::SyntaxDatabase; |
15 | use rustc_hash::{FxHashMap}; | ||
16 | use hir::source_binder; | 21 | use hir::source_binder; |
17 | 22 | ||
18 | use crate::{ | 23 | use crate::{ |
@@ -29,99 +34,133 @@ pub(crate) fn completions( | |||
29 | ) -> Cancelable<Option<Completions>> { | 34 | ) -> Cancelable<Option<Completions>> { |
30 | let original_file = db.source_file(position.file_id); | 35 | let original_file = db.source_file(position.file_id); |
31 | // Insert a fake ident to get a valid parse tree | 36 | // Insert a fake ident to get a valid parse tree |
37 | let file = { | ||
38 | let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); | ||
39 | original_file.reparse(&edit) | ||
40 | }; | ||
32 | let module = ctry!(source_binder::module_from_position(db, position)?); | 41 | let module = ctry!(source_binder::module_from_position(db, position)?); |
33 | 42 | ||
34 | let mut acc = Completions::default(); | 43 | let mut acc = Completions::default(); |
35 | let mut has_completions = false; | 44 | |
36 | // First, let's try to complete a reference to some declaration. | 45 | // First, let's try to complete a reference to some declaration. |
37 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) { | 46 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) { |
38 | has_completions = true; | ||
39 | reference_completion::completions(&mut acc, db, &module, &file, name_ref)?; | 47 | reference_completion::completions(&mut acc, db, &module, &file, name_ref)?; |
40 | // special case, `trait T { fn foo(i_am_a_name_ref) {} }` | ||
41 | if is_node::<ast::Param>(name_ref.syntax()) { | ||
42 | param_completions(&mut acc, name_ref.syntax()); | ||
43 | } | ||
44 | } | 48 | } |
45 | 49 | ||
46 | // Otherwise, if this is a declaration, use heuristics to suggest a name. | 50 | let ctx = ctry!(SyntaxContext::new(&original_file, position.offset)); |
47 | if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) { | 51 | complete_fn_param::complete_fn_param(&mut acc, &ctx); |
48 | if is_node::<ast::Param>(name.syntax()) { | 52 | complete_keywords::complete_expr_keyword(&mut acc, &ctx); |
49 | has_completions = true; | 53 | |
50 | param_completions(&mut acc, name.syntax()); | ||
51 | } | ||
52 | } | ||
53 | if !has_completions { | ||
54 | return Ok(None); | ||
55 | } | ||
56 | Ok(Some(acc)) | 54 | Ok(Some(acc)) |
57 | } | 55 | } |
58 | 56 | ||
59 | /// `SyntaxContext` is created early during completion to figure out, where | 57 | /// `SyntaxContext` is created early during completion to figure out, where |
60 | /// exactly is the cursor, syntax-wise. | 58 | /// exactly is the cursor, syntax-wise. |
61 | #[derive(Debug)] | 59 | #[derive(Debug)] |
62 | pub(super) enum SyntaxContext<'a> { | 60 | pub(super) struct SyntaxContext<'a> { |
63 | ParameterName(SyntaxNodeRef<'a>), | 61 | leaf: SyntaxNodeRef<'a>, |
64 | Other, | 62 | enclosing_fn: Option<ast::FnDef<'a>>, |
63 | is_param: bool, | ||
64 | /// a single-indent path, like `foo`. | ||
65 | is_trivial_path: bool, | ||
66 | after_if: bool, | ||
67 | is_stmt: bool, | ||
65 | } | 68 | } |
66 | 69 | ||
67 | impl SyntaxContext { | 70 | impl SyntaxContext<'_> { |
68 | pub(super) fn new(original_file: &SourceFileNode, offset: TextUnit) -> SyntaxContext { | 71 | pub(super) fn new(original_file: &SourceFileNode, offset: TextUnit) -> Option<SyntaxContext> { |
72 | let leaf = find_leaf_at_offset(original_file.syntax(), offset).left_biased()?; | ||
73 | let mut ctx = SyntaxContext { | ||
74 | leaf, | ||
75 | enclosing_fn: None, | ||
76 | is_param: false, | ||
77 | is_trivial_path: false, | ||
78 | after_if: false, | ||
79 | is_stmt: false, | ||
80 | }; | ||
81 | ctx.fill(original_file, offset); | ||
82 | Some(ctx) | ||
83 | } | ||
84 | |||
85 | fn fill(&mut self, original_file: &SourceFileNode, offset: TextUnit) { | ||
86 | // Insert a fake ident to get a valid parse tree. We will use this file | ||
87 | // to determine context, though the original_file will be used for | ||
88 | // actual completion. | ||
69 | let file = { | 89 | let file = { |
70 | let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string()); | 90 | let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string()); |
71 | original_file.reparse(&edit) | 91 | original_file.reparse(&edit) |
72 | }; | 92 | }; |
93 | |||
94 | // First, let's try to complete a reference to some declaration. | ||
95 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) { | ||
96 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. | ||
97 | // See RFC#1685. | ||
98 | if is_node::<ast::Param>(name_ref.syntax()) { | ||
99 | self.is_param = true; | ||
100 | return; | ||
101 | } | ||
102 | self.classify_name_ref(&file, name_ref); | ||
103 | } | ||
104 | |||
105 | // Otherwise, see if this is a declaration. We can use heuristics to | ||
106 | // suggest declaration names, see `CompletionKind::Magic`. | ||
73 | if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) { | 107 | if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) { |
74 | if is_node::<ast::Param>(name.syntax()) { | 108 | if is_node::<ast::Param>(name.syntax()) { |
75 | if let Some(node) = find_leaf_at_offset(original_file, offset).left_biased() { | 109 | self.is_param = true; |
76 | return SyntaxContext::ParameterName(node); | 110 | return; |
77 | } | ||
78 | } | 111 | } |
79 | } | 112 | } |
80 | |||
81 | SyntaxContext::Other | ||
82 | } | 113 | } |
83 | } | 114 | fn classify_name_ref(&mut self, file: &SourceFileNode, name_ref: ast::NameRef) { |
84 | 115 | // let name_range = name_ref.syntax().range(); | |
85 | /// Complete repeated parametes, both name and type. For example, if all | 116 | // let top_node = name_ref |
86 | /// functions in a file have a `spam: &mut Spam` parameter, a completion with | 117 | // .syntax() |
87 | /// `spam: &mut Spam` insert text/label and `spam` lookup string will be | 118 | // .ancestors() |
88 | /// suggested. | 119 | // .take_while(|it| it.range() == name_range) |
89 | fn param_completions(acc: &mut Completions, ctx: SyntaxNodeRef) { | 120 | // .last() |
90 | let mut params = FxHashMap::default(); | 121 | // .unwrap(); |
91 | for node in ctx.ancestors() { | 122 | // match top_node.parent().map(|it| it.kind()) { |
92 | let _ = visitor_ctx(&mut params) | 123 | // Some(SOURCE_FILE) | Some(ITEM_LIST) => return Some(NameRefKind::BareIdentInMod), |
93 | .visit::<ast::SourceFile, _>(process) | 124 | // _ => (), |
94 | .visit::<ast::ItemList, _>(process) | 125 | // } |
95 | .accept(node); | 126 | let parent = match name_ref.syntax().parent() { |
96 | } | 127 | Some(it) => it, |
97 | params | 128 | None => return, |
98 | .into_iter() | 129 | }; |
99 | .filter_map(|(label, (count, param))| { | 130 | if let Some(segment) = ast::PathSegment::cast(parent) { |
100 | let lookup = param.pat()?.syntax().text().to_string(); | 131 | let path = segment.parent_path(); |
101 | if count < 2 { | 132 | // if let Some(path) = Path::from_ast(path) { |
102 | None | 133 | // if !path.is_ident() { |
103 | } else { | 134 | // return Some(NameRefKind::Path(path)); |
104 | Some((label, lookup)) | 135 | // } |
136 | // } | ||
137 | if path.qualifier().is_none() { | ||
138 | self.is_trivial_path = true; | ||
139 | self.enclosing_fn = self | ||
140 | .leaf | ||
141 | .ancestors() | ||
142 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | ||
143 | .find_map(ast::FnDef::cast); | ||
144 | |||
145 | self.is_stmt = match name_ref | ||
146 | .syntax() | ||
147 | .ancestors() | ||
148 | .filter_map(ast::ExprStmt::cast) | ||
149 | .next() | ||
150 | { | ||
151 | None => false, | ||
152 | Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(), | ||
153 | }; | ||
154 | |||
155 | if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) { | ||
156 | if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) { | ||
157 | if if_expr.syntax().range().end() < name_ref.syntax().range().start() { | ||
158 | self.after_if = true; | ||
159 | } | ||
160 | } | ||
161 | } | ||
105 | } | 162 | } |
106 | }) | 163 | } |
107 | .for_each(|(label, lookup)| { | ||
108 | CompletionItem::new(label) | ||
109 | .lookup_by(lookup) | ||
110 | .kind(CompletionKind::Magic) | ||
111 | .add_to(acc) | ||
112 | }); | ||
113 | |||
114 | fn process<'a, N: ast::FnDefOwner<'a>>( | ||
115 | node: N, | ||
116 | params: &mut FxHashMap<String, (u32, ast::Param<'a>)>, | ||
117 | ) { | ||
118 | node.functions() | ||
119 | .filter_map(|it| it.param_list()) | ||
120 | .flat_map(|it| it.params()) | ||
121 | .for_each(|param| { | ||
122 | let text = param.syntax().text().to_string(); | ||
123 | params.entry(text).or_insert((0, param)).0 += 1; | ||
124 | }) | ||
125 | } | 164 | } |
126 | } | 165 | } |
127 | 166 | ||
@@ -143,51 +182,3 @@ fn check_completion(code: &str, expected_completions: &str, kind: CompletionKind | |||
143 | let completions = completions(&analysis.imp.db, position).unwrap().unwrap(); | 182 | let completions = completions(&analysis.imp.db, position).unwrap().unwrap(); |
144 | completions.assert_match(expected_completions, kind); | 183 | completions.assert_match(expected_completions, kind); |
145 | } | 184 | } |
146 | |||
147 | #[cfg(test)] | ||
148 | mod tests { | ||
149 | use super::*; | ||
150 | |||
151 | fn check_magic_completion(code: &str, expected_completions: &str) { | ||
152 | check_completion(code, expected_completions, CompletionKind::Magic); | ||
153 | } | ||
154 | |||
155 | #[test] | ||
156 | fn test_param_completion_last_param() { | ||
157 | check_magic_completion( | ||
158 | r" | ||
159 | fn foo(file_id: FileId) {} | ||
160 | fn bar(file_id: FileId) {} | ||
161 | fn baz(file<|>) {} | ||
162 | ", | ||
163 | r#"file_id "file_id: FileId""#, | ||
164 | ); | ||
165 | } | ||
166 | |||
167 | #[test] | ||
168 | fn test_param_completion_nth_param() { | ||
169 | check_magic_completion( | ||
170 | r" | ||
171 | fn foo(file_id: FileId) {} | ||
172 | fn bar(file_id: FileId) {} | ||
173 | fn baz(file<|>, x: i32) {} | ||
174 | ", | ||
175 | r#"file_id "file_id: FileId""#, | ||
176 | ); | ||
177 | } | ||
178 | |||
179 | #[test] | ||
180 | fn test_param_completion_trait_param() { | ||
181 | check_magic_completion( | ||
182 | r" | ||
183 | pub(crate) trait SourceRoot { | ||
184 | pub fn contains(&self, file_id: FileId) -> bool; | ||
185 | pub fn module_map(&self) -> &ModuleMap; | ||
186 | pub fn lines(&self, file_id: FileId) -> &LineIndex; | ||
187 | pub fn syntax(&self, file<|>) | ||
188 | } | ||
189 | ", | ||
190 | r#"file_id "file_id: FileId""#, | ||
191 | ); | ||
192 | } | ||
193 | } | ||