aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis/src/completion.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_analysis/src/completion.rs')
-rw-r--r--crates/ra_analysis/src/completion.rs225
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 @@
1mod completion_item; 1mod completion_item;
2mod reference_completion; 2mod reference_completion;
3 3
4mod complete_fn_param;
5mod complete_keywords;
6
4use ra_editor::find_node_at_offset; 7use ra_editor::find_node_at_offset;
5use ra_text_edit::AtomTextEdit; 8use ra_text_edit::AtomTextEdit;
6use ra_syntax::{ 9use 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};
14use ra_db::SyntaxDatabase; 20use ra_db::SyntaxDatabase;
15use rustc_hash::{FxHashMap};
16use hir::source_binder; 21use hir::source_binder;
17 22
18use crate::{ 23use 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)]
62pub(super) enum SyntaxContext<'a> { 60pub(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
67impl SyntaxContext { 70impl 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)
89fn 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)]
148mod 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}