aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock8
-rw-r--r--crates/ra_analysis/src/completion.rs372
-rw-r--r--crates/ra_analysis/src/completion/completion_item.rs178
-rw-r--r--crates/ra_analysis/src/completion/reference_completion.rs512
-rw-r--r--crates/ra_analysis/src/imp.rs3
-rw-r--r--crates/ra_analysis/src/lib.rs2
-rw-r--r--crates/ra_analysis/tests/tests.rs60
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs22
-rw-r--r--crates/test_utils/src/lib.rs22
9 files changed, 678 insertions, 501 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5e5db84c3..bf2297e69 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -734,7 +734,7 @@ dependencies = [
734 "serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)", 734 "serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
735 "serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)", 735 "serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
736 "serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", 736 "serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
737 "smol_str 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 737 "smol_str 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
738 "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 738 "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
739 "test_utils 0.1.0", 739 "test_utils 0.1.0",
740 "text_unit 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 740 "text_unit 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -959,7 +959,7 @@ version = "0.1.3"
959source = "registry+https://github.com/rust-lang/crates.io-index" 959source = "registry+https://github.com/rust-lang/crates.io-index"
960dependencies = [ 960dependencies = [
961 "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", 961 "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
962 "smol_str 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 962 "smol_str 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
963 "text_unit 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 963 "text_unit 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
964] 964]
965 965
@@ -1092,7 +1092,7 @@ dependencies = [
1092 1092
1093[[package]] 1093[[package]]
1094name = "smol_str" 1094name = "smol_str"
1095version = "0.1.7" 1095version = "0.1.8"
1096source = "registry+https://github.com/rust-lang/crates.io-index" 1096source = "registry+https://github.com/rust-lang/crates.io-index"
1097dependencies = [ 1097dependencies = [
1098 "serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)", 1098 "serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1565,7 +1565,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1565"checksum sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9d1f3b5de8a167ab06834a7c883bd197f2191e1dda1a22d9ccfeedbf9aded" 1565"checksum sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9d1f3b5de8a167ab06834a7c883bd197f2191e1dda1a22d9ccfeedbf9aded"
1566"checksum slug 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" 1566"checksum slug 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373"
1567"checksum smallvec 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b73ea3738b47563803ef814925e69be00799a8c07420be8b996f8e98fb2336db" 1567"checksum smallvec 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b73ea3738b47563803ef814925e69be00799a8c07420be8b996f8e98fb2336db"
1568"checksum smol_str 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f3ed6f19b800d76574926e458d5f8e2dbea86c2b58c08d33a982448f09ac8d0c" 1568"checksum smol_str 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "486a74e9b9fc53373808f7a17e10fc728adcb1fbe272292271d8bea61175e181"
1569"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" 1569"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
1570"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" 1570"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
1571"checksum superslice 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b50b13d42370e0f5fc62eafdd5c2d20065eaf5458dab215ff3e20e63eea96b30" 1571"checksum superslice 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b50b13d42370e0f5fc62eafdd5c2d20065eaf5458dab215ff3e20e63eea96b30"
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs
index f480af611..a11e98ac0 100644
--- a/crates/ra_analysis/src/completion.rs
+++ b/crates/ra_analysis/src/completion.rs
@@ -1,3 +1,4 @@
1mod completion_item;
1mod reference_completion; 2mod reference_completion;
2 3
3use ra_editor::find_node_at_offset; 4use ra_editor::find_node_at_offset;
@@ -14,23 +15,16 @@ use hir::source_binder;
14 15
15use crate::{ 16use crate::{
16 db, 17 db,
17 Cancelable, FilePosition 18 Cancelable, FilePosition,
19 completion::completion_item::{Completions, CompletionKind},
18}; 20};
19 21
20#[derive(Debug)] 22pub use crate::completion::completion_item::{CompletionItem, InsertText};
21pub struct CompletionItem {
22 /// What user sees in pop-up
23 pub label: String,
24 /// What string is used for filtering, defaults to label
25 pub lookup: Option<String>,
26 /// What is inserted, defaults to label
27 pub snippet: Option<String>,
28}
29 23
30pub(crate) fn completions( 24pub(crate) fn completions(
31 db: &db::RootDatabase, 25 db: &db::RootDatabase,
32 position: FilePosition, 26 position: FilePosition,
33) -> Cancelable<Option<Vec<CompletionItem>>> { 27) -> Cancelable<Option<Completions>> {
34 let original_file = db.source_file(position.file_id); 28 let original_file = db.source_file(position.file_id);
35 // Insert a fake ident to get a valid parse tree 29 // Insert a fake ident to get a valid parse tree
36 let file = { 30 let file = {
@@ -40,15 +34,15 @@ pub(crate) fn completions(
40 34
41 let module = ctry!(source_binder::module_from_position(db, position)?); 35 let module = ctry!(source_binder::module_from_position(db, position)?);
42 36
43 let mut res = Vec::new(); 37 let mut acc = Completions::default();
44 let mut has_completions = false; 38 let mut has_completions = false;
45 // First, let's try to complete a reference to some declaration. 39 // First, let's try to complete a reference to some declaration.
46 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) { 40 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) {
47 has_completions = true; 41 has_completions = true;
48 reference_completion::completions(&mut res, db, &module, &file, name_ref)?; 42 reference_completion::completions(&mut acc, db, &module, &file, name_ref)?;
49 // special case, `trait T { fn foo(i_am_a_name_ref) {} }` 43 // special case, `trait T { fn foo(i_am_a_name_ref) {} }`
50 if is_node::<ast::Param>(name_ref.syntax()) { 44 if is_node::<ast::Param>(name_ref.syntax()) {
51 param_completions(name_ref.syntax(), &mut res); 45 param_completions(&mut acc, name_ref.syntax());
52 } 46 }
53 } 47 }
54 48
@@ -56,14 +50,20 @@ pub(crate) fn completions(
56 if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) { 50 if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) {
57 if is_node::<ast::Param>(name.syntax()) { 51 if is_node::<ast::Param>(name.syntax()) {
58 has_completions = true; 52 has_completions = true;
59 param_completions(name.syntax(), &mut res); 53 param_completions(&mut acc, name.syntax());
60 } 54 }
61 } 55 }
62 let res = if has_completions { Some(res) } else { None }; 56 if !has_completions {
63 Ok(res) 57 return Ok(None);
58 }
59 Ok(Some(acc))
64} 60}
65 61
66fn param_completions(ctx: SyntaxNodeRef, acc: &mut Vec<CompletionItem>) { 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(); 67 let mut params = FxHashMap::default();
68 for node in ctx.ancestors() { 68 for node in ctx.ancestors() {
69 let _ = visitor_ctx(&mut params) 69 let _ = visitor_ctx(&mut params)
@@ -82,11 +82,10 @@ fn param_completions(ctx: SyntaxNodeRef, acc: &mut Vec<CompletionItem>) {
82 } 82 }
83 }) 83 })
84 .for_each(|(label, lookup)| { 84 .for_each(|(label, lookup)| {
85 acc.push(CompletionItem { 85 CompletionItem::new(label)
86 label, 86 .lookup_by(lookup)
87 lookup: Some(lookup), 87 .kind(CompletionKind::Magic)
88 snippet: None, 88 .add_to(acc)
89 })
90 }); 89 });
91 90
92 fn process<'a, N: ast::FnDefOwner<'a>>( 91 fn process<'a, N: ast::FnDefOwner<'a>>(
@@ -111,334 +110,61 @@ fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool {
111} 110}
112 111
113#[cfg(test)] 112#[cfg(test)]
114mod tests { 113fn check_completion(code: &str, expected_completions: &str, kind: CompletionKind) {
115 use test_utils::assert_eq_dbg; 114 use crate::mock_analysis::{single_file_with_position, analysis_and_position};
116 115 let (analysis, position) = if code.contains("//-") {
117 use crate::mock_analysis::single_file_with_position; 116 analysis_and_position(code)
117 } else {
118 single_file_with_position(code)
119 };
120 let completions = completions(&analysis.imp.db, position).unwrap().unwrap();
121 completions.assert_match(expected_completions, kind);
122}
118 123
124#[cfg(test)]
125mod tests {
119 use super::*; 126 use super::*;
120 127
121 fn check_scope_completion(code: &str, expected_completions: &str) { 128 fn check_magic_completion(code: &str, expected_completions: &str) {
122 let (analysis, position) = single_file_with_position(code); 129 check_completion(code, expected_completions, CompletionKind::Magic);
123 let completions = completions(&analysis.imp.db, position)
124 .unwrap()
125 .unwrap()
126 .into_iter()
127 .filter(|c| c.snippet.is_none())
128 .collect::<Vec<_>>();
129 assert_eq_dbg(expected_completions, &completions);
130 }
131
132 fn check_snippet_completion(code: &str, expected_completions: &str) {
133 let (analysis, position) = single_file_with_position(code);
134 let completions = completions(&analysis.imp.db, position)
135 .unwrap()
136 .unwrap()
137 .into_iter()
138 .filter(|c| c.snippet.is_some())
139 .collect::<Vec<_>>();
140 assert_eq_dbg(expected_completions, &completions);
141 }
142
143 #[test]
144 fn test_completion_let_scope() {
145 check_scope_completion(
146 r"
147 fn quux(x: i32) {
148 let y = 92;
149 1 + <|>;
150 let z = ();
151 }
152 ",
153 r#"[CompletionItem { label: "y", lookup: None, snippet: None },
154 CompletionItem { label: "x", lookup: None, snippet: None },
155 CompletionItem { label: "quux", lookup: None, snippet: None }]"#,
156 );
157 }
158
159 #[test]
160 fn test_completion_if_let_scope() {
161 check_scope_completion(
162 r"
163 fn quux() {
164 if let Some(x) = foo() {
165 let y = 92;
166 };
167 if let Some(a) = bar() {
168 let b = 62;
169 1 + <|>
170 }
171 }
172 ",
173 r#"[CompletionItem { label: "b", lookup: None, snippet: None },
174 CompletionItem { label: "a", lookup: None, snippet: None },
175 CompletionItem { label: "quux", lookup: None, snippet: None }]"#,
176 );
177 }
178
179 #[test]
180 fn test_completion_for_scope() {
181 check_scope_completion(
182 r"
183 fn quux() {
184 for x in &[1, 2, 3] {
185 <|>
186 }
187 }
188 ",
189 r#"[CompletionItem { label: "x", lookup: None, snippet: None },
190 CompletionItem { label: "quux", lookup: None, snippet: None }]"#,
191 );
192 }
193
194 #[test]
195 fn test_completion_mod_scope() {
196 check_scope_completion(
197 r"
198 struct Foo;
199 enum Baz {}
200 fn quux() {
201 <|>
202 }
203 ",
204 r#"[CompletionItem { label: "quux", lookup: None, snippet: None },
205 CompletionItem { label: "Foo", lookup: None, snippet: None },
206 CompletionItem { label: "Baz", lookup: None, snippet: None }]"#,
207 );
208 }
209
210 #[test]
211 fn test_completion_mod_scope_no_self_use() {
212 check_scope_completion(
213 r"
214 use foo<|>;
215 ",
216 r#"[]"#,
217 );
218 }
219
220 #[test]
221 fn test_completion_self_path() {
222 check_scope_completion(
223 r"
224 use self::m::<|>;
225
226 mod m {
227 struct Bar;
228 }
229 ",
230 r#"[CompletionItem { label: "Bar", lookup: None, snippet: None }]"#,
231 );
232 }
233
234 #[test]
235 fn test_completion_mod_scope_nested() {
236 check_scope_completion(
237 r"
238 struct Foo;
239 mod m {
240 struct Bar;
241 fn quux() { <|> }
242 }
243 ",
244 r#"[CompletionItem { label: "quux", lookup: None, snippet: None },
245 CompletionItem { label: "Bar", lookup: None, snippet: None }]"#,
246 );
247 }
248
249 #[test]
250 fn test_complete_type() {
251 check_scope_completion(
252 r"
253 struct Foo;
254 fn x() -> <|>
255 ",
256 r#"[CompletionItem { label: "Foo", lookup: None, snippet: None },
257 CompletionItem { label: "x", lookup: None, snippet: None }]"#,
258 )
259 }
260
261 #[test]
262 fn test_complete_shadowing() {
263 check_scope_completion(
264 r"
265 fn foo() -> {
266 let bar = 92;
267 {
268 let bar = 62;
269 <|>
270 }
271 }
272 ",
273 r#"[CompletionItem { label: "bar", lookup: None, snippet: None },
274 CompletionItem { label: "foo", lookup: None, snippet: None }]"#,
275 )
276 }
277
278 #[test]
279 fn test_complete_self() {
280 check_scope_completion(
281 r"
282 impl S { fn foo(&self) { <|> } }
283 ",
284 r#"[CompletionItem { label: "self", lookup: None, snippet: None }]"#,
285 )
286 }
287
288 #[test]
289 fn test_completion_kewords() {
290 check_snippet_completion(r"
291 fn quux() {
292 <|>
293 }
294 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
295 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
296 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
297 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
298 CompletionItem { label: "return", lookup: None, snippet: Some("return") },
299 CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
300 CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
301 }
302
303 #[test]
304 fn test_completion_else() {
305 check_snippet_completion(r"
306 fn quux() {
307 if true {
308 ()
309 } <|>
310 }
311 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
312 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
313 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
314 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
315 CompletionItem { label: "else", lookup: None, snippet: Some("else {$0}") },
316 CompletionItem { label: "else if", lookup: None, snippet: Some("else if $0 {}") },
317 CompletionItem { label: "return", lookup: None, snippet: Some("return") },
318 CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
319 CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
320 }
321
322 #[test]
323 fn test_completion_return_value() {
324 check_snippet_completion(r"
325 fn quux() -> i32 {
326 <|>
327 92
328 }
329 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
330 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
331 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
332 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
333 CompletionItem { label: "return", lookup: None, snippet: Some("return $0;") },
334 CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
335 CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
336 check_snippet_completion(r"
337 fn quux() {
338 <|>
339 92
340 }
341 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
342 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
343 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
344 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
345 CompletionItem { label: "return", lookup: None, snippet: Some("return;") },
346 CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
347 CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
348 }
349
350 #[test]
351 fn test_completion_return_no_stmt() {
352 check_snippet_completion(r"
353 fn quux() -> i32 {
354 match () {
355 () => <|>
356 }
357 }
358 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
359 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
360 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
361 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
362 CompletionItem { label: "return", lookup: None, snippet: Some("return $0") },
363 CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
364 CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
365 }
366
367 #[test]
368 fn test_continue_break_completion() {
369 check_snippet_completion(r"
370 fn quux() -> i32 {
371 loop { <|> }
372 }
373 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
374 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
375 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
376 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
377 CompletionItem { label: "continue", lookup: None, snippet: Some("continue") },
378 CompletionItem { label: "break", lookup: None, snippet: Some("break") },
379 CompletionItem { label: "return", lookup: None, snippet: Some("return $0") },
380 CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
381 CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
382 check_snippet_completion(r"
383 fn quux() -> i32 {
384 loop { || { <|> } }
385 }
386 ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") },
387 CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") },
388 CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") },
389 CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") },
390 CompletionItem { label: "return", lookup: None, snippet: Some("return $0") },
391 CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") },
392 CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#);
393 } 130 }
394 131
395 #[test] 132 #[test]
396 fn test_param_completion_last_param() { 133 fn test_param_completion_last_param() {
397 check_scope_completion(r" 134 check_magic_completion(
135 r"
398 fn foo(file_id: FileId) {} 136 fn foo(file_id: FileId) {}
399 fn bar(file_id: FileId) {} 137 fn bar(file_id: FileId) {}
400 fn baz(file<|>) {} 138 fn baz(file<|>) {}
401 ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); 139 ",
140 r#"file_id "file_id: FileId""#,
141 );
402 } 142 }
403 143
404 #[test] 144 #[test]
405 fn test_param_completion_nth_param() { 145 fn test_param_completion_nth_param() {
406 check_scope_completion(r" 146 check_magic_completion(
147 r"
407 fn foo(file_id: FileId) {} 148 fn foo(file_id: FileId) {}
408 fn bar(file_id: FileId) {} 149 fn bar(file_id: FileId) {}
409 fn baz(file<|>, x: i32) {} 150 fn baz(file<|>, x: i32) {}
410 ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); 151 ",
152 r#"file_id "file_id: FileId""#,
153 );
411 } 154 }
412 155
413 #[test] 156 #[test]
414 fn test_param_completion_trait_param() { 157 fn test_param_completion_trait_param() {
415 check_scope_completion(r" 158 check_magic_completion(
159 r"
416 pub(crate) trait SourceRoot { 160 pub(crate) trait SourceRoot {
417 pub fn contains(&self, file_id: FileId) -> bool; 161 pub fn contains(&self, file_id: FileId) -> bool;
418 pub fn module_map(&self) -> &ModuleMap; 162 pub fn module_map(&self) -> &ModuleMap;
419 pub fn lines(&self, file_id: FileId) -> &LineIndex; 163 pub fn lines(&self, file_id: FileId) -> &LineIndex;
420 pub fn syntax(&self, file<|>) 164 pub fn syntax(&self, file<|>)
421 } 165 }
422 ", r#"[CompletionItem { label: "self", lookup: None, snippet: None },
423 CompletionItem { label: "SourceRoot", lookup: None, snippet: None },
424 CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#);
425 }
426
427 #[test]
428 fn test_item_snippets() {
429 // check_snippet_completion(r"
430 // <|>
431 // ",
432 // r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##,
433 // );
434 check_snippet_completion(r"
435 #[cfg(test)]
436 mod tests {
437 <|>
438 }
439 ", 166 ",
440 r##"[CompletionItem { label: "Test function", lookup: Some("tfn"), snippet: Some("#[test]\nfn ${1:feature}() {\n$0\n}") }, 167 r#"file_id "file_id: FileId""#,
441 CompletionItem { label: "pub(crate)", lookup: None, snippet: Some("pub(crate) $0") }]"##,
442 ); 168 );
443 } 169 }
444} 170}
diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs
new file mode 100644
index 000000000..d5d751759
--- /dev/null
+++ b/crates/ra_analysis/src/completion/completion_item.rs
@@ -0,0 +1,178 @@
1/// `CompletionItem` describes a single completion variant in the editor pop-up.
2/// It is basically a POD with various properties. To construct a
3/// `CompletionItem`, use `new` method and the `Builder` struct.
4#[derive(Debug)]
5pub struct CompletionItem {
6 label: String,
7 lookup: Option<String>,
8 snippet: Option<String>,
9 /// Used only internally in test, to check only specific kind of completion.
10 kind: CompletionKind,
11}
12
13pub enum InsertText {
14 PlainText { text: String },
15 Snippet { text: String },
16}
17
18#[derive(Debug, PartialEq, Eq)]
19pub(crate) enum CompletionKind {
20 /// Parser-based keyword completion.
21 Keyword,
22 /// Your usual "complete all valid identifiers".
23 Reference,
24 /// "Secret sauce" completions.
25 Magic,
26 Snippet,
27 Unspecified,
28}
29
30impl CompletionItem {
31 pub(crate) fn new(label: impl Into<String>) -> Builder {
32 let label = label.into();
33 Builder {
34 label,
35 lookup: None,
36 snippet: None,
37 kind: CompletionKind::Unspecified,
38 }
39 }
40 /// What user sees in pop-up in the UI.
41 pub fn label(&self) -> &str {
42 &self.label
43 }
44 /// What string is used for filtering.
45 pub fn lookup(&self) -> &str {
46 self.lookup
47 .as_ref()
48 .map(|it| it.as_str())
49 .unwrap_or(self.label())
50 }
51 /// What is inserted.
52 pub fn insert_text(&self) -> InsertText {
53 match &self.snippet {
54 None => InsertText::PlainText {
55 text: self.label.clone(),
56 },
57 Some(it) => InsertText::Snippet { text: it.clone() },
58 }
59 }
60}
61
62/// A helper to make `CompletionItem`s.
63#[must_use]
64pub(crate) struct Builder {
65 label: String,
66 lookup: Option<String>,
67 snippet: Option<String>,
68 kind: CompletionKind,
69}
70
71impl Builder {
72 pub(crate) fn add_to(self, acc: &mut Completions) {
73 acc.add(self.build())
74 }
75
76 pub(crate) fn build(self) -> CompletionItem {
77 CompletionItem {
78 label: self.label,
79 lookup: self.lookup,
80 snippet: self.snippet,
81 kind: self.kind,
82 }
83 }
84 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
85 self.lookup = Some(lookup.into());
86 self
87 }
88 pub(crate) fn snippet(mut self, snippet: impl Into<String>) -> Builder {
89 self.snippet = Some(snippet.into());
90 self
91 }
92 pub(crate) fn kind(mut self, kind: CompletionKind) -> Builder {
93 self.kind = kind;
94 self
95 }
96}
97
98impl Into<CompletionItem> for Builder {
99 fn into(self) -> CompletionItem {
100 self.build()
101 }
102}
103
104/// Represents an in-progress set of completions being built.
105#[derive(Debug, Default)]
106pub(crate) struct Completions {
107 buf: Vec<CompletionItem>,
108}
109
110impl Completions {
111 pub(crate) fn add(&mut self, item: impl Into<CompletionItem>) {
112 self.buf.push(item.into())
113 }
114 pub(crate) fn add_all<I>(&mut self, items: I)
115 where
116 I: IntoIterator,
117 I::Item: Into<CompletionItem>,
118 {
119 items.into_iter().for_each(|item| self.add(item.into()))
120 }
121
122 #[cfg(test)]
123 pub(crate) fn assert_match(&self, expected: &str, kind: CompletionKind) {
124 let expected = normalize(expected);
125 let actual = self.debug_render(kind);
126 test_utils::assert_eq_text!(expected.as_str(), actual.as_str(),);
127
128 /// Normalize the textual representation of `Completions`:
129 /// replace `;` with newlines, normalize whitespace
130 fn normalize(expected: &str) -> String {
131 use ra_syntax::{tokenize, TextUnit, TextRange, SyntaxKind::SEMI};
132 let mut res = String::new();
133 for line in expected.trim().lines() {
134 let line = line.trim();
135 let mut start_offset: TextUnit = 0.into();
136 // Yep, we use rust tokenize in completion tests :-)
137 for token in tokenize(line) {
138 let range = TextRange::offset_len(start_offset, token.len);
139 start_offset += token.len;
140 if token.kind == SEMI {
141 res.push('\n');
142 } else {
143 res.push_str(&line[range]);
144 }
145 }
146
147 res.push('\n');
148 }
149 res
150 }
151 }
152
153 #[cfg(test)]
154 fn debug_render(&self, kind: CompletionKind) -> String {
155 let mut res = String::new();
156 for c in self.buf.iter() {
157 if c.kind == kind {
158 if let Some(lookup) = &c.lookup {
159 res.push_str(lookup);
160 res.push_str(&format!(" {:?}", c.label));
161 } else {
162 res.push_str(&c.label);
163 }
164 if let Some(snippet) = &c.snippet {
165 res.push_str(&format!(" {:?}", snippet));
166 }
167 res.push('\n');
168 }
169 }
170 res
171 }
172}
173
174impl Into<Vec<CompletionItem>> for Completions {
175 fn into(self) -> Vec<CompletionItem> {
176 self.buf
177 }
178}
diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs
index f483ed045..c2a650b6d 100644
--- a/crates/ra_analysis/src/completion/reference_completion.rs
+++ b/crates/ra_analysis/src/completion/reference_completion.rs
@@ -6,21 +6,19 @@ use ra_syntax::{
6 ast::{self, LoopBodyOwner}, 6 ast::{self, LoopBodyOwner},
7 SyntaxKind::*, 7 SyntaxKind::*,
8}; 8};
9use hir::{ 9use hir::{
10 self, 10 self,
11 FnScopes, 11 FnScopes, Def, Path
12 Def,
13 Path,
14}; 12};
15 13
16use crate::{ 14use crate::{
17 db::RootDatabase, 15 db::RootDatabase,
18 completion::CompletionItem, 16 completion::{CompletionItem, Completions, CompletionKind::*},
19 Cancelable 17 Cancelable
20}; 18};
21 19
22pub(super) fn completions( 20pub(super) fn completions(
23 acc: &mut Vec<CompletionItem>, 21 acc: &mut Completions,
24 db: &RootDatabase, 22 db: &RootDatabase,
25 module: &hir::Module, 23 module: &hir::Module,
26 file: &SourceFileNode, 24 file: &SourceFileNode,
@@ -41,25 +39,23 @@ pub(super) fn completions(
41 } 39 }
42 40
43 let module_scope = module.scope(db)?; 41 let module_scope = module.scope(db)?;
44 acc.extend( 42 module_scope
45 module_scope 43 .entries()
46 .entries() 44 .filter(|(_name, res)| {
47 .filter(|(_name, res)| { 45 // Don't expose this item
48 // Don't expose this item 46 match res.import {
49 match res.import { 47 None => true,
50 None => true, 48 Some(import) => {
51 Some(import) => { 49 let range = import.range(db, module.source().file_id());
52 let range = import.range(db, module.source().file_id()); 50 !range.is_subrange(&name_ref.syntax().range())
53 !range.is_subrange(&name_ref.syntax().range())
54 }
55 } 51 }
56 }) 52 }
57 .map(|(name, _res)| CompletionItem { 53 })
58 label: name.to_string(), 54 .for_each(|(name, _res)| {
59 lookup: None, 55 CompletionItem::new(name.to_string())
60 snippet: None, 56 .kind(Reference)
61 }), 57 .add_to(acc)
62 ); 58 });
63 } 59 }
64 NameRefKind::Path(path) => complete_path(acc, db, module, path)?, 60 NameRefKind::Path(path) => complete_path(acc, db, module, path)?,
65 NameRefKind::BareIdentInMod => { 61 NameRefKind::BareIdentInMod => {
@@ -125,30 +121,24 @@ fn classify_name_ref(name_ref: ast::NameRef) -> Option<NameRefKind> {
125 None 121 None
126} 122}
127 123
128fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) { 124fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Completions) {
129 let mut shadowed = FxHashSet::default(); 125 let mut shadowed = FxHashSet::default();
130 acc.extend( 126 scopes
131 scopes 127 .scope_chain(name_ref.syntax())
132 .scope_chain(name_ref.syntax()) 128 .flat_map(|scope| scopes.entries(scope).iter())
133 .flat_map(|scope| scopes.entries(scope).iter()) 129 .filter(|entry| shadowed.insert(entry.name()))
134 .filter(|entry| shadowed.insert(entry.name())) 130 .for_each(|entry| {
135 .map(|entry| CompletionItem { 131 CompletionItem::new(entry.name().to_string())
136 label: entry.name().to_string(), 132 .kind(Reference)
137 lookup: None, 133 .add_to(acc)
138 snippet: None, 134 });
139 }),
140 );
141 if scopes.self_param.is_some() { 135 if scopes.self_param.is_some() {
142 acc.push(CompletionItem { 136 CompletionItem::new("self").kind(Reference).add_to(acc);
143 label: "self".to_string(),
144 lookup: None,
145 snippet: None,
146 })
147 } 137 }
148} 138}
149 139
150fn complete_path( 140fn complete_path(
151 acc: &mut Vec<CompletionItem>, 141 acc: &mut Completions,
152 db: &RootDatabase, 142 db: &RootDatabase,
153 module: &hir::Module, 143 module: &hir::Module,
154 mut path: Path, 144 mut path: Path,
@@ -166,58 +156,56 @@ fn complete_path(
166 _ => return Ok(()), 156 _ => return Ok(()),
167 }; 157 };
168 let module_scope = target_module.scope(db)?; 158 let module_scope = target_module.scope(db)?;
169 let completions = module_scope.entries().map(|(name, _res)| CompletionItem { 159 module_scope.entries().for_each(|(name, _res)| {
170 label: name.to_string(), 160 CompletionItem::new(name.to_string())
171 lookup: None, 161 .kind(Reference)
172 snippet: None, 162 .add_to(acc)
173 }); 163 });
174 acc.extend(completions);
175 Ok(()) 164 Ok(())
176} 165}
177 166
178fn complete_mod_item_snippets(acc: &mut Vec<CompletionItem>) { 167fn complete_mod_item_snippets(acc: &mut Completions) {
179 acc.push(CompletionItem { 168 CompletionItem::new("Test function")
180 label: "Test function".to_string(), 169 .lookup_by("tfn")
181 lookup: Some("tfn".to_string()), 170 .snippet(
182 snippet: Some( 171 "\
183 "#[test]\n\ 172#[test]
184 fn ${1:feature}() {\n\ 173fn ${1:feature}() {
185 $0\n\ 174 $0
186 }" 175}",
187 .to_string(), 176 )
188 ), 177 .kind(Snippet)
189 }); 178 .add_to(acc);
190 acc.push(CompletionItem { 179 CompletionItem::new("pub(crate)")
191 label: "pub(crate)".to_string(), 180 .snippet("pub(crate) $0")
192 lookup: None, 181 .kind(Snippet)
193 snippet: Some("pub(crate) $0".to_string()), 182 .add_to(acc);
194 })
195} 183}
196 184
197fn complete_expr_keywords( 185fn complete_expr_keywords(
198 file: &SourceFileNode, 186 file: &SourceFileNode,
199 fn_def: ast::FnDef, 187 fn_def: ast::FnDef,
200 name_ref: ast::NameRef, 188 name_ref: ast::NameRef,
201 acc: &mut Vec<CompletionItem>, 189 acc: &mut Completions,
202) { 190) {
203 acc.push(keyword("if", "if $0 {}")); 191 acc.add(keyword("if", "if $0 {}"));
204 acc.push(keyword("match", "match $0 {}")); 192 acc.add(keyword("match", "match $0 {}"));
205 acc.push(keyword("while", "while $0 {}")); 193 acc.add(keyword("while", "while $0 {}"));
206 acc.push(keyword("loop", "loop {$0}")); 194 acc.add(keyword("loop", "loop {$0}"));
207 195
208 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) { 196 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
209 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) { 197 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
210 if if_expr.syntax().range().end() < name_ref.syntax().range().start() { 198 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
211 acc.push(keyword("else", "else {$0}")); 199 acc.add(keyword("else", "else {$0}"));
212 acc.push(keyword("else if", "else if $0 {}")); 200 acc.add(keyword("else if", "else if $0 {}"));
213 } 201 }
214 } 202 }
215 } 203 }
216 if is_in_loop_body(name_ref) { 204 if is_in_loop_body(name_ref) {
217 acc.push(keyword("continue", "continue")); 205 acc.add(keyword("continue", "continue"));
218 acc.push(keyword("break", "break")); 206 acc.add(keyword("break", "break"));
219 } 207 }
220 acc.extend(complete_return(fn_def, name_ref)); 208 acc.add_all(complete_return(fn_def, name_ref));
221} 209}
222 210
223fn is_in_loop_body(name_ref: ast::NameRef) -> bool { 211fn is_in_loop_body(name_ref: ast::NameRef) -> bool {
@@ -272,23 +260,363 @@ fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option<Complet
272 Some(keyword("return", snip)) 260 Some(keyword("return", snip))
273} 261}
274 262
275fn keyword(kw: &str, snip: &str) -> CompletionItem { 263fn keyword(kw: &str, snippet: &str) -> CompletionItem {
276 CompletionItem { 264 CompletionItem::new(kw)
277 label: kw.to_string(), 265 .kind(Keyword)
278 lookup: None, 266 .snippet(snippet)
279 snippet: Some(snip.to_string()), 267 .build()
280 }
281} 268}
282 269
283fn complete_expr_snippets(acc: &mut Vec<CompletionItem>) { 270fn complete_expr_snippets(acc: &mut Completions) {
284 acc.push(CompletionItem { 271 CompletionItem::new("pd")
285 label: "pd".to_string(), 272 .snippet("eprintln!(\"$0 = {:?}\", $0);")
286 lookup: None, 273 .kind(Snippet)
287 snippet: Some("eprintln!(\"$0 = {:?}\", $0);".to_string()), 274 .add_to(acc);
288 }); 275 CompletionItem::new("ppd")
289 acc.push(CompletionItem { 276 .snippet("eprintln!(\"$0 = {:#?}\", $0);")
290 label: "ppd".to_string(), 277 .kind(Snippet)
291 lookup: None, 278 .add_to(acc);
292 snippet: Some("eprintln!(\"$0 = {:#?}\", $0);".to_string()), 279}
293 }); 280
281#[cfg(test)]
282mod tests {
283 use crate::completion::{CompletionKind, check_completion};
284
285 fn check_reference_completion(code: &str, expected_completions: &str) {
286 check_completion(code, expected_completions, CompletionKind::Reference);
287 }
288
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) {
294 check_completion(code, expected_completions, CompletionKind::Snippet);
295 }
296
297 #[test]
298 fn test_completion_let_scope() {
299 check_reference_completion(
300 r"
301 fn quux(x: i32) {
302 let y = 92;
303 1 + <|>;
304 let z = ();
305 }
306 ",
307 "y;x;quux",
308 );
309 }
310
311 #[test]
312 fn test_completion_if_let_scope() {
313 check_reference_completion(
314 r"
315 fn quux() {
316 if let Some(x) = foo() {
317 let y = 92;
318 };
319 if let Some(a) = bar() {
320 let b = 62;
321 1 + <|>
322 }
323 }
324 ",
325 "b;a;quux",
326 );
327 }
328
329 #[test]
330 fn test_completion_for_scope() {
331 check_reference_completion(
332 r"
333 fn quux() {
334 for x in &[1, 2, 3] {
335 <|>
336 }
337 }
338 ",
339 "x;quux",
340 );
341 }
342
343 #[test]
344 fn test_completion_mod_scope() {
345 check_reference_completion(
346 r"
347 struct Foo;
348 enum Baz {}
349 fn quux() {
350 <|>
351 }
352 ",
353 "quux;Foo;Baz",
354 );
355 }
356
357 #[test]
358 fn test_completion_mod_scope_no_self_use() {
359 check_reference_completion(
360 r"
361 use foo<|>;
362 ",
363 "",
364 );
365 }
366
367 #[test]
368 fn test_completion_self_path() {
369 check_reference_completion(
370 r"
371 use self::m::<|>;
372
373 mod m {
374 struct Bar;
375 }
376 ",
377 "Bar",
378 );
379 }
380
381 #[test]
382 fn test_completion_mod_scope_nested() {
383 check_reference_completion(
384 r"
385 struct Foo;
386 mod m {
387 struct Bar;
388 fn quux() { <|> }
389 }
390 ",
391 "quux;Bar",
392 );
393 }
394
395 #[test]
396 fn test_complete_type() {
397 check_reference_completion(
398 r"
399 struct Foo;
400 fn x() -> <|>
401 ",
402 "Foo;x",
403 )
404 }
405
406 #[test]
407 fn test_complete_shadowing() {
408 check_reference_completion(
409 r"
410 fn foo() -> {
411 let bar = 92;
412 {
413 let bar = 62;
414 <|>
415 }
416 }
417 ",
418 "bar;foo",
419 )
420 }
421
422 #[test]
423 fn test_complete_self() {
424 check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self")
425 }
426
427 #[test]
428 fn test_complete_crate_path() {
429 check_reference_completion(
430 "
431 //- /lib.rs
432 mod foo;
433 struct Spam;
434 //- /foo.rs
435 use crate::Sp<|>
436 ",
437 "Spam;foo",
438 );
439 }
440
441 #[test]
442 fn test_complete_crate_path_with_braces() {
443 check_reference_completion(
444 "
445 //- /lib.rs
446 mod foo;
447 struct Spam;
448 //- /foo.rs
449 use crate::{Sp<|>};
450 ",
451 "Spam;foo",
452 );
453 }
454
455 #[test]
456 fn test_complete_crate_path_in_nested_tree() {
457 check_reference_completion(
458 "
459 //- /lib.rs
460 mod foo;
461 pub mod bar {
462 pub mod baz {
463 pub struct Spam;
464 }
465 }
466 //- /foo.rs
467 use crate::{bar::{baz::Sp<|>}};
468 ",
469 "Spam",
470 );
471 }
472
473 #[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 test_item_snippets() {
603 // check_snippet_completion(r"
604 // <|>
605 // ",
606 // r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##,
607 // );
608 check_snippet_completion(
609 r"
610 #[cfg(test)]
611 mod tests {
612 <|>
613 }
614 ",
615 r##"
616 tfn "Test function" "#[test]\nfn ${1:feature}() {\n $0\n}"
617 pub(crate) "pub(crate) $0"
618 "##,
619 );
620 }
621
294} 622}
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs
index 5701e1ae2..340f7c78c 100644
--- a/crates/ra_analysis/src/imp.rs
+++ b/crates/ra_analysis/src/imp.rs
@@ -219,7 +219,8 @@ impl AnalysisImpl {
219 self.db.crate_graph().crate_root(crate_id) 219 self.db.crate_graph().crate_root(crate_id)
220 } 220 }
221 pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> { 221 pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> {
222 completions(&self.db, position) 222 let completions = completions(&self.db, position)?;
223 Ok(completions.map(|it| it.into()))
223 } 224 }
224 pub fn approximately_resolve_symbol( 225 pub fn approximately_resolve_symbol(
225 &self, 226 &self,
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs
index c7e7dc1c0..1c8aa308b 100644
--- a/crates/ra_analysis/src/lib.rs
+++ b/crates/ra_analysis/src/lib.rs
@@ -30,7 +30,7 @@ use crate::{
30}; 30};
31 31
32pub use crate::{ 32pub use crate::{
33 completion::CompletionItem, 33 completion::{CompletionItem, InsertText},
34}; 34};
35pub use ra_editor::{ 35pub use ra_editor::{
36 FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, RunnableKind, StructureNode, 36 FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, RunnableKind, StructureNode,
diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs
index 67738da48..938ca797a 100644
--- a/crates/ra_analysis/tests/tests.rs
+++ b/crates/ra_analysis/tests/tests.rs
@@ -452,63 +452,3 @@ fn test_find_all_refs_for_fn_param() {
452 let refs = get_all_refs(code); 452 let refs = get_all_refs(code);
453 assert_eq!(refs.len(), 2); 453 assert_eq!(refs.len(), 2);
454} 454}
455
456#[test]
457fn test_complete_crate_path() {
458 let (analysis, position) = analysis_and_position(
459 "
460 //- /lib.rs
461 mod foo;
462 struct Spam;
463 //- /foo.rs
464 use crate::Sp<|>
465 ",
466 );
467 let completions = analysis.completions(position).unwrap().unwrap();
468 assert_eq_dbg(
469 r#"[CompletionItem { label: "Spam", lookup: None, snippet: None },
470 CompletionItem { label: "foo", lookup: None, snippet: None }]"#,
471 &completions,
472 );
473}
474
475#[test]
476fn test_complete_crate_path_with_braces() {
477 let (analysis, position) = analysis_and_position(
478 "
479 //- /lib.rs
480 mod foo;
481 struct Spam;
482 //- /foo.rs
483 use crate::{Sp<|>};
484 ",
485 );
486 let completions = analysis.completions(position).unwrap().unwrap();
487 assert_eq_dbg(
488 r#"[CompletionItem { label: "Spam", lookup: None, snippet: None },
489 CompletionItem { label: "foo", lookup: None, snippet: None }]"#,
490 &completions,
491 );
492}
493
494#[test]
495fn test_complete_crate_path_in_nested_tree() {
496 let (analysis, position) = analysis_and_position(
497 "
498 //- /lib.rs
499 mod foo;
500 pub mod bar {
501 pub mod baz {
502 pub struct Spam;
503 }
504 }
505 //- /foo.rs
506 use crate::{bar::{baz::Sp<|>}};
507 ",
508 );
509 let completions = analysis.completions(position).unwrap().unwrap();
510 assert_eq_dbg(
511 r#"[CompletionItem { label: "Spam", lookup: None, snippet: None }]"#,
512 &completions,
513 );
514}
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 1751d7fa8..2dfeb061a 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -8,7 +8,7 @@ use languageserver_types::{
8 PrepareRenameResponse, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit, 8 PrepareRenameResponse, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit,
9 WorkspaceEdit, ParameterInformation, ParameterLabel, SignatureInformation, Hover, HoverContents, 9 WorkspaceEdit, ParameterInformation, ParameterLabel, SignatureInformation, Hover, HoverContents,
10}; 10};
11use ra_analysis::{FileId, FoldKind, Query, RunnableKind, FilePosition}; 11use ra_analysis::{FileId, FoldKind, Query, RunnableKind, FilePosition, InsertText};
12use ra_syntax::{TextUnit, text_utils::intersect}; 12use ra_syntax::{TextUnit, text_utils::intersect};
13use ra_text_edit::text_utils::contains_offset_nonstrict; 13use ra_text_edit::text_utils::contains_offset_nonstrict;
14use rustc_hash::FxHashMap; 14use rustc_hash::FxHashMap;
@@ -423,15 +423,21 @@ pub fn handle_completion(
423 .into_iter() 423 .into_iter()
424 .map(|item| { 424 .map(|item| {
425 let mut res = CompletionItem { 425 let mut res = CompletionItem {
426 label: item.label, 426 label: item.label().to_string(),
427 filter_text: item.lookup, 427 filter_text: Some(item.lookup().to_string()),
428 ..Default::default() 428 ..Default::default()
429 }; 429 };
430 if let Some(snip) = item.snippet { 430 match item.insert_text() {
431 res.insert_text = Some(snip); 431 InsertText::PlainText { text } => {
432 res.insert_text_format = Some(InsertTextFormat::Snippet); 432 res.insert_text = Some(text);
433 res.kind = Some(CompletionItemKind::Keyword); 433 res.insert_text_format = Some(InsertTextFormat::PlainText);
434 }; 434 }
435 InsertText::Snippet { text } => {
436 res.insert_text = Some(text);
437 res.insert_text_format = Some(InsertTextFormat::Snippet);
438 res.kind = Some(CompletionItemKind::Keyword);
439 }
440 }
435 res 441 res
436 }) 442 })
437 .collect(); 443 .collect();
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index 1ae800d7c..beb936c61 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -10,22 +10,20 @@ pub const CURSOR_MARKER: &str = "<|>";
10 10
11#[macro_export] 11#[macro_export]
12macro_rules! assert_eq_text { 12macro_rules! assert_eq_text {
13 ($expected:expr, $actual:expr) => {{ 13 ($expected:expr, $actual:expr) => {
14 let expected = $expected; 14 assert_eq_text!($expected, $actual,)
15 let actual = $actual; 15 };
16 if expected != actual {
17 let changeset = $crate::__Changeset::new(actual, expected, "\n");
18 println!("Expected:\n{}\n\nActual:\n{}\nDiff:{}\n", expected, actual, changeset);
19 panic!("text differs");
20 }
21 }};
22 ($expected:expr, $actual:expr, $($tt:tt)*) => {{ 16 ($expected:expr, $actual:expr, $($tt:tt)*) => {{
23 let expected = $expected; 17 let expected = $expected;
24 let actual = $actual; 18 let actual = $actual;
25 if expected != actual { 19 if expected != actual {
26 let changeset = $crate::__Changeset::new(actual, expected, "\n"); 20 if expected.trim() == actual.trim() {
27 println!("Expected:\n{}\n\nActual:\n{}\n\nDiff:\n{}\n", expected, actual, changeset); 21 eprintln!("Expected:\n{:?}\n\nActual:\n{:?}\n\nWhitespace difference\n", expected, actual);
28 println!($($tt)*); 22 } else {
23 let changeset = $crate::__Changeset::new(actual, expected, "\n");
24 eprintln!("Expected:\n{}\n\nActual:\n{}\n\nDiff:\n{}\n", expected, actual, changeset);
25 }
26 eprintln!($($tt)*);
29 panic!("text differs"); 27 panic!("text differs");
30 } 28 }
31 }}; 29 }};