aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_analysis')
-rw-r--r--crates/ra_analysis/src/completion.rs170
-rw-r--r--crates/ra_analysis/src/completion/complete_fn_param.rs4
-rw-r--r--crates/ra_analysis/src/completion/complete_keyword.rs4
-rw-r--r--crates/ra_analysis/src/completion/complete_path.rs4
-rw-r--r--crates/ra_analysis/src/completion/complete_scope.rs4
-rw-r--r--crates/ra_analysis/src/completion/complete_snippet.rs6
-rw-r--r--crates/ra_analysis/src/completion/completion_context.rs156
7 files changed, 181 insertions, 167 deletions
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs
index 93edcc4c2..2d61a3aef 100644
--- a/crates/ra_analysis/src/completion.rs
+++ b/crates/ra_analysis/src/completion.rs
@@ -1,4 +1,5 @@
1mod completion_item; 1mod completion_item;
2mod completion_context;
2 3
3mod complete_fn_param; 4mod complete_fn_param;
4mod complete_keyword; 5mod complete_keyword;
@@ -6,34 +7,33 @@ mod complete_snippet;
6mod complete_path; 7mod complete_path;
7mod complete_scope; 8mod complete_scope;
8 9
9use ra_editor::find_node_at_offset;
10use ra_text_edit::AtomTextEdit;
11use ra_syntax::{
12 algo::find_leaf_at_offset,
13 ast,
14 AstNode,
15 SyntaxNodeRef,
16 SourceFileNode,
17 TextUnit,
18 SyntaxKind::*,
19};
20use ra_db::SyntaxDatabase; 10use ra_db::SyntaxDatabase;
21use hir::source_binder;
22 11
23use crate::{ 12use crate::{
24 db, 13 db,
25 Cancelable, FilePosition, 14 Cancelable, FilePosition,
26 completion::completion_item::{Completions, CompletionKind}, 15 completion::{
16 completion_item::{Completions, CompletionKind},
17 completion_context::CompletionContext,
18 },
27}; 19};
28 20
29pub use crate::completion::completion_item::{CompletionItem, InsertText}; 21pub use crate::completion::completion_item::{CompletionItem, InsertText};
30 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.
31pub(crate) fn completions( 31pub(crate) fn completions(
32 db: &db::RootDatabase, 32 db: &db::RootDatabase,
33 position: FilePosition, 33 position: FilePosition,
34) -> Cancelable<Option<Completions>> { 34) -> Cancelable<Option<Completions>> {
35 let original_file = db.source_file(position.file_id); 35 let original_file = db.source_file(position.file_id);
36 let ctx = ctry!(SyntaxContext::new(db, &original_file, position)?); 36 let ctx = ctry!(CompletionContext::new(db, &original_file, position)?);
37 37
38 let mut acc = Completions::default(); 38 let mut acc = Completions::default();
39 39
@@ -47,148 +47,6 @@ pub(crate) fn completions(
47 Ok(Some(acc)) 47 Ok(Some(acc))
48} 48}
49 49
50/// `SyntaxContext` is created early during completion to figure out, where
51/// exactly is the cursor, syntax-wise.
52#[derive(Debug)]
53pub(super) struct SyntaxContext<'a> {
54 db: &'a db::RootDatabase,
55 offset: TextUnit,
56 leaf: SyntaxNodeRef<'a>,
57 module: Option<hir::Module>,
58 enclosing_fn: Option<ast::FnDef<'a>>,
59 is_param: bool,
60 /// A single-indent path, like `foo`.
61 is_trivial_path: bool,
62 /// If not a trivial, path, the prefix (qualifier).
63 path_prefix: Option<hir::Path>,
64 after_if: bool,
65 is_stmt: bool,
66 /// Something is typed at the "top" level, in module or impl/trait.
67 is_new_item: bool,
68}
69
70impl<'a> SyntaxContext<'a> {
71 pub(super) fn new(
72 db: &'a db::RootDatabase,
73 original_file: &'a SourceFileNode,
74 position: FilePosition,
75 ) -> Cancelable<Option<SyntaxContext<'a>>> {
76 let module = source_binder::module_from_position(db, position)?;
77 let leaf =
78 ctry!(find_leaf_at_offset(original_file.syntax(), position.offset).left_biased());
79 let mut ctx = SyntaxContext {
80 db,
81 leaf,
82 offset: position.offset,
83 module,
84 enclosing_fn: None,
85 is_param: false,
86 is_trivial_path: false,
87 path_prefix: None,
88 after_if: false,
89 is_stmt: false,
90 is_new_item: false,
91 };
92 ctx.fill(original_file, position.offset);
93 Ok(Some(ctx))
94 }
95
96 fn fill(&mut self, original_file: &SourceFileNode, offset: TextUnit) {
97 // Insert a fake ident to get a valid parse tree. We will use this file
98 // to determine context, though the original_file will be used for
99 // actual completion.
100 let file = {
101 let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string());
102 original_file.reparse(&edit)
103 };
104
105 // First, let's try to complete a reference to some declaration.
106 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) {
107 // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
108 // See RFC#1685.
109 if is_node::<ast::Param>(name_ref.syntax()) {
110 self.is_param = true;
111 return;
112 }
113 self.classify_name_ref(&file, name_ref);
114 }
115
116 // Otherwise, see if this is a declaration. We can use heuristics to
117 // suggest declaration names, see `CompletionKind::Magic`.
118 if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) {
119 if is_node::<ast::Param>(name.syntax()) {
120 self.is_param = true;
121 return;
122 }
123 }
124 }
125 fn classify_name_ref(&mut self, file: &SourceFileNode, name_ref: ast::NameRef) {
126 let name_range = name_ref.syntax().range();
127 let top_node = name_ref
128 .syntax()
129 .ancestors()
130 .take_while(|it| it.range() == name_range)
131 .last()
132 .unwrap();
133
134 match top_node.parent().map(|it| it.kind()) {
135 Some(SOURCE_FILE) | Some(ITEM_LIST) => {
136 self.is_new_item = true;
137 return;
138 }
139 _ => (),
140 }
141
142 let parent = match name_ref.syntax().parent() {
143 Some(it) => it,
144 None => return,
145 };
146 if let Some(segment) = ast::PathSegment::cast(parent) {
147 let path = segment.parent_path();
148 if let Some(mut path) = hir::Path::from_ast(path) {
149 if !path.is_ident() {
150 path.segments.pop().unwrap();
151 self.path_prefix = Some(path);
152 return;
153 }
154 }
155 if path.qualifier().is_none() {
156 self.is_trivial_path = true;
157 self.enclosing_fn = self
158 .leaf
159 .ancestors()
160 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
161 .find_map(ast::FnDef::cast);
162
163 self.is_stmt = match name_ref
164 .syntax()
165 .ancestors()
166 .filter_map(ast::ExprStmt::cast)
167 .next()
168 {
169 None => false,
170 Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(),
171 };
172
173 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
174 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
175 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
176 self.after_if = true;
177 }
178 }
179 }
180 }
181 }
182 }
183}
184
185fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool {
186 match node.ancestors().filter_map(N::cast).next() {
187 None => false,
188 Some(n) => n.syntax().range() == node.range(),
189 }
190}
191
192#[cfg(test)] 50#[cfg(test)]
193fn check_completion(code: &str, expected_completions: &str, kind: CompletionKind) { 51fn check_completion(code: &str, expected_completions: &str, kind: CompletionKind) {
194 use crate::mock_analysis::{single_file_with_position, analysis_and_position}; 52 use crate::mock_analysis::{single_file_with_position, analysis_and_position};
diff --git a/crates/ra_analysis/src/completion/complete_fn_param.rs b/crates/ra_analysis/src/completion/complete_fn_param.rs
index d05a5e3cf..3ec507fdf 100644
--- a/crates/ra_analysis/src/completion/complete_fn_param.rs
+++ b/crates/ra_analysis/src/completion/complete_fn_param.rs
@@ -8,14 +8,14 @@ use ra_syntax::{
8use rustc_hash::{FxHashMap}; 8use rustc_hash::{FxHashMap};
9 9
10use crate::{ 10use crate::{
11 completion::{SyntaxContext, Completions, CompletionKind, CompletionItem}, 11 completion::{CompletionContext, Completions, CompletionKind, CompletionItem},
12}; 12};
13 13
14/// Complete repeated parametes, both name and type. For example, if all 14/// Complete repeated parametes, both name and type. For example, if all
15/// functions in a file have a `spam: &mut Spam` parameter, a completion with 15/// functions in a file have a `spam: &mut Spam` parameter, a completion with
16/// `spam: &mut Spam` insert text/label and `spam` lookup string will be 16/// `spam: &mut Spam` insert text/label and `spam` lookup string will be
17/// suggested. 17/// suggested.
18pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &SyntaxContext) { 18pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) {
19 if !ctx.is_param { 19 if !ctx.is_param {
20 return; 20 return;
21 } 21 }
diff --git a/crates/ra_analysis/src/completion/complete_keyword.rs b/crates/ra_analysis/src/completion/complete_keyword.rs
index d0a6ec19e..2ee36430e 100644
--- a/crates/ra_analysis/src/completion/complete_keyword.rs
+++ b/crates/ra_analysis/src/completion/complete_keyword.rs
@@ -6,10 +6,10 @@ use ra_syntax::{
6}; 6};
7 7
8use crate::{ 8use crate::{
9 completion::{SyntaxContext, CompletionItem, Completions, CompletionKind::*}, 9 completion::{CompletionContext, CompletionItem, Completions, CompletionKind::*},
10}; 10};
11 11
12pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &SyntaxContext) { 12pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
13 if !ctx.is_trivial_path { 13 if !ctx.is_trivial_path {
14 return; 14 return;
15 } 15 }
diff --git a/crates/ra_analysis/src/completion/complete_path.rs b/crates/ra_analysis/src/completion/complete_path.rs
index 8374ec346..41e439b1b 100644
--- a/crates/ra_analysis/src/completion/complete_path.rs
+++ b/crates/ra_analysis/src/completion/complete_path.rs
@@ -1,9 +1,9 @@
1use crate::{ 1use crate::{
2 completion::{CompletionItem, Completions, CompletionKind::*, SyntaxContext}, 2 completion::{CompletionItem, Completions, CompletionKind::*, CompletionContext},
3 Cancelable, 3 Cancelable,
4}; 4};
5 5
6pub(super) fn complete_path(acc: &mut Completions, ctx: &SyntaxContext) -> Cancelable<()> { 6pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
7 let (path, module) = match (&ctx.path_prefix, &ctx.module) { 7 let (path, module) = match (&ctx.path_prefix, &ctx.module) {
8 (Some(path), Some(module)) => (path.clone(), module), 8 (Some(path), Some(module)) => (path.clone(), module),
9 _ => return Ok(()), 9 _ => return Ok(()),
diff --git a/crates/ra_analysis/src/completion/complete_scope.rs b/crates/ra_analysis/src/completion/complete_scope.rs
index ddaf13b88..c1ab19d5b 100644
--- a/crates/ra_analysis/src/completion/complete_scope.rs
+++ b/crates/ra_analysis/src/completion/complete_scope.rs
@@ -2,11 +2,11 @@ use rustc_hash::FxHashSet;
2use ra_syntax::TextUnit; 2use ra_syntax::TextUnit;
3 3
4use crate::{ 4use crate::{
5 completion::{CompletionItem, Completions, CompletionKind::*, SyntaxContext}, 5 completion::{CompletionItem, Completions, CompletionKind::*, CompletionContext},
6 Cancelable 6 Cancelable
7}; 7};
8 8
9pub(super) fn complete_scope(acc: &mut Completions, ctx: &SyntaxContext) -> Cancelable<()> { 9pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
10 if !ctx.is_trivial_path { 10 if !ctx.is_trivial_path {
11 return Ok(()); 11 return Ok(());
12 } 12 }
diff --git a/crates/ra_analysis/src/completion/complete_snippet.rs b/crates/ra_analysis/src/completion/complete_snippet.rs
index 5d6cc5dc9..6816ae695 100644
--- a/crates/ra_analysis/src/completion/complete_snippet.rs
+++ b/crates/ra_analysis/src/completion/complete_snippet.rs
@@ -1,8 +1,8 @@
1use crate::{ 1use crate::{
2 completion::{CompletionItem, Completions, CompletionKind::*, SyntaxContext}, 2 completion::{CompletionItem, Completions, CompletionKind::*, CompletionContext},
3}; 3};
4 4
5pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &SyntaxContext) { 5pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) {
6 if !(ctx.is_trivial_path && ctx.enclosing_fn.is_some()) { 6 if !(ctx.is_trivial_path && ctx.enclosing_fn.is_some()) {
7 return; 7 return;
8 } 8 }
@@ -16,7 +16,7 @@ pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &SyntaxContext)
16 .add_to(acc); 16 .add_to(acc);
17} 17}
18 18
19pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &SyntaxContext) { 19pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
20 if !ctx.is_new_item { 20 if !ctx.is_new_item {
21 return; 21 return;
22 } 22 }
diff --git a/crates/ra_analysis/src/completion/completion_context.rs b/crates/ra_analysis/src/completion/completion_context.rs
new file mode 100644
index 000000000..064fbc6f7
--- /dev/null
+++ b/crates/ra_analysis/src/completion/completion_context.rs
@@ -0,0 +1,156 @@
1use ra_editor::find_node_at_offset;
2use ra_text_edit::AtomTextEdit;
3use ra_syntax::{
4 algo::find_leaf_at_offset,
5 ast,
6 AstNode,
7 SyntaxNodeRef,
8 SourceFileNode,
9 TextUnit,
10 SyntaxKind::*,
11};
12use hir::source_binder;
13
14use crate::{db, FilePosition, Cancelable};
15
16/// `CompletionContext` is created early during completion to figure out, where
17/// exactly is the cursor, syntax-wise.
18#[derive(Debug)]
19pub(super) struct CompletionContext<'a> {
20 pub(super) db: &'a db::RootDatabase,
21 pub(super) offset: TextUnit,
22 pub(super) leaf: SyntaxNodeRef<'a>,
23 pub(super) module: Option<hir::Module>,
24 pub(super) enclosing_fn: Option<ast::FnDef<'a>>,
25 pub(super) is_param: bool,
26 /// A single-indent path, like `foo`.
27 pub(super) is_trivial_path: bool,
28 /// If not a trivial, path, the prefix (qualifier).
29 pub(super) path_prefix: Option<hir::Path>,
30 pub(super) after_if: bool,
31 pub(super) is_stmt: bool,
32 /// Something is typed at the "top" level, in module or impl/trait.
33 pub(super) is_new_item: bool,
34}
35
36impl<'a> CompletionContext<'a> {
37 pub(super) fn new(
38 db: &'a db::RootDatabase,
39 original_file: &'a SourceFileNode,
40 position: FilePosition,
41 ) -> Cancelable<Option<CompletionContext<'a>>> {
42 let module = source_binder::module_from_position(db, position)?;
43 let leaf =
44 ctry!(find_leaf_at_offset(original_file.syntax(), position.offset).left_biased());
45 let mut ctx = CompletionContext {
46 db,
47 leaf,
48 offset: position.offset,
49 module,
50 enclosing_fn: None,
51 is_param: false,
52 is_trivial_path: false,
53 path_prefix: None,
54 after_if: false,
55 is_stmt: false,
56 is_new_item: false,
57 };
58 ctx.fill(original_file, position.offset);
59 Ok(Some(ctx))
60 }
61
62 fn fill(&mut self, original_file: &SourceFileNode, offset: TextUnit) {
63 // Insert a fake ident to get a valid parse tree. We will use this file
64 // to determine context, though the original_file will be used for
65 // actual completion.
66 let file = {
67 let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string());
68 original_file.reparse(&edit)
69 };
70
71 // First, let's try to complete a reference to some declaration.
72 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) {
73 // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
74 // See RFC#1685.
75 if is_node::<ast::Param>(name_ref.syntax()) {
76 self.is_param = true;
77 return;
78 }
79 self.classify_name_ref(&file, name_ref);
80 }
81
82 // Otherwise, see if this is a declaration. We can use heuristics to
83 // suggest declaration names, see `CompletionKind::Magic`.
84 if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) {
85 if is_node::<ast::Param>(name.syntax()) {
86 self.is_param = true;
87 return;
88 }
89 }
90 }
91 fn classify_name_ref(&mut self, file: &SourceFileNode, name_ref: ast::NameRef) {
92 let name_range = name_ref.syntax().range();
93 let top_node = name_ref
94 .syntax()
95 .ancestors()
96 .take_while(|it| it.range() == name_range)
97 .last()
98 .unwrap();
99
100 match top_node.parent().map(|it| it.kind()) {
101 Some(SOURCE_FILE) | Some(ITEM_LIST) => {
102 self.is_new_item = true;
103 return;
104 }
105 _ => (),
106 }
107
108 let parent = match name_ref.syntax().parent() {
109 Some(it) => it,
110 None => return,
111 };
112 if let Some(segment) = ast::PathSegment::cast(parent) {
113 let path = segment.parent_path();
114 if let Some(mut path) = hir::Path::from_ast(path) {
115 if !path.is_ident() {
116 path.segments.pop().unwrap();
117 self.path_prefix = Some(path);
118 return;
119 }
120 }
121 if path.qualifier().is_none() {
122 self.is_trivial_path = true;
123 self.enclosing_fn = self
124 .leaf
125 .ancestors()
126 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
127 .find_map(ast::FnDef::cast);
128
129 self.is_stmt = match name_ref
130 .syntax()
131 .ancestors()
132 .filter_map(ast::ExprStmt::cast)
133 .next()
134 {
135 None => false,
136 Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(),
137 };
138
139 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
140 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
141 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
142 self.after_if = true;
143 }
144 }
145 }
146 }
147 }
148 }
149}
150
151fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool {
152 match node.ancestors().filter_map(N::cast).next() {
153 None => false,
154 Some(n) => n.syntax().range() == node.range(),
155 }
156}