aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis/src/completion.rs
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2018-12-21 22:04:32 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2018-12-21 22:04:32 +0000
commit184665ff9b7b64730ecf481c1914a74e7191a6dd (patch)
tree4f5e97a0832821a4b06b784591d6cb3a417f1198 /crates/ra_analysis/src/completion.rs
parent2351308d92f4c785d98cc24026a818d28945513e (diff)
parent2ae87ffc9afe67945e2ad655c3577b589ff640ab (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.rs164
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 @@
1mod completion_item; 1mod completion_item;
2mod reference_completion; 2mod completion_context;
3
4mod complete_fn_param;
5mod complete_keyword;
6mod complete_snippet;
7mod complete_path;
8mod complete_scope;
3 9
4use ra_editor::find_node_at_offset;
5use ra_text_edit::AtomTextEdit;
6use ra_syntax::{
7 algo::visit::{visitor_ctx, VisitorCtx},
8 ast,
9 AstNode,
10 SyntaxNodeRef,
11};
12use ra_db::SyntaxDatabase; 10use ra_db::SyntaxDatabase;
13use rustc_hash::{FxHashMap};
14use hir::source_binder;
15 11
16use crate::{ 12use 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
22pub use crate::completion::completion_item::{CompletionItem, InsertText}; 21pub 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.
24pub(crate) fn completions( 31pub(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.
66fn 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
105fn 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)]
125mod 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}