aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2018-12-21 17:25:29 +0000
committerAleksey Kladov <[email protected]>2018-12-21 17:25:29 +0000
commitc2bf174e9c3f994d83e7e72b6e15c9b26c5b31a2 (patch)
tree7bd3920e5139b8fef6cf0b4e07d671f022d58b65 /crates
parent12810b93c5141b9ae31f4af17dcc61b0166314b0 (diff)
Start splitting completion into components
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_analysis/src/completion.rs225
-rw-r--r--crates/ra_analysis/src/completion/complete_fn_param.rs107
-rw-r--r--crates/ra_analysis/src/completion/complete_keywords.rs206
-rw-r--r--crates/ra_analysis/src/completion/reference_completion.rs225
4 files changed, 424 insertions, 339 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}
diff --git a/crates/ra_analysis/src/completion/complete_fn_param.rs b/crates/ra_analysis/src/completion/complete_fn_param.rs
new file mode 100644
index 000000000..d05a5e3cf
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_fn_param.rs
@@ -0,0 +1,107 @@
1use ra_syntax::{
2 algo::{
3 visit::{visitor_ctx, VisitorCtx}
4 },
5 ast,
6 AstNode,
7};
8use rustc_hash::{FxHashMap};
9
10use crate::{
11 completion::{SyntaxContext, Completions, CompletionKind, CompletionItem},
12};
13
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
16/// `spam: &mut Spam` insert text/label and `spam` lookup string will be
17/// suggested.
18pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &SyntaxContext) {
19 if !ctx.is_param {
20 return;
21 }
22
23 let mut params = FxHashMap::default();
24 for node in ctx.leaf.ancestors() {
25 let _ = visitor_ctx(&mut params)
26 .visit::<ast::SourceFile, _>(process)
27 .visit::<ast::ItemList, _>(process)
28 .accept(node);
29 }
30 params
31 .into_iter()
32 .filter_map(|(label, (count, param))| {
33 let lookup = param.pat()?.syntax().text().to_string();
34 if count < 2 {
35 None
36 } else {
37 Some((label, lookup))
38 }
39 })
40 .for_each(|(label, lookup)| {
41 CompletionItem::new(label)
42 .lookup_by(lookup)
43 .kind(CompletionKind::Magic)
44 .add_to(acc)
45 });
46
47 fn process<'a, N: ast::FnDefOwner<'a>>(
48 node: N,
49 params: &mut FxHashMap<String, (u32, ast::Param<'a>)>,
50 ) {
51 node.functions()
52 .filter_map(|it| it.param_list())
53 .flat_map(|it| it.params())
54 .for_each(|param| {
55 let text = param.syntax().text().to_string();
56 params.entry(text).or_insert((0, param)).0 += 1;
57 })
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use crate::completion::*;
64
65 fn check_magic_completion(code: &str, expected_completions: &str) {
66 check_completion(code, expected_completions, CompletionKind::Magic);
67 }
68
69 #[test]
70 fn test_param_completion_last_param() {
71 check_magic_completion(
72 r"
73 fn foo(file_id: FileId) {}
74 fn bar(file_id: FileId) {}
75 fn baz(file<|>) {}
76 ",
77 r#"file_id "file_id: FileId""#,
78 );
79 }
80
81 #[test]
82 fn test_param_completion_nth_param() {
83 check_magic_completion(
84 r"
85 fn foo(file_id: FileId) {}
86 fn bar(file_id: FileId) {}
87 fn baz(file<|>, x: i32) {}
88 ",
89 r#"file_id "file_id: FileId""#,
90 );
91 }
92
93 #[test]
94 fn test_param_completion_trait_param() {
95 check_magic_completion(
96 r"
97 pub(crate) trait SourceRoot {
98 pub fn contains(&self, file_id: FileId) -> bool;
99 pub fn module_map(&self) -> &ModuleMap;
100 pub fn lines(&self, file_id: FileId) -> &LineIndex;
101 pub fn syntax(&self, file<|>)
102 }
103 ",
104 r#"file_id "file_id: FileId""#,
105 );
106 }
107}
diff --git a/crates/ra_analysis/src/completion/complete_keywords.rs b/crates/ra_analysis/src/completion/complete_keywords.rs
new file mode 100644
index 000000000..d0a6ec19e
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_keywords.rs
@@ -0,0 +1,206 @@
1use ra_syntax::{
2 algo::visit::{visitor, Visitor},
3 AstNode,
4 ast::{self, LoopBodyOwner},
5 SyntaxKind::*, SyntaxNodeRef,
6};
7
8use crate::{
9 completion::{SyntaxContext, CompletionItem, Completions, CompletionKind::*},
10};
11
12pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &SyntaxContext) {
13 if !ctx.is_trivial_path {
14 return;
15 }
16 let fn_def = match ctx.enclosing_fn {
17 Some(it) => it,
18 None => return,
19 };
20 acc.add(keyword("if", "if $0 {}"));
21 acc.add(keyword("match", "match $0 {}"));
22 acc.add(keyword("while", "while $0 {}"));
23 acc.add(keyword("loop", "loop {$0}"));
24
25 if ctx.after_if {
26 acc.add(keyword("else", "else {$0}"));
27 acc.add(keyword("else if", "else if $0 {}"));
28 }
29 if is_in_loop_body(ctx.leaf) {
30 acc.add(keyword("continue", "continue"));
31 acc.add(keyword("break", "break"));
32 }
33 acc.add_all(complete_return(fn_def, ctx.is_stmt));
34}
35
36fn is_in_loop_body(leaf: SyntaxNodeRef) -> bool {
37 for node in leaf.ancestors() {
38 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
39 break;
40 }
41 let loop_body = visitor()
42 .visit::<ast::ForExpr, _>(LoopBodyOwner::loop_body)
43 .visit::<ast::WhileExpr, _>(LoopBodyOwner::loop_body)
44 .visit::<ast::LoopExpr, _>(LoopBodyOwner::loop_body)
45 .accept(node);
46 if let Some(Some(body)) = loop_body {
47 if leaf.range().is_subrange(&body.syntax().range()) {
48 return true;
49 }
50 }
51 }
52 false
53}
54
55fn complete_return(fn_def: ast::FnDef, is_stmt: bool) -> Option<CompletionItem> {
56 let snip = match (is_stmt, fn_def.ret_type().is_some()) {
57 (true, true) => "return $0;",
58 (true, false) => "return;",
59 (false, true) => "return $0",
60 (false, false) => "return",
61 };
62 Some(keyword("return", snip))
63}
64
65fn keyword(kw: &str, snippet: &str) -> CompletionItem {
66 CompletionItem::new(kw)
67 .kind(Keyword)
68 .snippet(snippet)
69 .build()
70}
71
72#[cfg(test)]
73mod tests {
74 use crate::completion::{CompletionKind, check_completion};
75 fn check_keyword_completion(code: &str, expected_completions: &str) {
76 check_completion(code, expected_completions, CompletionKind::Keyword);
77 }
78
79 #[test]
80 fn test_completion_kewords() {
81 check_keyword_completion(
82 r"
83 fn quux() {
84 <|>
85 }
86 ",
87 r#"
88 if "if $0 {}"
89 match "match $0 {}"
90 while "while $0 {}"
91 loop "loop {$0}"
92 return "return"
93 "#,
94 );
95 }
96
97 #[test]
98 fn test_completion_else() {
99 check_keyword_completion(
100 r"
101 fn quux() {
102 if true {
103 ()
104 } <|>
105 }
106 ",
107 r#"
108 if "if $0 {}"
109 match "match $0 {}"
110 while "while $0 {}"
111 loop "loop {$0}"
112 else "else {$0}"
113 else if "else if $0 {}"
114 return "return"
115 "#,
116 );
117 }
118
119 #[test]
120 fn test_completion_return_value() {
121 check_keyword_completion(
122 r"
123 fn quux() -> i32 {
124 <|>
125 92
126 }
127 ",
128 r#"
129 if "if $0 {}"
130 match "match $0 {}"
131 while "while $0 {}"
132 loop "loop {$0}"
133 return "return $0;"
134 "#,
135 );
136 check_keyword_completion(
137 r"
138 fn quux() {
139 <|>
140 92
141 }
142 ",
143 r#"
144 if "if $0 {}"
145 match "match $0 {}"
146 while "while $0 {}"
147 loop "loop {$0}"
148 return "return;"
149 "#,
150 );
151 }
152
153 #[test]
154 fn test_completion_return_no_stmt() {
155 check_keyword_completion(
156 r"
157 fn quux() -> i32 {
158 match () {
159 () => <|>
160 }
161 }
162 ",
163 r#"
164 if "if $0 {}"
165 match "match $0 {}"
166 while "while $0 {}"
167 loop "loop {$0}"
168 return "return $0"
169 "#,
170 );
171 }
172
173 #[test]
174 fn test_continue_break_completion() {
175 check_keyword_completion(
176 r"
177 fn quux() -> i32 {
178 loop { <|> }
179 }
180 ",
181 r#"
182 if "if $0 {}"
183 match "match $0 {}"
184 while "while $0 {}"
185 loop "loop {$0}"
186 continue "continue"
187 break "break"
188 return "return $0"
189 "#,
190 );
191 check_keyword_completion(
192 r"
193 fn quux() -> i32 {
194 loop { || { <|> } }
195 }
196 ",
197 r#"
198 if "if $0 {}"
199 match "match $0 {}"
200 while "while $0 {}"
201 loop "loop {$0}"
202 return "return $0"
203 "#,
204 );
205 }
206}
diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs
index c2ac95453..15ff4c5dd 100644
--- a/crates/ra_analysis/src/completion/reference_completion.rs
+++ b/crates/ra_analysis/src/completion/reference_completion.rs
@@ -1,9 +1,7 @@
1use rustc_hash::{FxHashSet}; 1use rustc_hash::{FxHashSet};
2use ra_editor::find_node_at_offset;
3use ra_syntax::{ 2use ra_syntax::{
4 algo::visit::{visitor, Visitor},
5 SourceFileNode, AstNode, 3 SourceFileNode, AstNode,
6 ast::{self, LoopBodyOwner}, 4 ast,
7 SyntaxKind::*, 5 SyntaxKind::*,
8}; 6};
9use hir::{ 7use hir::{
@@ -21,7 +19,7 @@ pub(super) fn completions(
21 acc: &mut Completions, 19 acc: &mut Completions,
22 db: &RootDatabase, 20 db: &RootDatabase,
23 module: &hir::Module, 21 module: &hir::Module,
24 file: &SourceFileNode, 22 _file: &SourceFileNode,
25 name_ref: ast::NameRef, 23 name_ref: ast::NameRef,
26) -> Cancelable<()> { 24) -> Cancelable<()> {
27 let kind = match classify_name_ref(name_ref) { 25 let kind = match classify_name_ref(name_ref) {
@@ -34,7 +32,7 @@ pub(super) fn completions(
34 if let Some(fn_def) = enclosing_fn { 32 if let Some(fn_def) = enclosing_fn {
35 let scopes = FnScopes::new(fn_def); 33 let scopes = FnScopes::new(fn_def);
36 complete_fn(name_ref, &scopes, acc); 34 complete_fn(name_ref, &scopes, acc);
37 complete_expr_keywords(&file, fn_def, name_ref, acc); 35 // complete_expr_keywords(&file, fn_def, name_ref, acc);
38 complete_expr_snippets(acc); 36 complete_expr_snippets(acc);
39 } 37 }
40 38
@@ -182,91 +180,6 @@ fn ${1:feature}() {
182 .add_to(acc); 180 .add_to(acc);
183} 181}
184 182
185fn complete_expr_keywords(
186 file: &SourceFileNode,
187 fn_def: ast::FnDef,
188 name_ref: ast::NameRef,
189 acc: &mut Completions,
190) {
191 acc.add(keyword("if", "if $0 {}"));
192 acc.add(keyword("match", "match $0 {}"));
193 acc.add(keyword("while", "while $0 {}"));
194 acc.add(keyword("loop", "loop {$0}"));
195
196 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
197 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
198 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
199 acc.add(keyword("else", "else {$0}"));
200 acc.add(keyword("else if", "else if $0 {}"));
201 }
202 }
203 }
204 if is_in_loop_body(name_ref) {
205 acc.add(keyword("continue", "continue"));
206 acc.add(keyword("break", "break"));
207 }
208 acc.add_all(complete_return(fn_def, name_ref));
209}
210
211fn is_in_loop_body(name_ref: ast::NameRef) -> bool {
212 for node in name_ref.syntax().ancestors() {
213 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
214 break;
215 }
216 let loop_body = visitor()
217 .visit::<ast::ForExpr, _>(LoopBodyOwner::loop_body)
218 .visit::<ast::WhileExpr, _>(LoopBodyOwner::loop_body)
219 .visit::<ast::LoopExpr, _>(LoopBodyOwner::loop_body)
220 .accept(node);
221 if let Some(Some(body)) = loop_body {
222 if name_ref
223 .syntax()
224 .range()
225 .is_subrange(&body.syntax().range())
226 {
227 return true;
228 }
229 }
230 }
231 false
232}
233
234fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option<CompletionItem> {
235 // let is_last_in_block = name_ref.syntax().ancestors().filter_map(ast::Expr::cast)
236 // .next()
237 // .and_then(|it| it.syntax().parent())
238 // .and_then(ast::Block::cast)
239 // .is_some();
240
241 // if is_last_in_block {
242 // return None;
243 // }
244
245 let is_stmt = match name_ref
246 .syntax()
247 .ancestors()
248 .filter_map(ast::ExprStmt::cast)
249 .next()
250 {
251 None => false,
252 Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(),
253 };
254 let snip = match (is_stmt, fn_def.ret_type().is_some()) {
255 (true, true) => "return $0;",
256 (true, false) => "return;",
257 (false, true) => "return $0",
258 (false, false) => "return",
259 };
260 Some(keyword("return", snip))
261}
262
263fn keyword(kw: &str, snippet: &str) -> CompletionItem {
264 CompletionItem::new(kw)
265 .kind(Keyword)
266 .snippet(snippet)
267 .build()
268}
269
270fn complete_expr_snippets(acc: &mut Completions) { 183fn complete_expr_snippets(acc: &mut Completions) {
271 CompletionItem::new("pd") 184 CompletionItem::new("pd")
272 .snippet("eprintln!(\"$0 = {:?}\", $0);") 185 .snippet("eprintln!(\"$0 = {:?}\", $0);")
@@ -286,10 +199,6 @@ mod tests {
286 check_completion(code, expected_completions, CompletionKind::Reference); 199 check_completion(code, expected_completions, CompletionKind::Reference);
287 } 200 }
288 201
289 fn check_keyword_completion(code: &str, expected_completions: &str) {
290 check_completion(code, expected_completions, CompletionKind::Keyword);
291 }
292
293 fn check_snippet_completion(code: &str, expected_completions: &str) { 202 fn check_snippet_completion(code: &str, expected_completions: &str) {
294 check_completion(code, expected_completions, CompletionKind::Snippet); 203 check_completion(code, expected_completions, CompletionKind::Snippet);
295 } 204 }
@@ -471,134 +380,6 @@ mod tests {
471 } 380 }
472 381
473 #[test] 382 #[test]
474 fn test_completion_kewords() {
475 check_keyword_completion(
476 r"
477 fn quux() {
478 <|>
479 }
480 ",
481 r#"
482 if "if $0 {}"
483 match "match $0 {}"
484 while "while $0 {}"
485 loop "loop {$0}"
486 return "return"
487 "#,
488 );
489 }
490
491 #[test]
492 fn test_completion_else() {
493 check_keyword_completion(
494 r"
495 fn quux() {
496 if true {
497 ()
498 } <|>
499 }
500 ",
501 r#"
502 if "if $0 {}"
503 match "match $0 {}"
504 while "while $0 {}"
505 loop "loop {$0}"
506 else "else {$0}"
507 else if "else if $0 {}"
508 return "return"
509 "#,
510 );
511 }
512
513 #[test]
514 fn test_completion_return_value() {
515 check_keyword_completion(
516 r"
517 fn quux() -> i32 {
518 <|>
519 92
520 }
521 ",
522 r#"
523 if "if $0 {}"
524 match "match $0 {}"
525 while "while $0 {}"
526 loop "loop {$0}"
527 return "return $0;"
528 "#,
529 );
530 check_keyword_completion(
531 r"
532 fn quux() {
533 <|>
534 92
535 }
536 ",
537 r#"
538 if "if $0 {}"
539 match "match $0 {}"
540 while "while $0 {}"
541 loop "loop {$0}"
542 return "return;"
543 "#,
544 );
545 }
546
547 #[test]
548 fn test_completion_return_no_stmt() {
549 check_keyword_completion(
550 r"
551 fn quux() -> i32 {
552 match () {
553 () => <|>
554 }
555 }
556 ",
557 r#"
558 if "if $0 {}"
559 match "match $0 {}"
560 while "while $0 {}"
561 loop "loop {$0}"
562 return "return $0"
563 "#,
564 );
565 }
566
567 #[test]
568 fn test_continue_break_completion() {
569 check_keyword_completion(
570 r"
571 fn quux() -> i32 {
572 loop { <|> }
573 }
574 ",
575 r#"
576 if "if $0 {}"
577 match "match $0 {}"
578 while "while $0 {}"
579 loop "loop {$0}"
580 continue "continue"
581 break "break"
582 return "return $0"
583 "#,
584 );
585 check_keyword_completion(
586 r"
587 fn quux() -> i32 {
588 loop { || { <|> } }
589 }
590 ",
591 r#"
592 if "if $0 {}"
593 match "match $0 {}"
594 while "while $0 {}"
595 loop "loop {$0}"
596 return "return $0"
597 "#,
598 );
599 }
600
601 #[test]
602 fn completes_snippets_in_expressions() { 383 fn completes_snippets_in_expressions() {
603 check_snippet_completion( 384 check_snippet_completion(
604 r"fn foo(x: i32) { <|> }", 385 r"fn foo(x: i32) { <|> }",