aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/gen_lsp_server/Cargo.toml3
-rw-r--r--crates/gen_lsp_server/src/msg.rs12
-rw-r--r--crates/ra_analysis/src/completion.rs466
-rw-r--r--crates/ra_analysis/src/completion/complete_fn_param.rs102
-rw-r--r--crates/ra_analysis/src/completion/complete_keyword.rs204
-rw-r--r--crates/ra_analysis/src/completion/complete_path.rs95
-rw-r--r--crates/ra_analysis/src/completion/complete_scope.rs173
-rw-r--r--crates/ra_analysis/src/completion/complete_snippet.rs73
-rw-r--r--crates/ra_analysis/src/completion/completion_context.rs156
-rw-r--r--crates/ra_analysis/src/completion/completion_item.rs214
-rw-r--r--crates/ra_analysis/src/completion/reference_completion.rs294
-rw-r--r--crates/ra_analysis/src/imp.rs9
-rw-r--r--crates/ra_analysis/src/lib.rs37
-rw-r--r--crates/ra_analysis/src/symbol_index.rs4
-rw-r--r--crates/ra_analysis/tests/tests.rs60
-rw-r--r--crates/ra_db/src/input.rs3
-rw-r--r--crates/ra_editor/src/typing.rs58
-rw-r--r--crates/ra_hir/src/function.rs2
-rw-r--r--crates/ra_hir/src/function/scope.rs48
-rw-r--r--crates/ra_hir/src/lib.rs2
-rw-r--r--crates/ra_hir/src/module.rs2
-rw-r--r--crates/ra_hir/src/module/nameres.rs2
-rw-r--r--crates/ra_hir/src/module/nameres/tests.rs19
-rw-r--r--crates/ra_hir/src/path.rs21
-rw-r--r--crates/ra_hir/src/source_binder.rs12
-rw-r--r--crates/ra_lsp_server/Cargo.toml1
-rw-r--r--crates/ra_lsp_server/src/conv.rs44
-rw-r--r--crates/ra_lsp_server/src/main.rs4
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs56
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs21
-rw-r--r--crates/ra_lsp_server/src/req.rs2
-rw-r--r--crates/ra_lsp_server/src/server_world.rs1
-rw-r--r--crates/ra_syntax/src/ast.rs2
-rw-r--r--crates/ra_syntax/src/grammar/type_params.rs7
-rw-r--r--crates/ra_syntax/src/lexer.rs2
-rw-r--r--crates/ra_syntax/src/string_lexing/byte.rs2
-rw-r--r--crates/ra_syntax/src/string_lexing/byte_string.rs2
-rw-r--r--crates/ra_syntax/src/string_lexing/char.rs2
-rw-r--r--crates/ra_syntax/src/string_lexing/string.rs2
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0056_where_clause.rs1
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0056_where_clause.txt44
-rw-r--r--crates/ra_syntax/tests/data/parser/ok/0036_fully_qualified.rs8
-rw-r--r--crates/ra_syntax/tests/data/parser/ok/0036_fully_qualified.txt88
-rw-r--r--crates/ra_syntax/tests/test.rs12
-rw-r--r--crates/test_utils/src/lib.rs22
45 files changed, 1520 insertions, 874 deletions
diff --git a/crates/gen_lsp_server/Cargo.toml b/crates/gen_lsp_server/Cargo.toml
index 6c91e38aa..9776a82e3 100644
--- a/crates/gen_lsp_server/Cargo.toml
+++ b/crates/gen_lsp_server/Cargo.toml
@@ -12,6 +12,5 @@ languageserver-types = "0.53.0"
12log = "0.4.3" 12log = "0.4.3"
13failure = "0.1.2" 13failure = "0.1.2"
14serde_json = "1.0.24" 14serde_json = "1.0.24"
15serde = "1.0.71" 15serde = { version = "1.0.71", features = ["derive"] }
16serde_derive = "1.0.71"
17crossbeam-channel = "0.2.4" 16crossbeam-channel = "0.2.4"
diff --git a/crates/gen_lsp_server/src/msg.rs b/crates/gen_lsp_server/src/msg.rs
index 22d273d55..f68cbc541 100644
--- a/crates/gen_lsp_server/src/msg.rs
+++ b/crates/gen_lsp_server/src/msg.rs
@@ -1,13 +1,13 @@
1use std::io::{BufRead, Write}; 1use std::io::{BufRead, Write};
2 2
3use languageserver_types::{notification::Notification, request::Request}; 3use languageserver_types::{notification::Notification, request::Request};
4use serde_derive::{Deserialize, Serialize}; 4use serde::{Deserialize, Serialize};
5use serde_json::{from_str, from_value, to_string, to_value, Value}; 5use serde_json::{from_str, from_value, to_string, to_value, Value};
6use failure::{bail, format_err}; 6use failure::{bail, format_err};
7 7
8use crate::Result; 8use crate::Result;
9 9
10#[derive(Debug, Serialize, Deserialize, Clone)] 10#[derive(Serialize, Deserialize, Debug, Clone)]
11#[serde(untagged)] 11#[serde(untagged)]
12pub enum RawMessage { 12pub enum RawMessage {
13 Request(RawRequest), 13 Request(RawRequest),
@@ -152,12 +152,18 @@ impl RawNotification {
152 params: to_value(params).unwrap(), 152 params: to_value(params).unwrap(),
153 } 153 }
154 } 154 }
155 pub fn is<N>(&self) -> bool
156 where
157 N: Notification,
158 {
159 self.method == N::METHOD
160 }
155 pub fn cast<N>(self) -> ::std::result::Result<N::Params, RawNotification> 161 pub fn cast<N>(self) -> ::std::result::Result<N::Params, RawNotification>
156 where 162 where
157 N: Notification, 163 N: Notification,
158 N::Params: serde::de::DeserializeOwned, 164 N::Params: serde::de::DeserializeOwned,
159 { 165 {
160 if self.method != N::METHOD { 166 if !self.is::<N>() {
161 return Err(self); 167 return Err(self);
162 } 168 }
163 Ok(from_value(self.params).unwrap()) 169 Ok(from_value(self.params).unwrap())
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs
index f480af611..d742d6295 100644
--- a/crates/ra_analysis/src/completion.rs
+++ b/crates/ra_analysis/src/completion.rs
@@ -1,444 +1,60 @@
1mod reference_completion; 1mod completion_item;
2mod completion_context;
3
4mod complete_fn_param;
5mod complete_keyword;
6mod complete_snippet;
7mod complete_path;
8mod complete_scope;
2 9
3use ra_editor::find_node_at_offset;
4use ra_text_edit::AtomTextEdit;
5use ra_syntax::{
6 algo::visit::{visitor_ctx, VisitorCtx},
7 ast,
8 AstNode,
9 SyntaxNodeRef,
10};
11use ra_db::SyntaxDatabase; 10use ra_db::SyntaxDatabase;
12use rustc_hash::{FxHashMap};
13use hir::source_binder;
14 11
15use crate::{ 12use crate::{
16 db, 13 db,
17 Cancelable, FilePosition 14 Cancelable, FilePosition,
15 completion::{
16 completion_item::{Completions, CompletionKind},
17 completion_context::CompletionContext,
18 },
18}; 19};
19 20
20#[derive(Debug)] 21pub use crate::completion::completion_item::{CompletionItem, InsertText, CompletionItemKind};
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 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.
30pub(crate) fn completions( 31pub(crate) fn completions(
31 db: &db::RootDatabase, 32 db: &db::RootDatabase,
32 position: FilePosition, 33 position: FilePosition,
33) -> Cancelable<Option<Vec<CompletionItem>>> { 34) -> Cancelable<Option<Completions>> {
34 let original_file = db.source_file(position.file_id); 35 let original_file = db.source_file(position.file_id);
35 // Insert a fake ident to get a valid parse tree 36 let ctx = ctry!(CompletionContext::new(db, &original_file, position)?);
36 let file = {
37 let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string());
38 original_file.reparse(&edit)
39 };
40
41 let module = ctry!(source_binder::module_from_position(db, position)?);
42 37
43 let mut res = Vec::new(); 38 let mut acc = Completions::default();
44 let mut has_completions = false;
45 // 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) {
47 has_completions = true;
48 reference_completion::completions(&mut res, db, &module, &file, name_ref)?;
49 // special case, `trait T { fn foo(i_am_a_name_ref) {} }`
50 if is_node::<ast::Param>(name_ref.syntax()) {
51 param_completions(name_ref.syntax(), &mut res);
52 }
53 }
54 39
55 // Otherwise, if this is a declaration, use heuristics to suggest a name. 40 complete_fn_param::complete_fn_param(&mut acc, &ctx);
56 if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) { 41 complete_keyword::complete_expr_keyword(&mut acc, &ctx);
57 if is_node::<ast::Param>(name.syntax()) { 42 complete_snippet::complete_expr_snippet(&mut acc, &ctx);
58 has_completions = true; 43 complete_snippet::complete_item_snippet(&mut acc, &ctx);
59 param_completions(name.syntax(), &mut res); 44 complete_path::complete_path(&mut acc, &ctx)?;
60 } 45 complete_scope::complete_scope(&mut acc, &ctx)?;
61 }
62 let res = if has_completions { Some(res) } else { None };
63 Ok(res)
64}
65
66fn param_completions(ctx: SyntaxNodeRef, acc: &mut Vec<CompletionItem>) {
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 acc.push(CompletionItem {
86 label,
87 lookup: Some(lookup),
88 snippet: None,
89 })
90 });
91
92 fn process<'a, N: ast::FnDefOwner<'a>>(
93 node: N,
94 params: &mut FxHashMap<String, (u32, ast::Param<'a>)>,
95 ) {
96 node.functions()
97 .filter_map(|it| it.param_list())
98 .flat_map(|it| it.params())
99 .for_each(|param| {
100 let text = param.syntax().text().to_string();
101 params.entry(text).or_insert((0, param)).0 += 1;
102 })
103 }
104}
105 46
106fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { 47 Ok(Some(acc))
107 match node.ancestors().filter_map(N::cast).next() {
108 None => false,
109 Some(n) => n.syntax().range() == node.range(),
110 }
111} 48}
112 49
113#[cfg(test)] 50#[cfg(test)]
114mod tests { 51fn check_completion(code: &str, expected_completions: &str, kind: CompletionKind) {
115 use test_utils::assert_eq_dbg; 52 use crate::mock_analysis::{single_file_with_position, analysis_and_position};
116 53 let (analysis, position) = if code.contains("//-") {
117 use crate::mock_analysis::single_file_with_position; 54 analysis_and_position(code)
118 55 } else {
119 use super::*; 56 single_file_with_position(code)
120 57 };
121 fn check_scope_completion(code: &str, expected_completions: &str) { 58 let completions = completions(&analysis.imp.db, position).unwrap().unwrap();
122 let (analysis, position) = single_file_with_position(code); 59 completions.assert_match(expected_completions, kind);
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 }
394
395 #[test]
396 fn test_param_completion_last_param() {
397 check_scope_completion(r"
398 fn foo(file_id: FileId) {}
399 fn bar(file_id: FileId) {}
400 fn baz(file<|>) {}
401 ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#);
402 }
403
404 #[test]
405 fn test_param_completion_nth_param() {
406 check_scope_completion(r"
407 fn foo(file_id: FileId) {}
408 fn bar(file_id: FileId) {}
409 fn baz(file<|>, x: i32) {}
410 ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#);
411 }
412
413 #[test]
414 fn test_param_completion_trait_param() {
415 check_scope_completion(r"
416 pub(crate) trait SourceRoot {
417 pub fn contains(&self, file_id: FileId) -> bool;
418 pub fn module_map(&self) -> &ModuleMap;
419 pub fn lines(&self, file_id: FileId) -> &LineIndex;
420 pub fn syntax(&self, file<|>)
421 }
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 ",
440 r##"[CompletionItem { label: "Test function", lookup: Some("tfn"), snippet: Some("#[test]\nfn ${1:feature}() {\n$0\n}") },
441 CompletionItem { label: "pub(crate)", lookup: None, snippet: Some("pub(crate) $0") }]"##,
442 );
443 }
444} 60}
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..bb5fdfda0
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_fn_param.rs
@@ -0,0 +1,102 @@
1use ra_syntax::{
2 algo::visit::{visitor_ctx, VisitorCtx},
3 ast,
4 AstNode,
5};
6use rustc_hash::FxHashMap;
7
8use crate::completion::{CompletionContext, Completions, CompletionKind, CompletionItem};
9
10/// Complete repeated parametes, both name and type. For example, if all
11/// functions in a file have a `spam: &mut Spam` parameter, a completion with
12/// `spam: &mut Spam` insert text/label and `spam` lookup string will be
13/// suggested.
14pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) {
15 if !ctx.is_param {
16 return;
17 }
18
19 let mut params = FxHashMap::default();
20 for node in ctx.leaf.ancestors() {
21 let _ = visitor_ctx(&mut params)
22 .visit::<ast::SourceFile, _>(process)
23 .visit::<ast::ItemList, _>(process)
24 .accept(node);
25 }
26 params
27 .into_iter()
28 .filter_map(|(label, (count, param))| {
29 let lookup = param.pat()?.syntax().text().to_string();
30 if count < 2 {
31 None
32 } else {
33 Some((label, lookup))
34 }
35 })
36 .for_each(|(label, lookup)| {
37 CompletionItem::new(CompletionKind::Magic, label)
38 .lookup_by(lookup)
39 .add_to(acc)
40 });
41
42 fn process<'a, N: ast::FnDefOwner<'a>>(
43 node: N,
44 params: &mut FxHashMap<String, (u32, ast::Param<'a>)>,
45 ) {
46 node.functions()
47 .filter_map(|it| it.param_list())
48 .flat_map(|it| it.params())
49 .for_each(|param| {
50 let text = param.syntax().text().to_string();
51 params.entry(text).or_insert((0, param)).0 += 1;
52 })
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use crate::completion::*;
59
60 fn check_magic_completion(code: &str, expected_completions: &str) {
61 check_completion(code, expected_completions, CompletionKind::Magic);
62 }
63
64 #[test]
65 fn test_param_completion_last_param() {
66 check_magic_completion(
67 r"
68 fn foo(file_id: FileId) {}
69 fn bar(file_id: FileId) {}
70 fn baz(file<|>) {}
71 ",
72 r#"file_id "file_id: FileId""#,
73 );
74 }
75
76 #[test]
77 fn test_param_completion_nth_param() {
78 check_magic_completion(
79 r"
80 fn foo(file_id: FileId) {}
81 fn bar(file_id: FileId) {}
82 fn baz(file<|>, x: i32) {}
83 ",
84 r#"file_id "file_id: FileId""#,
85 );
86 }
87
88 #[test]
89 fn test_param_completion_trait_param() {
90 check_magic_completion(
91 r"
92 pub(crate) trait SourceRoot {
93 pub fn contains(&self, file_id: FileId) -> bool;
94 pub fn module_map(&self) -> &ModuleMap;
95 pub fn lines(&self, file_id: FileId) -> &LineIndex;
96 pub fn syntax(&self, file<|>)
97 }
98 ",
99 r#"file_id "file_id: FileId""#,
100 );
101 }
102}
diff --git a/crates/ra_analysis/src/completion/complete_keyword.rs b/crates/ra_analysis/src/completion/complete_keyword.rs
new file mode 100644
index 000000000..5427fcb11
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_keyword.rs
@@ -0,0 +1,204 @@
1use ra_syntax::{
2 algo::visit::{visitor, Visitor},
3 AstNode,
4 ast::{self, LoopBodyOwner},
5 SyntaxKind::*, SyntaxNodeRef,
6};
7
8use crate::completion::{CompletionContext, CompletionItem, Completions, CompletionKind, CompletionItemKind};
9
10fn keyword(kw: &str, snippet: &str) -> CompletionItem {
11 CompletionItem::new(CompletionKind::Keyword, kw)
12 .kind(CompletionItemKind::Keyword)
13 .snippet(snippet)
14 .build()
15}
16
17pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
18 if !ctx.is_trivial_path {
19 return;
20 }
21 let fn_def = match ctx.enclosing_fn {
22 Some(it) => it,
23 None => return,
24 };
25 acc.add(keyword("if", "if $0 {}"));
26 acc.add(keyword("match", "match $0 {}"));
27 acc.add(keyword("while", "while $0 {}"));
28 acc.add(keyword("loop", "loop {$0}"));
29
30 if ctx.after_if {
31 acc.add(keyword("else", "else {$0}"));
32 acc.add(keyword("else if", "else if $0 {}"));
33 }
34 if is_in_loop_body(ctx.leaf) {
35 acc.add(keyword("continue", "continue"));
36 acc.add(keyword("break", "break"));
37 }
38 acc.add_all(complete_return(fn_def, ctx.is_stmt));
39}
40
41fn is_in_loop_body(leaf: SyntaxNodeRef) -> bool {
42 for node in leaf.ancestors() {
43 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
44 break;
45 }
46 let loop_body = visitor()
47 .visit::<ast::ForExpr, _>(LoopBodyOwner::loop_body)
48 .visit::<ast::WhileExpr, _>(LoopBodyOwner::loop_body)
49 .visit::<ast::LoopExpr, _>(LoopBodyOwner::loop_body)
50 .accept(node);
51 if let Some(Some(body)) = loop_body {
52 if leaf.range().is_subrange(&body.syntax().range()) {
53 return true;
54 }
55 }
56 }
57 false
58}
59
60fn complete_return(fn_def: ast::FnDef, is_stmt: bool) -> Option<CompletionItem> {
61 let snip = match (is_stmt, fn_def.ret_type().is_some()) {
62 (true, true) => "return $0;",
63 (true, false) => "return;",
64 (false, true) => "return $0",
65 (false, false) => "return",
66 };
67 Some(keyword("return", snip))
68}
69
70#[cfg(test)]
71mod tests {
72 use crate::completion::{CompletionKind, check_completion};
73 fn check_keyword_completion(code: &str, expected_completions: &str) {
74 check_completion(code, expected_completions, CompletionKind::Keyword);
75 }
76
77 #[test]
78 fn test_completion_kewords() {
79 check_keyword_completion(
80 r"
81 fn quux() {
82 <|>
83 }
84 ",
85 r#"
86 if "if $0 {}"
87 match "match $0 {}"
88 while "while $0 {}"
89 loop "loop {$0}"
90 return "return"
91 "#,
92 );
93 }
94
95 #[test]
96 fn test_completion_else() {
97 check_keyword_completion(
98 r"
99 fn quux() {
100 if true {
101 ()
102 } <|>
103 }
104 ",
105 r#"
106 if "if $0 {}"
107 match "match $0 {}"
108 while "while $0 {}"
109 loop "loop {$0}"
110 else "else {$0}"
111 else if "else if $0 {}"
112 return "return"
113 "#,
114 );
115 }
116
117 #[test]
118 fn test_completion_return_value() {
119 check_keyword_completion(
120 r"
121 fn quux() -> i32 {
122 <|>
123 92
124 }
125 ",
126 r#"
127 if "if $0 {}"
128 match "match $0 {}"
129 while "while $0 {}"
130 loop "loop {$0}"
131 return "return $0;"
132 "#,
133 );
134 check_keyword_completion(
135 r"
136 fn quux() {
137 <|>
138 92
139 }
140 ",
141 r#"
142 if "if $0 {}"
143 match "match $0 {}"
144 while "while $0 {}"
145 loop "loop {$0}"
146 return "return;"
147 "#,
148 );
149 }
150
151 #[test]
152 fn test_completion_return_no_stmt() {
153 check_keyword_completion(
154 r"
155 fn quux() -> i32 {
156 match () {
157 () => <|>
158 }
159 }
160 ",
161 r#"
162 if "if $0 {}"
163 match "match $0 {}"
164 while "while $0 {}"
165 loop "loop {$0}"
166 return "return $0"
167 "#,
168 );
169 }
170
171 #[test]
172 fn test_continue_break_completion() {
173 check_keyword_completion(
174 r"
175 fn quux() -> i32 {
176 loop { <|> }
177 }
178 ",
179 r#"
180 if "if $0 {}"
181 match "match $0 {}"
182 while "while $0 {}"
183 loop "loop {$0}"
184 continue "continue"
185 break "break"
186 return "return $0"
187 "#,
188 );
189 check_keyword_completion(
190 r"
191 fn quux() -> i32 {
192 loop { || { <|> } }
193 }
194 ",
195 r#"
196 if "if $0 {}"
197 match "match $0 {}"
198 while "while $0 {}"
199 loop "loop {$0}"
200 return "return $0"
201 "#,
202 );
203 }
204}
diff --git a/crates/ra_analysis/src/completion/complete_path.rs b/crates/ra_analysis/src/completion/complete_path.rs
new file mode 100644
index 000000000..ad4d68a33
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_path.rs
@@ -0,0 +1,95 @@
1use crate::{
2 Cancelable,
3 completion::{CompletionItem, Completions, CompletionKind, CompletionContext},
4};
5
6pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
7 let (path, module) = match (&ctx.path_prefix, &ctx.module) {
8 (Some(path), Some(module)) => (path.clone(), module),
9 _ => return Ok(()),
10 };
11 let def_id = match module.resolve_path(ctx.db, path)? {
12 Some(it) => it,
13 None => return Ok(()),
14 };
15 let target_module = match def_id.resolve(ctx.db)? {
16 hir::Def::Module(it) => it,
17 _ => return Ok(()),
18 };
19 let module_scope = target_module.scope(ctx.db)?;
20 module_scope.entries().for_each(|(name, res)| {
21 CompletionItem::new(CompletionKind::Reference, name.to_string())
22 .from_resolution(ctx.db, res)
23 .add_to(acc)
24 });
25 Ok(())
26}
27
28#[cfg(test)]
29mod tests {
30 use crate::completion::{CompletionKind, check_completion};
31
32 fn check_reference_completion(code: &str, expected_completions: &str) {
33 check_completion(code, expected_completions, CompletionKind::Reference);
34 }
35
36 #[test]
37 fn completes_use_item_starting_with_self() {
38 check_reference_completion(
39 r"
40 use self::m::<|>;
41
42 mod m {
43 struct Bar;
44 }
45 ",
46 "Bar",
47 );
48 }
49
50 #[test]
51 fn completes_use_item_starting_with_crate() {
52 check_reference_completion(
53 "
54 //- /lib.rs
55 mod foo;
56 struct Spam;
57 //- /foo.rs
58 use crate::Sp<|>
59 ",
60 "Spam;foo",
61 );
62 }
63
64 #[test]
65 fn completes_nested_use_tree() {
66 check_reference_completion(
67 "
68 //- /lib.rs
69 mod foo;
70 struct Spam;
71 //- /foo.rs
72 use crate::{Sp<|>};
73 ",
74 "Spam;foo",
75 );
76 }
77
78 #[test]
79 fn completes_deeply_nested_use_tree() {
80 check_reference_completion(
81 "
82 //- /lib.rs
83 mod foo;
84 pub mod bar {
85 pub mod baz {
86 pub struct Spam;
87 }
88 }
89 //- /foo.rs
90 use crate::{bar::{baz::Sp<|>}};
91 ",
92 "Spam",
93 );
94 }
95}
diff --git a/crates/ra_analysis/src/completion/complete_scope.rs b/crates/ra_analysis/src/completion/complete_scope.rs
new file mode 100644
index 000000000..a57670e3b
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_scope.rs
@@ -0,0 +1,173 @@
1use rustc_hash::FxHashSet;
2use ra_syntax::TextUnit;
3
4use crate::{
5 Cancelable,
6 completion::{CompletionItem, CompletionItemKind, Completions, CompletionKind, CompletionContext},
7};
8
9pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
10 if !ctx.is_trivial_path {
11 return Ok(());
12 }
13 let module = match &ctx.module {
14 Some(it) => it,
15 None => return Ok(()),
16 };
17 if let Some(fn_def) = ctx.enclosing_fn {
18 let function = hir::source_binder::function_from_module(ctx.db, module, fn_def);
19 let scopes = function.scopes(ctx.db);
20 complete_fn(acc, &scopes, ctx.offset);
21 }
22
23 let module_scope = module.scope(ctx.db)?;
24 module_scope
25 .entries()
26 .filter(|(_name, res)| {
27 // Don't expose this item
28 match res.import {
29 None => true,
30 Some(import) => {
31 let range = import.range(ctx.db, module.source().file_id());
32 !range.is_subrange(&ctx.leaf.range())
33 }
34 }
35 })
36 .for_each(|(name, res)| {
37 CompletionItem::new(CompletionKind::Reference, name.to_string())
38 .from_resolution(ctx.db, res)
39 .add_to(acc)
40 });
41 Ok(())
42}
43
44fn complete_fn(acc: &mut Completions, scopes: &hir::FnScopes, offset: TextUnit) {
45 let mut shadowed = FxHashSet::default();
46 scopes
47 .scope_chain_for_offset(offset)
48 .flat_map(|scope| scopes.entries(scope).iter())
49 .filter(|entry| shadowed.insert(entry.name()))
50 .for_each(|entry| {
51 CompletionItem::new(CompletionKind::Reference, entry.name().to_string())
52 .kind(CompletionItemKind::Binding)
53 .add_to(acc)
54 });
55 if scopes.self_param.is_some() {
56 CompletionItem::new(CompletionKind::Reference, "self").add_to(acc);
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use crate::completion::{CompletionKind, check_completion};
63
64 fn check_reference_completion(code: &str, expected_completions: &str) {
65 check_completion(code, expected_completions, CompletionKind::Reference);
66 }
67
68 #[test]
69 fn completes_bindings_from_let() {
70 check_reference_completion(
71 r"
72 fn quux(x: i32) {
73 let y = 92;
74 1 + <|>;
75 let z = ();
76 }
77 ",
78 "y;x;quux",
79 );
80 }
81
82 #[test]
83 fn completes_bindings_from_if_let() {
84 check_reference_completion(
85 r"
86 fn quux() {
87 if let Some(x) = foo() {
88 let y = 92;
89 };
90 if let Some(a) = bar() {
91 let b = 62;
92 1 + <|>
93 }
94 }
95 ",
96 "b;a;quux",
97 );
98 }
99
100 #[test]
101 fn completes_bindings_from_for() {
102 check_reference_completion(
103 r"
104 fn quux() {
105 for x in &[1, 2, 3] {
106 <|>
107 }
108 }
109 ",
110 "x;quux",
111 );
112 }
113
114 #[test]
115 fn completes_module_items() {
116 check_reference_completion(
117 r"
118 struct Foo;
119 enum Baz {}
120 fn quux() {
121 <|>
122 }
123 ",
124 "quux;Foo;Baz",
125 );
126 }
127
128 #[test]
129 fn completes_module_items_in_nested_modules() {
130 check_reference_completion(
131 r"
132 struct Foo;
133 mod m {
134 struct Bar;
135 fn quux() { <|> }
136 }
137 ",
138 "quux;Bar",
139 );
140 }
141
142 #[test]
143 fn completes_return_type() {
144 check_reference_completion(
145 r"
146 struct Foo;
147 fn x() -> <|>
148 ",
149 "Foo;x",
150 )
151 }
152
153 #[test]
154 fn dont_show_to_completions_for_shadowing() {
155 check_reference_completion(
156 r"
157 fn foo() -> {
158 let bar = 92;
159 {
160 let bar = 62;
161 <|>
162 }
163 }
164 ",
165 "bar;foo",
166 )
167 }
168
169 #[test]
170 fn completes_self_in_methods() {
171 check_reference_completion(r"impl S { fn foo(&self) { <|> } }", "self")
172 }
173}
diff --git a/crates/ra_analysis/src/completion/complete_snippet.rs b/crates/ra_analysis/src/completion/complete_snippet.rs
new file mode 100644
index 000000000..fb9da0a4f
--- /dev/null
+++ b/crates/ra_analysis/src/completion/complete_snippet.rs
@@ -0,0 +1,73 @@
1use crate::completion::{CompletionItem, Completions, CompletionKind, CompletionItemKind, CompletionContext, completion_item::Builder};
2
3fn snippet(label: &str, snippet: &str) -> Builder {
4 CompletionItem::new(CompletionKind::Snippet, label)
5 .snippet(snippet)
6 .kind(CompletionItemKind::Snippet)
7}
8
9pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) {
10 if !(ctx.is_trivial_path && ctx.enclosing_fn.is_some()) {
11 return;
12 }
13 snippet("pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
14 snippet("ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
15}
16
17pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
18 if !ctx.is_new_item {
19 return;
20 }
21 snippet(
22 "Test function",
23 "\
24#[test]
25fn ${1:feature}() {
26 $0
27}",
28 )
29 .lookup_by("tfn")
30 .add_to(acc);
31
32 snippet("pub(crate)", "pub(crate) $0").add_to(acc);
33}
34
35#[cfg(test)]
36mod tests {
37 use crate::completion::{CompletionKind, check_completion};
38 fn check_snippet_completion(code: &str, expected_completions: &str) {
39 check_completion(code, expected_completions, CompletionKind::Snippet);
40 }
41
42 #[test]
43 fn completes_snippets_in_expressions() {
44 check_snippet_completion(
45 r"fn foo(x: i32) { <|> }",
46 r##"
47 pd "eprintln!(\"$0 = {:?}\", $0);"
48 ppd "eprintln!(\"$0 = {:#?}\", $0);"
49 "##,
50 );
51 }
52
53 #[test]
54 fn completes_snippets_in_items() {
55 // check_snippet_completion(r"
56 // <|>
57 // ",
58 // r##"[CompletionItem { label: "Test function", lookup: None, snippet: Some("#[test]\nfn test_${1:feature}() {\n$0\n}"##,
59 // );
60 check_snippet_completion(
61 r"
62 #[cfg(test)]
63 mod tests {
64 <|>
65 }
66 ",
67 r##"
68 tfn "Test function" "#[test]\nfn ${1:feature}() {\n $0\n}"
69 pub(crate) "pub(crate) $0"
70 "##,
71 );
72 }
73}
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}
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..911f08468
--- /dev/null
+++ b/crates/ra_analysis/src/completion/completion_item.rs
@@ -0,0 +1,214 @@
1use crate::db;
2
3/// `CompletionItem` describes a single completion variant in the editor pop-up.
4/// It is basically a POD with various properties. To construct a
5/// `CompletionItem`, use `new` method and the `Builder` struct.
6#[derive(Debug)]
7pub struct CompletionItem {
8 /// Used only internally in tests, to check only specific kind of
9 /// completion.
10 completion_kind: CompletionKind,
11 label: String,
12 lookup: Option<String>,
13 snippet: Option<String>,
14 kind: Option<CompletionItemKind>,
15}
16
17pub enum InsertText {
18 PlainText { text: String },
19 Snippet { text: String },
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum CompletionItemKind {
24 Snippet,
25 Keyword,
26 Module,
27 Function,
28 Binding,
29}
30
31#[derive(Debug, PartialEq, Eq)]
32pub(crate) enum CompletionKind {
33 /// Parser-based keyword completion.
34 Keyword,
35 /// Your usual "complete all valid identifiers".
36 Reference,
37 /// "Secret sauce" completions.
38 Magic,
39 Snippet,
40}
41
42impl CompletionItem {
43 pub(crate) fn new(completion_kind: CompletionKind, label: impl Into<String>) -> Builder {
44 let label = label.into();
45 Builder {
46 completion_kind,
47 label,
48 lookup: None,
49 snippet: None,
50 kind: None,
51 }
52 }
53 /// What user sees in pop-up in the UI.
54 pub fn label(&self) -> &str {
55 &self.label
56 }
57 /// What string is used for filtering.
58 pub fn lookup(&self) -> &str {
59 self.lookup
60 .as_ref()
61 .map(|it| it.as_str())
62 .unwrap_or(self.label())
63 }
64 /// What is inserted.
65 pub fn insert_text(&self) -> InsertText {
66 match &self.snippet {
67 None => InsertText::PlainText {
68 text: self.label.clone(),
69 },
70 Some(it) => InsertText::Snippet { text: it.clone() },
71 }
72 }
73
74 pub fn kind(&self) -> Option<CompletionItemKind> {
75 self.kind
76 }
77}
78
79/// A helper to make `CompletionItem`s.
80#[must_use]
81pub(crate) struct Builder {
82 completion_kind: CompletionKind,
83 label: String,
84 lookup: Option<String>,
85 snippet: Option<String>,
86 kind: Option<CompletionItemKind>,
87}
88
89impl Builder {
90 pub(crate) fn add_to(self, acc: &mut Completions) {
91 acc.add(self.build())
92 }
93
94 pub(crate) fn build(self) -> CompletionItem {
95 CompletionItem {
96 label: self.label,
97 lookup: self.lookup,
98 snippet: self.snippet,
99 kind: self.kind,
100 completion_kind: self.completion_kind,
101 }
102 }
103 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
104 self.lookup = Some(lookup.into());
105 self
106 }
107 pub(crate) fn snippet(mut self, snippet: impl Into<String>) -> Builder {
108 self.snippet = Some(snippet.into());
109 self
110 }
111 pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder {
112 self.kind = Some(kind);
113 self
114 }
115 pub(crate) fn from_resolution(
116 mut self,
117 db: &db::RootDatabase,
118 resolution: &hir::Resolution,
119 ) -> Builder {
120 if let Some(def_id) = resolution.def_id {
121 if let Ok(def) = def_id.resolve(db) {
122 let kind = match def {
123 hir::Def::Module(..) => CompletionItemKind::Module,
124 hir::Def::Function(..) => CompletionItemKind::Function,
125 _ => return self,
126 };
127 self.kind = Some(kind);
128 }
129 }
130 self
131 }
132}
133
134impl Into<CompletionItem> for Builder {
135 fn into(self) -> CompletionItem {
136 self.build()
137 }
138}
139
140/// Represents an in-progress set of completions being built.
141#[derive(Debug, Default)]
142pub(crate) struct Completions {
143 buf: Vec<CompletionItem>,
144}
145
146impl Completions {
147 pub(crate) fn add(&mut self, item: impl Into<CompletionItem>) {
148 self.buf.push(item.into())
149 }
150 pub(crate) fn add_all<I>(&mut self, items: I)
151 where
152 I: IntoIterator,
153 I::Item: Into<CompletionItem>,
154 {
155 items.into_iter().for_each(|item| self.add(item.into()))
156 }
157
158 #[cfg(test)]
159 pub(crate) fn assert_match(&self, expected: &str, kind: CompletionKind) {
160 let expected = normalize(expected);
161 let actual = self.debug_render(kind);
162 test_utils::assert_eq_text!(expected.as_str(), actual.as_str(),);
163
164 /// Normalize the textual representation of `Completions`:
165 /// replace `;` with newlines, normalize whitespace
166 fn normalize(expected: &str) -> String {
167 use ra_syntax::{tokenize, TextUnit, TextRange, SyntaxKind::SEMI};
168 let mut res = String::new();
169 for line in expected.trim().lines() {
170 let line = line.trim();
171 let mut start_offset: TextUnit = 0.into();
172 // Yep, we use rust tokenize in completion tests :-)
173 for token in tokenize(line) {
174 let range = TextRange::offset_len(start_offset, token.len);
175 start_offset += token.len;
176 if token.kind == SEMI {
177 res.push('\n');
178 } else {
179 res.push_str(&line[range]);
180 }
181 }
182
183 res.push('\n');
184 }
185 res
186 }
187 }
188
189 #[cfg(test)]
190 fn debug_render(&self, kind: CompletionKind) -> String {
191 let mut res = String::new();
192 for c in self.buf.iter() {
193 if c.completion_kind == kind {
194 if let Some(lookup) = &c.lookup {
195 res.push_str(lookup);
196 res.push_str(&format!(" {:?}", c.label));
197 } else {
198 res.push_str(&c.label);
199 }
200 if let Some(snippet) = &c.snippet {
201 res.push_str(&format!(" {:?}", snippet));
202 }
203 res.push('\n');
204 }
205 }
206 res
207 }
208}
209
210impl Into<Vec<CompletionItem>> for Completions {
211 fn into(self) -> Vec<CompletionItem> {
212 self.buf
213 }
214}
diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs
deleted file mode 100644
index f483ed045..000000000
--- a/crates/ra_analysis/src/completion/reference_completion.rs
+++ /dev/null
@@ -1,294 +0,0 @@
1use rustc_hash::{FxHashSet};
2use ra_editor::find_node_at_offset;
3use ra_syntax::{
4 algo::visit::{visitor, Visitor},
5 SourceFileNode, AstNode,
6 ast::{self, LoopBodyOwner},
7 SyntaxKind::*,
8};
9use hir::{
10 self,
11 FnScopes,
12 Def,
13 Path,
14};
15
16use crate::{
17 db::RootDatabase,
18 completion::CompletionItem,
19 Cancelable
20};
21
22pub(super) fn completions(
23 acc: &mut Vec<CompletionItem>,
24 db: &RootDatabase,
25 module: &hir::Module,
26 file: &SourceFileNode,
27 name_ref: ast::NameRef,
28) -> Cancelable<()> {
29 let kind = match classify_name_ref(name_ref) {
30 Some(it) => it,
31 None => return Ok(()),
32 };
33
34 match kind {
35 NameRefKind::LocalRef { enclosing_fn } => {
36 if let Some(fn_def) = enclosing_fn {
37 let scopes = FnScopes::new(fn_def);
38 complete_fn(name_ref, &scopes, acc);
39 complete_expr_keywords(&file, fn_def, name_ref, acc);
40 complete_expr_snippets(acc);
41 }
42
43 let module_scope = module.scope(db)?;
44 acc.extend(
45 module_scope
46 .entries()
47 .filter(|(_name, res)| {
48 // Don't expose this item
49 match res.import {
50 None => true,
51 Some(import) => {
52 let range = import.range(db, module.source().file_id());
53 !range.is_subrange(&name_ref.syntax().range())
54 }
55 }
56 })
57 .map(|(name, _res)| CompletionItem {
58 label: name.to_string(),
59 lookup: None,
60 snippet: None,
61 }),
62 );
63 }
64 NameRefKind::Path(path) => complete_path(acc, db, module, path)?,
65 NameRefKind::BareIdentInMod => {
66 let name_range = name_ref.syntax().range();
67 let top_node = name_ref
68 .syntax()
69 .ancestors()
70 .take_while(|it| it.range() == name_range)
71 .last()
72 .unwrap();
73 match top_node.parent().map(|it| it.kind()) {
74 Some(SOURCE_FILE) | Some(ITEM_LIST) => complete_mod_item_snippets(acc),
75 _ => (),
76 }
77 }
78 }
79 Ok(())
80}
81
82enum NameRefKind<'a> {
83 /// NameRef is a part of single-segment path, for example, a refernece to a
84 /// local variable.
85 LocalRef {
86 enclosing_fn: Option<ast::FnDef<'a>>,
87 },
88 /// NameRef is the last segment in some path
89 Path(Path),
90 /// NameRef is bare identifier at the module's root.
91 /// Used for keyword completion
92 BareIdentInMod,
93}
94
95fn classify_name_ref(name_ref: ast::NameRef) -> Option<NameRefKind> {
96 let name_range = name_ref.syntax().range();
97 let top_node = name_ref
98 .syntax()
99 .ancestors()
100 .take_while(|it| it.range() == name_range)
101 .last()
102 .unwrap();
103 match top_node.parent().map(|it| it.kind()) {
104 Some(SOURCE_FILE) | Some(ITEM_LIST) => return Some(NameRefKind::BareIdentInMod),
105 _ => (),
106 }
107
108 let parent = name_ref.syntax().parent()?;
109 if let Some(segment) = ast::PathSegment::cast(parent) {
110 let path = segment.parent_path();
111 if let Some(path) = Path::from_ast(path) {
112 if !path.is_ident() {
113 return Some(NameRefKind::Path(path));
114 }
115 }
116 if path.qualifier().is_none() {
117 let enclosing_fn = name_ref
118 .syntax()
119 .ancestors()
120 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
121 .find_map(ast::FnDef::cast);
122 return Some(NameRefKind::LocalRef { enclosing_fn });
123 }
124 }
125 None
126}
127
128fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) {
129 let mut shadowed = FxHashSet::default();
130 acc.extend(
131 scopes
132 .scope_chain(name_ref.syntax())
133 .flat_map(|scope| scopes.entries(scope).iter())
134 .filter(|entry| shadowed.insert(entry.name()))
135 .map(|entry| CompletionItem {
136 label: entry.name().to_string(),
137 lookup: None,
138 snippet: None,
139 }),
140 );
141 if scopes.self_param.is_some() {
142 acc.push(CompletionItem {
143 label: "self".to_string(),
144 lookup: None,
145 snippet: None,
146 })
147 }
148}
149
150fn complete_path(
151 acc: &mut Vec<CompletionItem>,
152 db: &RootDatabase,
153 module: &hir::Module,
154 mut path: Path,
155) -> Cancelable<()> {
156 if path.segments.is_empty() {
157 return Ok(());
158 }
159 path.segments.pop();
160 let def_id = match module.resolve_path(db, path)? {
161 None => return Ok(()),
162 Some(it) => it,
163 };
164 let target_module = match def_id.resolve(db)? {
165 Def::Module(it) => it,
166 _ => return Ok(()),
167 };
168 let module_scope = target_module.scope(db)?;
169 let completions = module_scope.entries().map(|(name, _res)| CompletionItem {
170 label: name.to_string(),
171 lookup: None,
172 snippet: None,
173 });
174 acc.extend(completions);
175 Ok(())
176}
177
178fn complete_mod_item_snippets(acc: &mut Vec<CompletionItem>) {
179 acc.push(CompletionItem {
180 label: "Test function".to_string(),
181 lookup: Some("tfn".to_string()),
182 snippet: Some(
183 "#[test]\n\
184 fn ${1:feature}() {\n\
185 $0\n\
186 }"
187 .to_string(),
188 ),
189 });
190 acc.push(CompletionItem {
191 label: "pub(crate)".to_string(),
192 lookup: None,
193 snippet: Some("pub(crate) $0".to_string()),
194 })
195}
196
197fn complete_expr_keywords(
198 file: &SourceFileNode,
199 fn_def: ast::FnDef,
200 name_ref: ast::NameRef,
201 acc: &mut Vec<CompletionItem>,
202) {
203 acc.push(keyword("if", "if $0 {}"));
204 acc.push(keyword("match", "match $0 {}"));
205 acc.push(keyword("while", "while $0 {}"));
206 acc.push(keyword("loop", "loop {$0}"));
207
208 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) {
210 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
211 acc.push(keyword("else", "else {$0}"));
212 acc.push(keyword("else if", "else if $0 {}"));
213 }
214 }
215 }
216 if is_in_loop_body(name_ref) {
217 acc.push(keyword("continue", "continue"));
218 acc.push(keyword("break", "break"));
219 }
220 acc.extend(complete_return(fn_def, name_ref));
221}
222
223fn is_in_loop_body(name_ref: ast::NameRef) -> bool {
224 for node in name_ref.syntax().ancestors() {
225 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
226 break;
227 }
228 let loop_body = visitor()
229 .visit::<ast::ForExpr, _>(LoopBodyOwner::loop_body)
230 .visit::<ast::WhileExpr, _>(LoopBodyOwner::loop_body)
231 .visit::<ast::LoopExpr, _>(LoopBodyOwner::loop_body)
232 .accept(node);
233 if let Some(Some(body)) = loop_body {
234 if name_ref
235 .syntax()
236 .range()
237 .is_subrange(&body.syntax().range())
238 {
239 return true;
240 }
241 }
242 }
243 false
244}
245
246fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option<CompletionItem> {
247 // let is_last_in_block = name_ref.syntax().ancestors().filter_map(ast::Expr::cast)
248 // .next()
249 // .and_then(|it| it.syntax().parent())
250 // .and_then(ast::Block::cast)
251 // .is_some();
252
253 // if is_last_in_block {
254 // return None;
255 // }
256
257 let is_stmt = match name_ref
258 .syntax()
259 .ancestors()
260 .filter_map(ast::ExprStmt::cast)
261 .next()
262 {
263 None => false,
264 Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(),
265 };
266 let snip = match (is_stmt, fn_def.ret_type().is_some()) {
267 (true, true) => "return $0;",
268 (true, false) => "return;",
269 (false, true) => "return $0",
270 (false, false) => "return",
271 };
272 Some(keyword("return", snip))
273}
274
275fn keyword(kw: &str, snip: &str) -> CompletionItem {
276 CompletionItem {
277 label: kw.to_string(),
278 lookup: None,
279 snippet: Some(snip.to_string()),
280 }
281}
282
283fn complete_expr_snippets(acc: &mut Vec<CompletionItem>) {
284 acc.push(CompletionItem {
285 label: "pd".to_string(),
286 lookup: None,
287 snippet: Some("eprintln!(\"$0 = {:?}\", $0);".to_string()),
288 });
289 acc.push(CompletionItem {
290 label: "ppd".to_string(),
291 lookup: None,
292 snippet: Some("eprintln!(\"$0 = {:#?}\", $0);".to_string()),
293 });
294}
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs
index 5701e1ae2..b01382808 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,
@@ -234,7 +235,7 @@ impl AnalysisImpl {
234 position.file_id, 235 position.file_id,
235 name_ref.syntax(), 236 name_ref.syntax(),
236 )? { 237 )? {
237 let scope = fn_descr.scope(&*self.db); 238 let scope = fn_descr.scopes(&*self.db);
238 // First try to resolve the symbol locally 239 // First try to resolve the symbol locally
239 if let Some(entry) = scope.resolve_local_name(name_ref) { 240 if let Some(entry) = scope.resolve_local_name(name_ref) {
240 rr.add_resolution( 241 rr.add_resolution(
@@ -293,7 +294,7 @@ impl AnalysisImpl {
293 let mut ret = vec![(position.file_id, binding.syntax().range())]; 294 let mut ret = vec![(position.file_id, binding.syntax().range())];
294 ret.extend( 295 ret.extend(
295 descr 296 descr
296 .scope(&*self.db) 297 .scopes(&*self.db)
297 .find_all_refs(binding) 298 .find_all_refs(binding)
298 .into_iter() 299 .into_iter()
299 .map(|ref_desc| (position.file_id, ref_desc.range)), 300 .map(|ref_desc| (position.file_id, ref_desc.range)),
@@ -321,7 +322,7 @@ impl AnalysisImpl {
321 position.file_id, 322 position.file_id,
322 name_ref.syntax(), 323 name_ref.syntax(),
323 )?); 324 )?);
324 let scope = descr.scope(db); 325 let scope = descr.scopes(db);
325 let resolved = ctry!(scope.resolve_local_name(name_ref)); 326 let resolved = ctry!(scope.resolve_local_name(name_ref));
326 let resolved = resolved.ptr().resolve(source_file); 327 let resolved = resolved.ptr().resolve(source_file);
327 let binding = ctry!(find_node_at_offset::<ast::BindPat>( 328 let binding = ctry!(find_node_at_offset::<ast::BindPat>(
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs
index c7e7dc1c0..85df9c089 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, CompletionItemKind, 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,
@@ -72,13 +72,23 @@ struct RemoveFile {
72 72
73impl fmt::Debug for AnalysisChange { 73impl fmt::Debug for AnalysisChange {
74 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 74 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
75 fmt.debug_struct("AnalysisChange") 75 let mut d = fmt.debug_struct("AnalysisChange");
76 .field("new_roots", &self.new_roots) 76 if !self.new_roots.is_empty() {
77 .field("roots_changed", &self.roots_changed) 77 d.field("new_roots", &self.new_roots);
78 .field("files_changed", &self.files_changed.len()) 78 }
79 .field("libraries_added", &self.libraries_added.len()) 79 if !self.roots_changed.is_empty() {
80 .field("crate_graph", &self.crate_graph) 80 d.field("roots_changed", &self.roots_changed);
81 .finish() 81 }
82 if !self.files_changed.is_empty() {
83 d.field("files_changed", &self.files_changed.len());
84 }
85 if !self.libraries_added.is_empty() {
86 d.field("libraries_added", &self.libraries_added.len());
87 }
88 if !self.crate_graph.is_some() {
89 d.field("crate_graph", &self.crate_graph);
90 }
91 d.finish()
82 } 92 }
83} 93}
84 94
@@ -358,13 +368,22 @@ impl Analysis {
358 } 368 }
359} 369}
360 370
361#[derive(Debug)]
362pub struct LibraryData { 371pub struct LibraryData {
363 root_id: SourceRootId, 372 root_id: SourceRootId,
364 root_change: RootChange, 373 root_change: RootChange,
365 symbol_index: SymbolIndex, 374 symbol_index: SymbolIndex,
366} 375}
367 376
377impl fmt::Debug for LibraryData {
378 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
379 f.debug_struct("LibraryData")
380 .field("root_id", &self.root_id)
381 .field("root_change", &self.root_change)
382 .field("n_symbols", &self.symbol_index.len())
383 .finish()
384 }
385}
386
368impl LibraryData { 387impl LibraryData {
369 pub fn prepare( 388 pub fn prepare(
370 root_id: SourceRootId, 389 root_id: SourceRootId,
diff --git a/crates/ra_analysis/src/symbol_index.rs b/crates/ra_analysis/src/symbol_index.rs
index b48a37229..e5bdf0aa1 100644
--- a/crates/ra_analysis/src/symbol_index.rs
+++ b/crates/ra_analysis/src/symbol_index.rs
@@ -56,6 +56,10 @@ impl Hash for SymbolIndex {
56} 56}
57 57
58impl SymbolIndex { 58impl SymbolIndex {
59 pub(crate) fn len(&self) -> usize {
60 self.symbols.len()
61 }
62
59 pub(crate) fn for_files( 63 pub(crate) fn for_files(
60 files: impl ParallelIterator<Item = (FileId, SourceFileNode)>, 64 files: impl ParallelIterator<Item = (FileId, SourceFileNode)>,
61 ) -> SymbolIndex { 65 ) -> SymbolIndex {
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_db/src/input.rs b/crates/ra_db/src/input.rs
index f445e03bc..7c3dd9296 100644
--- a/crates/ra_db/src/input.rs
+++ b/crates/ra_db/src/input.rs
@@ -101,6 +101,9 @@ impl CrateGraph {
101 } 101 }
102 self.arena.get_mut(&from).unwrap().add_dep(name, to) 102 self.arena.get_mut(&from).unwrap().add_dep(name, to)
103 } 103 }
104 pub fn is_empty(&self) -> bool {
105 self.arena.is_empty()
106 }
104 pub fn crate_root(&self, crate_id: CrateId) -> FileId { 107 pub fn crate_root(&self, crate_id: CrateId) -> FileId {
105 self.arena[&crate_id].file_id 108 self.arena[&crate_id].file_id
106 } 109 }
diff --git a/crates/ra_editor/src/typing.rs b/crates/ra_editor/src/typing.rs
index 46a6e2d62..5e412bcfa 100644
--- a/crates/ra_editor/src/typing.rs
+++ b/crates/ra_editor/src/typing.rs
@@ -164,6 +164,16 @@ fn remove_newline(
164 if join_single_expr_block(edit, node).is_some() { 164 if join_single_expr_block(edit, node).is_some() {
165 return; 165 return;
166 } 166 }
167 // ditto for
168 //
169 // ```
170 // use foo::{<|>
171 // bar
172 // };
173 // ```
174 if join_single_use_tree(edit, node).is_some() {
175 return;
176 }
167 177
168 // The node is between two other nodes 178 // The node is between two other nodes
169 let prev = node.prev_sibling().unwrap(); 179 let prev = node.prev_sibling().unwrap();
@@ -228,6 +238,36 @@ fn single_expr(block: ast::Block) -> Option<ast::Expr> {
228 res 238 res
229} 239}
230 240
241fn join_single_use_tree(edit: &mut TextEditBuilder, node: SyntaxNodeRef) -> Option<()> {
242 let use_tree_list = ast::UseTreeList::cast(node.parent()?)?;
243 let tree = single_use_tree(use_tree_list)?;
244 edit.replace(
245 use_tree_list.syntax().range(),
246 tree.syntax().text().to_string(),
247 );
248 Some(())
249}
250
251fn single_use_tree(tree_list: ast::UseTreeList) -> Option<ast::UseTree> {
252 let mut res = None;
253 for child in tree_list.syntax().children() {
254 if let Some(tree) = ast::UseTree::cast(child) {
255 if tree.syntax().text().contains('\n') {
256 return None;
257 }
258 if mem::replace(&mut res, Some(tree)).is_some() {
259 return None;
260 }
261 } else {
262 match child.kind() {
263 WHITESPACE | L_CURLY | R_CURLY | COMMA => (),
264 _ => return None,
265 }
266 }
267 }
268 res
269}
270
231fn compute_ws(left: SyntaxNodeRef, right: SyntaxNodeRef) -> &'static str { 271fn compute_ws(left: SyntaxNodeRef, right: SyntaxNodeRef) -> &'static str {
232 match left.kind() { 272 match left.kind() {
233 L_PAREN | L_BRACK => return "", 273 L_PAREN | L_BRACK => return "",
@@ -306,6 +346,24 @@ fn foo() {
306 } 346 }
307 347
308 #[test] 348 #[test]
349 fn test_join_lines_use_tree() {
350 check_join_lines(
351 r"
352use ra_syntax::{
353 algo::<|>{
354 find_leaf_at_offset,
355 },
356 ast,
357};",
358 r"
359use ra_syntax::{
360 algo::<|>find_leaf_at_offset,
361 ast,
362};",
363 );
364 }
365
366 #[test]
309 fn test_join_lines_normal_comments() { 367 fn test_join_lines_normal_comments() {
310 check_join_lines( 368 check_join_lines(
311 r" 369 r"
diff --git a/crates/ra_hir/src/function.rs b/crates/ra_hir/src/function.rs
index 5187dc051..2925beb16 100644
--- a/crates/ra_hir/src/function.rs
+++ b/crates/ra_hir/src/function.rs
@@ -27,7 +27,7 @@ impl Function {
27 Function { fn_id } 27 Function { fn_id }
28 } 28 }
29 29
30 pub fn scope(&self, db: &impl HirDatabase) -> Arc<FnScopes> { 30 pub fn scopes(&self, db: &impl HirDatabase) -> Arc<FnScopes> {
31 db.fn_scopes(self.fn_id) 31 db.fn_scopes(self.fn_id)
32 } 32 }
33 33
diff --git a/crates/ra_hir/src/function/scope.rs b/crates/ra_hir/src/function/scope.rs
index 863453291..a1a580979 100644
--- a/crates/ra_hir/src/function/scope.rs
+++ b/crates/ra_hir/src/function/scope.rs
@@ -1,7 +1,7 @@
1use rustc_hash::{FxHashMap, FxHashSet}; 1use rustc_hash::{FxHashMap, FxHashSet};
2 2
3use ra_syntax::{ 3use ra_syntax::{
4 AstNode, SmolStr, SyntaxNodeRef, TextRange, 4 AstNode, SmolStr, SyntaxNodeRef, TextUnit, TextRange,
5 algo::generate, 5 algo::generate,
6 ast::{self, ArgListOwner, LoopBodyOwner, NameOwner}, 6 ast::{self, ArgListOwner, LoopBodyOwner, NameOwner},
7}; 7};
@@ -33,7 +33,7 @@ pub struct ScopeData {
33} 33}
34 34
35impl FnScopes { 35impl FnScopes {
36 pub fn new(fn_def: ast::FnDef) -> FnScopes { 36 pub(crate) fn new(fn_def: ast::FnDef) -> FnScopes {
37 let mut scopes = FnScopes { 37 let mut scopes = FnScopes {
38 self_param: fn_def 38 self_param: fn_def
39 .param_list() 39 .param_list()
@@ -57,6 +57,48 @@ impl FnScopes {
57 self.scopes[scope].parent 57 self.scopes[scope].parent
58 }) 58 })
59 } 59 }
60 pub fn scope_chain_for_offset<'a>(
61 &'a self,
62 offset: TextUnit,
63 ) -> impl Iterator<Item = ScopeId> + 'a {
64 let scope = self
65 .scope_for
66 .iter()
67 // find containin scope
68 .min_by_key(|(ptr, _scope)| {
69 (
70 !(ptr.range().start() <= offset && offset <= ptr.range().end()),
71 ptr.range().len(),
72 )
73 })
74 .map(|(ptr, scope)| self.adjust(*ptr, *scope, offset));
75
76 generate(scope, move |&scope| self.scopes[scope].parent)
77 }
78 // XXX: during completion, cursor might be outside of any particular
79 // expression. Try to figure out the correct scope...
80 fn adjust(&self, ptr: LocalSyntaxPtr, original_scope: ScopeId, offset: TextUnit) -> ScopeId {
81 let r = ptr.range();
82 let child_scopes = self
83 .scope_for
84 .iter()
85 .map(|(ptr, scope)| (ptr.range(), scope))
86 .filter(|(range, _)| range.start() <= offset && range.is_subrange(&r) && *range != r);
87
88 child_scopes
89 .max_by(|(r1, _), (r2, _)| {
90 if r2.is_subrange(&r1) {
91 std::cmp::Ordering::Greater
92 } else if r1.is_subrange(&r2) {
93 std::cmp::Ordering::Less
94 } else {
95 r1.start().cmp(&r2.start())
96 }
97 })
98 .map(|(_ptr, scope)| *scope)
99 .unwrap_or(original_scope)
100 }
101
60 pub fn resolve_local_name<'a>(&'a self, name_ref: ast::NameRef) -> Option<&'a ScopeEntry> { 102 pub fn resolve_local_name<'a>(&'a self, name_ref: ast::NameRef) -> Option<&'a ScopeEntry> {
61 let mut shadowed = FxHashSet::default(); 103 let mut shadowed = FxHashSet::default();
62 let ret = self 104 let ret = self
@@ -144,6 +186,8 @@ impl ScopeEntry {
144} 186}
145 187
146fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: ScopeId) { 188fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: ScopeId) {
189 // A hack for completion :(
190 scopes.set_scope(block.syntax(), scope);
147 for stmt in block.statements() { 191 for stmt in block.statements() {
148 match stmt { 192 match stmt {
149 ast::Stmt::LetStmt(stmt) => { 193 ast::Stmt::LetStmt(stmt) => {
diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs
index 5941a9ea3..f56214b47 100644
--- a/crates/ra_hir/src/lib.rs
+++ b/crates/ra_hir/src/lib.rs
@@ -39,7 +39,7 @@ use crate::{
39pub use self::{ 39pub use self::{
40 path::{Path, PathKind}, 40 path::{Path, PathKind},
41 krate::Crate, 41 krate::Crate,
42 module::{Module, ModuleId, Problem, nameres::ItemMap}, 42 module::{Module, ModuleId, Problem, nameres::ItemMap, ModuleScope, Resolution},
43 function::{Function, FnScopes}, 43 function::{Function, FnScopes},
44}; 44};
45 45
diff --git a/crates/ra_hir/src/module.rs b/crates/ra_hir/src/module.rs
index d5866f6ef..cd31e8cfe 100644
--- a/crates/ra_hir/src/module.rs
+++ b/crates/ra_hir/src/module.rs
@@ -16,7 +16,7 @@ use crate::{
16 arena::{Arena, Id}, 16 arena::{Arena, Id},
17}; 17};
18 18
19pub use self::nameres::ModuleScope; 19pub use self::nameres::{ModuleScope, Resolution};
20 20
21/// `Module` is API entry point to get all the information 21/// `Module` is API entry point to get all the information
22/// about a particular module. 22/// about a particular module.
diff --git a/crates/ra_hir/src/module/nameres.rs b/crates/ra_hir/src/module/nameres.rs
index f44abc730..39e891cda 100644
--- a/crates/ra_hir/src/module/nameres.rs
+++ b/crates/ra_hir/src/module/nameres.rs
@@ -49,7 +49,7 @@ pub struct ModuleScope {
49} 49}
50 50
51impl ModuleScope { 51impl ModuleScope {
52 pub fn entries<'a>(&'a self) -> impl Iterator<Item = (&'a SmolStr, &Resolution)> + 'a { 52 pub fn entries<'a>(&'a self) -> impl Iterator<Item = (&'a SmolStr, &'a Resolution)> + 'a {
53 self.items.iter() 53 self.items.iter()
54 } 54 }
55 pub fn get(&self, name: &SmolStr) -> Option<&Resolution> { 55 pub fn get(&self, name: &SmolStr) -> Option<&Resolution> {
diff --git a/crates/ra_hir/src/module/nameres/tests.rs b/crates/ra_hir/src/module/nameres/tests.rs
index 9fa9146e3..3e29c3954 100644
--- a/crates/ra_hir/src/module/nameres/tests.rs
+++ b/crates/ra_hir/src/module/nameres/tests.rs
@@ -44,6 +44,25 @@ fn item_map_smoke_test() {
44} 44}
45 45
46#[test] 46#[test]
47fn test_self() {
48 let (item_map, module_id) = item_map(
49 "
50 //- /lib.rs
51 mod foo;
52 use crate::foo::bar::Baz::{self};
53 <|>
54 //- /foo/mod.rs
55 pub mod bar;
56 //- /foo/bar.rs
57 pub struct Baz;
58 ",
59 );
60 let name = SmolStr::from("Baz");
61 let resolution = &item_map.per_module[&module_id].items[&name];
62 assert!(resolution.def_id.is_some());
63}
64
65#[test]
47fn item_map_across_crates() { 66fn item_map_across_crates() {
48 let (mut db, sr) = MockDatabase::with_files( 67 let (mut db, sr) = MockDatabase::with_files(
49 " 68 "
diff --git a/crates/ra_hir/src/path.rs b/crates/ra_hir/src/path.rs
index 4a2e427cd..e04d00900 100644
--- a/crates/ra_hir/src/path.rs
+++ b/crates/ra_hir/src/path.rs
@@ -76,17 +76,32 @@ fn expand_use_tree(
76) { 76) {
77 if let Some(use_tree_list) = tree.use_tree_list() { 77 if let Some(use_tree_list) = tree.use_tree_list() {
78 let prefix = match tree.path() { 78 let prefix = match tree.path() {
79 // E.g. use something::{{{inner}}};
79 None => prefix, 80 None => prefix,
81 // E.g. `use something::{inner}` (prefix is `None`, path is `something`)
82 // or `use something::{path::{inner::{innerer}}}` (prefix is `something::path`, path is `inner`)
80 Some(path) => match convert_path(prefix, path) { 83 Some(path) => match convert_path(prefix, path) {
81 Some(it) => Some(it), 84 Some(it) => Some(it),
82 None => return, // TODO: report errors somewhere 85 None => return, // TODO: report errors somewhere
83 }, 86 },
84 }; 87 };
85 for tree in use_tree_list.use_trees() { 88 for child_tree in use_tree_list.use_trees() {
86 expand_use_tree(prefix.clone(), tree, cb); 89 expand_use_tree(prefix.clone(), child_tree, cb);
87 } 90 }
88 } else { 91 } else {
89 if let Some(ast_path) = tree.path() { 92 if let Some(ast_path) = tree.path() {
93 // Handle self in a path.
94 // E.g. `use something::{self, <...>}`
95 if ast_path.qualifier().is_none() {
96 if let Some(segment) = ast_path.segment() {
97 if segment.kind() == Some(ast::PathSegmentKind::SelfKw) {
98 if let Some(prefix) = prefix {
99 cb(prefix, Some(segment.syntax().range()));
100 return;
101 }
102 }
103 }
104 }
90 if let Some(path) = convert_path(prefix, ast_path) { 105 if let Some(path) = convert_path(prefix, ast_path) {
91 let range = if tree.has_star() { 106 let range = if tree.has_star() {
92 None 107 None
@@ -96,6 +111,8 @@ fn expand_use_tree(
96 }; 111 };
97 cb(path, range) 112 cb(path, range)
98 } 113 }
114 // TODO: report errors somewhere
115 // We get here if we do
99 } 116 }
100 } 117 }
101} 118}
diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs
index ce2a0f2e8..a0165aef2 100644
--- a/crates/ra_hir/src/source_binder.rs
+++ b/crates/ra_hir/src/source_binder.rs
@@ -74,6 +74,16 @@ pub fn function_from_source(
74 fn_def: ast::FnDef, 74 fn_def: ast::FnDef,
75) -> Cancelable<Option<Function>> { 75) -> Cancelable<Option<Function>> {
76 let module = ctry!(module_from_child_node(db, file_id, fn_def.syntax())?); 76 let module = ctry!(module_from_child_node(db, file_id, fn_def.syntax())?);
77 let res = function_from_module(db, &module, fn_def);
78 Ok(Some(res))
79}
80
81pub fn function_from_module(
82 db: &impl HirDatabase,
83 module: &Module,
84 fn_def: ast::FnDef,
85) -> Function {
86 let file_id = module.source().file_id();
77 let file_items = db.file_items(file_id); 87 let file_items = db.file_items(file_id);
78 let item_id = file_items.id_of(file_id, fn_def.syntax()); 88 let item_id = file_items.id_of(file_id, fn_def.syntax());
79 let source_item_id = SourceItemId { 89 let source_item_id = SourceItemId {
@@ -86,7 +96,7 @@ pub fn function_from_source(
86 module_id: module.module_id, 96 module_id: module.module_id,
87 source_item_id, 97 source_item_id,
88 }; 98 };
89 Ok(Some(Function::new(def_loc.id(db)))) 99 Function::new(def_loc.id(db))
90} 100}
91 101
92pub fn function_from_child_node( 102pub fn function_from_child_node(
diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml
index fc10096e5..3c8c240cd 100644
--- a/crates/ra_lsp_server/Cargo.toml
+++ b/crates/ra_lsp_server/Cargo.toml
@@ -12,7 +12,6 @@ failure = "0.1.2"
12failure_derive = "0.1.2" 12failure_derive = "0.1.2"
13serde_json = "1.0.24" 13serde_json = "1.0.24"
14serde = "1.0.71" 14serde = "1.0.71"
15serde_derive = "1.0.71"
16drop_bomb = "0.1.0" 15drop_bomb = "0.1.0"
17crossbeam-channel = "0.2.4" 16crossbeam-channel = "0.2.4"
18flexi_logger = "0.10.0" 17flexi_logger = "0.10.0"
diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs
index 218ded4ee..051f1f995 100644
--- a/crates/ra_lsp_server/src/conv.rs
+++ b/crates/ra_lsp_server/src/conv.rs
@@ -1,8 +1,8 @@
1use languageserver_types::{ 1use languageserver_types::{
2 self, Location, Position, Range, SymbolKind, TextDocumentEdit, TextDocumentIdentifier, 2 self, Location, Position, Range, SymbolKind, TextDocumentEdit, TextDocumentIdentifier,
3 TextDocumentItem, TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, 3 TextDocumentItem, TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, InsertTextFormat,
4}; 4};
5use ra_analysis::{FileId, FileSystemEdit, SourceChange, SourceFileEdit, FilePosition}; 5use ra_analysis::{FileId, FileSystemEdit, SourceChange, SourceFileEdit, FilePosition, CompletionItem, CompletionItemKind, InsertText};
6use ra_editor::{LineCol, LineIndex}; 6use ra_editor::{LineCol, LineIndex};
7use ra_text_edit::{AtomTextEdit, TextEdit}; 7use ra_text_edit::{AtomTextEdit, TextEdit};
8use ra_syntax::{SyntaxKind, TextRange, TextUnit}; 8use ra_syntax::{SyntaxKind, TextRange, TextUnit};
@@ -45,6 +45,46 @@ impl Conv for SyntaxKind {
45 } 45 }
46} 46}
47 47
48impl Conv for CompletionItemKind {
49 type Output = ::languageserver_types::CompletionItemKind;
50
51 fn conv(self) -> <Self as Conv>::Output {
52 use ::languageserver_types::CompletionItemKind::*;
53 match self {
54 CompletionItemKind::Keyword => Keyword,
55 CompletionItemKind::Snippet => Snippet,
56 CompletionItemKind::Module => Module,
57 CompletionItemKind::Function => Function,
58 CompletionItemKind::Binding => Variable,
59 }
60 }
61}
62
63impl Conv for CompletionItem {
64 type Output = ::languageserver_types::CompletionItem;
65
66 fn conv(self) -> <Self as Conv>::Output {
67 let mut res = ::languageserver_types::CompletionItem {
68 label: self.label().to_string(),
69 filter_text: Some(self.lookup().to_string()),
70 kind: self.kind().map(|it| it.conv()),
71 ..Default::default()
72 };
73 match self.insert_text() {
74 InsertText::PlainText { text } => {
75 res.insert_text = Some(text);
76 res.insert_text_format = Some(InsertTextFormat::PlainText);
77 }
78 InsertText::Snippet { text } => {
79 res.insert_text = Some(text);
80 res.insert_text_format = Some(InsertTextFormat::Snippet);
81 res.kind = Some(languageserver_types::CompletionItemKind::Keyword);
82 }
83 }
84 res
85 }
86}
87
48impl ConvWith for Position { 88impl ConvWith for Position {
49 type Ctx = LineIndex; 89 type Ctx = LineIndex;
50 type Output = TextUnit; 90 type Output = TextUnit;
diff --git a/crates/ra_lsp_server/src/main.rs b/crates/ra_lsp_server/src/main.rs
index 4497980e5..eae601f91 100644
--- a/crates/ra_lsp_server/src/main.rs
+++ b/crates/ra_lsp_server/src/main.rs
@@ -1,7 +1,7 @@
1use serde_derive::Deserialize; 1use serde::Deserialize;
2use serde::Deserialize as _D;
3use flexi_logger::{Duplicate, Logger}; 2use flexi_logger::{Duplicate, Logger};
4use gen_lsp_server::{run_server, stdio_transport}; 3use gen_lsp_server::{run_server, stdio_transport};
4
5use ra_lsp_server::Result; 5use ra_lsp_server::Result;
6 6
7fn main() -> Result<()> { 7fn main() -> Result<()> {
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index afe0fec89..565ec92af 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -2,6 +2,7 @@ mod handlers;
2mod subscriptions; 2mod subscriptions;
3 3
4use std::{ 4use std::{
5 fmt,
5 path::PathBuf, 6 path::PathBuf,
6 sync::Arc, 7 sync::Arc,
7}; 8};
@@ -109,6 +110,50 @@ pub fn main_loop(
109 Ok(()) 110 Ok(())
110} 111}
111 112
113enum Event {
114 Msg(RawMessage),
115 Task(Task),
116 Vfs(VfsTask),
117 Lib(LibraryData),
118}
119
120impl fmt::Debug for Event {
121 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
122 let debug_verbose_not = |not: &RawNotification, f: &mut fmt::Formatter| {
123 f.debug_struct("RawNotification")
124 .field("method", &not.method)
125 .finish()
126 };
127
128 match self {
129 Event::Msg(RawMessage::Notification(not)) => {
130 if not.is::<req::DidOpenTextDocument>() || not.is::<req::DidChangeTextDocument>() {
131 return debug_verbose_not(not, f);
132 }
133 }
134 Event::Task(Task::Notify(not)) => {
135 if not.is::<req::PublishDecorations>() || not.is::<req::PublishDiagnostics>() {
136 return debug_verbose_not(not, f);
137 }
138 }
139 Event::Task(Task::Respond(resp)) => {
140 return f
141 .debug_struct("RawResponse")
142 .field("id", &resp.id)
143 .field("error", &resp.error)
144 .finish();
145 }
146 _ => (),
147 }
148 match self {
149 Event::Msg(it) => fmt::Debug::fmt(it, f),
150 Event::Task(it) => fmt::Debug::fmt(it, f),
151 Event::Vfs(it) => fmt::Debug::fmt(it, f),
152 Event::Lib(it) => fmt::Debug::fmt(it, f),
153 }
154 }
155}
156
112fn main_loop_inner( 157fn main_loop_inner(
113 internal_mode: bool, 158 internal_mode: bool,
114 publish_decorations: bool, 159 publish_decorations: bool,
@@ -123,13 +168,6 @@ fn main_loop_inner(
123) -> Result<()> { 168) -> Result<()> {
124 let (libdata_sender, libdata_receiver) = unbounded(); 169 let (libdata_sender, libdata_receiver) = unbounded();
125 loop { 170 loop {
126 #[derive(Debug)]
127 enum Event {
128 Msg(RawMessage),
129 Task(Task),
130 Vfs(VfsTask),
131 Lib(LibraryData),
132 }
133 log::trace!("selecting"); 171 log::trace!("selecting");
134 let event = select! { 172 let event = select! {
135 recv(msg_receiver, msg) => match msg { 173 recv(msg_receiver, msg) => match msg {
@@ -143,7 +181,8 @@ fn main_loop_inner(
143 } 181 }
144 recv(libdata_receiver, data) => Event::Lib(data.unwrap()) 182 recv(libdata_receiver, data) => Event::Lib(data.unwrap())
145 }; 183 };
146 log::info!("{:?}", event); 184 log::info!("loop_turn = {:?}", event);
185 let start = std::time::Instant::now();
147 let mut state_changed = false; 186 let mut state_changed = false;
148 match event { 187 match event {
149 Event::Task(task) => on_task(task, msg_sender, pending_requests), 188 Event::Task(task) => on_task(task, msg_sender, pending_requests),
@@ -206,6 +245,7 @@ fn main_loop_inner(
206 subs.subscriptions(), 245 subs.subscriptions(),
207 ) 246 )
208 } 247 }
248 log::info!("loop_turn = {:?}", start.elapsed());
209 } 249 }
210} 250}
211 251
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 1751d7fa8..252d1ba3e 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -2,9 +2,9 @@ use std::collections::HashMap;
2 2
3use gen_lsp_server::ErrorCode; 3use gen_lsp_server::ErrorCode;
4use languageserver_types::{ 4use languageserver_types::{
5 CodeActionResponse, Command, CompletionItem, CompletionItemKind, Diagnostic, 5 CodeActionResponse, Command, Diagnostic,
6 DiagnosticSeverity, DocumentSymbol, Documentation, FoldingRange, FoldingRangeKind, 6 DiagnosticSeverity, DocumentSymbol, Documentation, FoldingRange, FoldingRangeKind,
7 FoldingRangeParams, InsertTextFormat, Location, MarkupContent, MarkupKind, MarkedString, Position, 7 FoldingRangeParams, Location, MarkupContent, MarkupKind, MarkedString, Position,
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};
@@ -419,22 +419,7 @@ pub fn handle_completion(
419 None => return Ok(None), 419 None => return Ok(None),
420 Some(items) => items, 420 Some(items) => items,
421 }; 421 };
422 let items = items 422 let items = items.into_iter().map(|item| item.conv()).collect();
423 .into_iter()
424 .map(|item| {
425 let mut res = CompletionItem {
426 label: item.label,
427 filter_text: item.lookup,
428 ..Default::default()
429 };
430 if let Some(snip) = item.snippet {
431 res.insert_text = Some(snip);
432 res.insert_text_format = Some(InsertTextFormat::Snippet);
433 res.kind = Some(CompletionItemKind::Keyword);
434 };
435 res
436 })
437 .collect();
438 423
439 Ok(Some(req::CompletionResponse::Array(items))) 424 Ok(Some(req::CompletionResponse::Array(items)))
440} 425}
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs
index 999792ecb..747ab8a8c 100644
--- a/crates/ra_lsp_server/src/req.rs
+++ b/crates/ra_lsp_server/src/req.rs
@@ -1,4 +1,4 @@
1use serde_derive::{Serialize, Deserialize}; 1use serde::{Serialize, Deserialize};
2use languageserver_types::{Location, Position, Range, TextDocumentIdentifier, Url}; 2use languageserver_types::{Location, Position, Range, TextDocumentIdentifier, Url};
3use rustc_hash::FxHashMap; 3use rustc_hash::FxHashMap;
4use url_serde; 4use url_serde;
diff --git a/crates/ra_lsp_server/src/server_world.rs b/crates/ra_lsp_server/src/server_world.rs
index 73cccc9dd..c183c25af 100644
--- a/crates/ra_lsp_server/src/server_world.rs
+++ b/crates/ra_lsp_server/src/server_world.rs
@@ -107,7 +107,6 @@ impl ServerWorldState {
107 let mut libs = Vec::new(); 107 let mut libs = Vec::new();
108 let mut change = AnalysisChange::new(); 108 let mut change = AnalysisChange::new();
109 for c in changes { 109 for c in changes {
110 log::info!("vfs change {:?}", c);
111 match c { 110 match c {
112 VfsChange::AddRoot { root, files } => { 111 VfsChange::AddRoot { root, files } => {
113 let root_path = self.vfs.read().root2path(root); 112 let root_path = self.vfs.read().root2path(root);
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs
index 91c67119f..f12479fb4 100644
--- a/crates/ra_syntax/src/ast.rs
+++ b/crates/ra_syntax/src/ast.rs
@@ -284,7 +284,7 @@ impl<'a> IfExpr<'a> {
284 } 284 }
285} 285}
286 286
287#[derive(Debug, Clone, Copy)] 287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288pub enum PathSegmentKind<'a> { 288pub enum PathSegmentKind<'a> {
289 Name(NameRef<'a>), 289 Name(NameRef<'a>),
290 SelfKw, 290 SelfKw,
diff --git a/crates/ra_syntax/src/grammar/type_params.rs b/crates/ra_syntax/src/grammar/type_params.rs
index 863f8e00c..7db25beba 100644
--- a/crates/ra_syntax/src/grammar/type_params.rs
+++ b/crates/ra_syntax/src/grammar/type_params.rs
@@ -96,6 +96,7 @@ pub(super) fn bounds_without_colon(p: &mut Parser) {
96// 'a: 'b + 'c, 96// 'a: 'b + 'c,
97// T: Clone + Copy + 'static, 97// T: Clone + Copy + 'static,
98// Iterator::Item: 'a, 98// Iterator::Item: 'a,
99// <T as Iterator>::Item: 'a
99// {} 100// {}
100pub(super) fn opt_where_clause(p: &mut Parser) { 101pub(super) fn opt_where_clause(p: &mut Parser) {
101 if !p.at(WHERE_KW) { 102 if !p.at(WHERE_KW) {
@@ -104,7 +105,11 @@ pub(super) fn opt_where_clause(p: &mut Parser) {
104 let m = p.start(); 105 let m = p.start();
105 p.bump(); 106 p.bump();
106 loop { 107 loop {
107 if !(paths::is_path_start(p) || p.current() == LIFETIME || p.current() == FOR_KW) { 108 if !(paths::is_path_start(p)
109 || p.current() == LIFETIME
110 || p.current() == FOR_KW
111 || p.current() == L_ANGLE)
112 {
108 break; 113 break;
109 } 114 }
110 where_predicate(p); 115 where_predicate(p);
diff --git a/crates/ra_syntax/src/lexer.rs b/crates/ra_syntax/src/lexer.rs
index f388da273..c6acd095e 100644
--- a/crates/ra_syntax/src/lexer.rs
+++ b/crates/ra_syntax/src/lexer.rs
@@ -160,7 +160,7 @@ fn next_token_inner(c: char, ptr: &mut Ptr) -> SyntaxKind {
160 // if we find one, then this is an invalid character literal 160 // if we find one, then this is an invalid character literal
161 if ptr.at('\'') { 161 if ptr.at('\'') {
162 ptr.bump(); 162 ptr.bump();
163 return CHAR; // TODO: error reporting 163 return CHAR;
164 } 164 }
165 LIFETIME 165 LIFETIME
166 } else { 166 } else {
diff --git a/crates/ra_syntax/src/string_lexing/byte.rs b/crates/ra_syntax/src/string_lexing/byte.rs
index 24424349c..b3228d6ca 100644
--- a/crates/ra_syntax/src/string_lexing/byte.rs
+++ b/crates/ra_syntax/src/string_lexing/byte.rs
@@ -40,7 +40,7 @@ impl<'a> Iterator for ByteComponentIterator<'a> {
40 40
41 assert!( 41 assert!(
42 self.parser.peek() == None, 42 self.parser.peek() == None,
43 "byte literal should leave no unparsed input: src = {}, pos = {}, length = {}", 43 "byte literal should leave no unparsed input: src = {:?}, pos = {}, length = {}",
44 self.parser.src, 44 self.parser.src,
45 self.parser.pos, 45 self.parser.pos,
46 self.parser.src.len() 46 self.parser.src.len()
diff --git a/crates/ra_syntax/src/string_lexing/byte_string.rs b/crates/ra_syntax/src/string_lexing/byte_string.rs
index 5b6dda760..a6056159b 100644
--- a/crates/ra_syntax/src/string_lexing/byte_string.rs
+++ b/crates/ra_syntax/src/string_lexing/byte_string.rs
@@ -40,7 +40,7 @@ impl<'a> Iterator for ByteStringComponentIterator<'a> {
40 40
41 assert!( 41 assert!(
42 self.parser.peek() == None, 42 self.parser.peek() == None,
43 "byte string literal should leave no unparsed input: src = {}, pos = {}, length = {}", 43 "byte string literal should leave no unparsed input: src = {:?}, pos = {}, length = {}",
44 self.parser.src, 44 self.parser.src,
45 self.parser.pos, 45 self.parser.pos,
46 self.parser.src.len() 46 self.parser.src.len()
diff --git a/crates/ra_syntax/src/string_lexing/char.rs b/crates/ra_syntax/src/string_lexing/char.rs
index 885c03b14..e01813176 100644
--- a/crates/ra_syntax/src/string_lexing/char.rs
+++ b/crates/ra_syntax/src/string_lexing/char.rs
@@ -35,7 +35,7 @@ impl<'a> Iterator for CharComponentIterator<'a> {
35 35
36 assert!( 36 assert!(
37 self.parser.peek() == None, 37 self.parser.peek() == None,
38 "char literal should leave no unparsed input: src = {}, pos = {}, length = {}", 38 "char literal should leave no unparsed input: src = {:?}, pos = {}, length = {}",
39 self.parser.src, 39 self.parser.src,
40 self.parser.pos, 40 self.parser.pos,
41 self.parser.src.len() 41 self.parser.src.len()
diff --git a/crates/ra_syntax/src/string_lexing/string.rs b/crates/ra_syntax/src/string_lexing/string.rs
index 1b23029c6..d8351e9af 100644
--- a/crates/ra_syntax/src/string_lexing/string.rs
+++ b/crates/ra_syntax/src/string_lexing/string.rs
@@ -35,7 +35,7 @@ impl<'a> Iterator for StringComponentIterator<'a> {
35 35
36 assert!( 36 assert!(
37 self.parser.peek() == None, 37 self.parser.peek() == None,
38 "string literal should leave no unparsed input: src = {}, pos = {}, length = {}", 38 "string literal should leave no unparsed input: src = {:?}, pos = {}, length = {}",
39 self.parser.src, 39 self.parser.src,
40 self.parser.pos, 40 self.parser.pos,
41 self.parser.src.len() 41 self.parser.src.len()
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0056_where_clause.rs b/crates/ra_syntax/tests/data/parser/inline/ok/0056_where_clause.rs
index 592a005f9..19d7e571b 100644
--- a/crates/ra_syntax/tests/data/parser/inline/ok/0056_where_clause.rs
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0056_where_clause.rs
@@ -3,4 +3,5 @@ where
3 'a: 'b + 'c, 3 'a: 'b + 'c,
4 T: Clone + Copy + 'static, 4 T: Clone + Copy + 'static,
5 Iterator::Item: 'a, 5 Iterator::Item: 'a,
6 <T as Iterator>::Item: 'a
6{} 7{}
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0056_where_clause.txt b/crates/ra_syntax/tests/data/parser/inline/ok/0056_where_clause.txt
index 54c3d64f1..68485dc0b 100644
--- a/crates/ra_syntax/tests/data/parser/inline/ok/0056_where_clause.txt
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0056_where_clause.txt
@@ -1,5 +1,5 @@
1SOURCE_FILE@[0; 87) 1SOURCE_FILE@[0; 116)
2 FN_DEF@[0; 86) 2 FN_DEF@[0; 115)
3 FN_KW@[0; 2) 3 FN_KW@[0; 2)
4 WHITESPACE@[2; 3) 4 WHITESPACE@[2; 3)
5 NAME@[3; 6) 5 NAME@[3; 6)
@@ -8,7 +8,7 @@ SOURCE_FILE@[0; 87)
8 L_PAREN@[6; 7) 8 L_PAREN@[6; 7)
9 R_PAREN@[7; 8) 9 R_PAREN@[7; 8)
10 WHITESPACE@[8; 9) 10 WHITESPACE@[8; 9)
11 WHERE_CLAUSE@[9; 83) 11 WHERE_CLAUSE@[9; 112)
12 WHERE_KW@[9; 14) 12 WHERE_KW@[9; 14)
13 WHITESPACE@[14; 18) 13 WHITESPACE@[14; 18)
14 WHERE_PRED@[18; 29) 14 WHERE_PRED@[18; 29)
@@ -64,8 +64,36 @@ SOURCE_FILE@[0; 87)
64 WHITESPACE@[79; 80) 64 WHITESPACE@[79; 80)
65 LIFETIME@[80; 82) "'a" 65 LIFETIME@[80; 82) "'a"
66 COMMA@[82; 83) 66 COMMA@[82; 83)
67 WHITESPACE@[83; 84) 67 WHITESPACE@[83; 87)
68 BLOCK@[84; 86) 68 WHERE_PRED@[87; 112)
69 L_CURLY@[84; 85) 69 PATH_TYPE@[87; 108)
70 R_CURLY@[85; 86) 70 PATH@[87; 108)
71 WHITESPACE@[86; 87) 71 PATH@[87; 102)
72 PATH_SEGMENT@[87; 102)
73 L_ANGLE@[87; 88)
74 PATH_TYPE@[88; 89)
75 PATH@[88; 89)
76 PATH_SEGMENT@[88; 89)
77 NAME_REF@[88; 89)
78 IDENT@[88; 89) "T"
79 WHITESPACE@[89; 90)
80 AS_KW@[90; 92)
81 WHITESPACE@[92; 93)
82 PATH_TYPE@[93; 101)
83 PATH@[93; 101)
84 PATH_SEGMENT@[93; 101)
85 NAME_REF@[93; 101)
86 IDENT@[93; 101) "Iterator"
87 R_ANGLE@[101; 102)
88 COLONCOLON@[102; 104)
89 PATH_SEGMENT@[104; 108)
90 NAME_REF@[104; 108)
91 IDENT@[104; 108) "Item"
92 COLON@[108; 109)
93 WHITESPACE@[109; 110)
94 LIFETIME@[110; 112) "'a"
95 WHITESPACE@[112; 113)
96 BLOCK@[113; 115)
97 L_CURLY@[113; 114)
98 R_CURLY@[114; 115)
99 WHITESPACE@[115; 116)
diff --git a/crates/ra_syntax/tests/data/parser/ok/0036_fully_qualified.rs b/crates/ra_syntax/tests/data/parser/ok/0036_fully_qualified.rs
new file mode 100644
index 000000000..6da27933e
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/ok/0036_fully_qualified.rs
@@ -0,0 +1,8 @@
1// https://github.com/rust-analyzer/rust-analyzer/issues/311
2
3pub fn foo<S: Iterator>() -> String
4where
5 <S as Iterator>::Item: Eq,
6{
7 "".to_owned()
8}
diff --git a/crates/ra_syntax/tests/data/parser/ok/0036_fully_qualified.txt b/crates/ra_syntax/tests/data/parser/ok/0036_fully_qualified.txt
new file mode 100644
index 000000000..208e5e51c
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/ok/0036_fully_qualified.txt
@@ -0,0 +1,88 @@
1SOURCE_FILE@[0; 157)
2 COMMENT@[0; 60)
3 WHITESPACE@[60; 62)
4 FN_DEF@[62; 156)
5 VISIBILITY@[62; 65)
6 PUB_KW@[62; 65)
7 WHITESPACE@[65; 66)
8 FN_KW@[66; 68)
9 WHITESPACE@[68; 69)
10 NAME@[69; 72)
11 IDENT@[69; 72) "foo"
12 TYPE_PARAM_LIST@[72; 85)
13 L_ANGLE@[72; 73)
14 TYPE_PARAM@[73; 84)
15 NAME@[73; 74)
16 IDENT@[73; 74) "S"
17 COLON@[74; 75)
18 WHITESPACE@[75; 76)
19 PATH_TYPE@[76; 84)
20 PATH@[76; 84)
21 PATH_SEGMENT@[76; 84)
22 NAME_REF@[76; 84)
23 IDENT@[76; 84) "Iterator"
24 R_ANGLE@[84; 85)
25 PARAM_LIST@[85; 87)
26 L_PAREN@[85; 86)
27 R_PAREN@[86; 87)
28 WHITESPACE@[87; 88)
29 RET_TYPE@[88; 97)
30 THIN_ARROW@[88; 90)
31 WHITESPACE@[90; 91)
32 PATH_TYPE@[91; 97)
33 PATH@[91; 97)
34 PATH_SEGMENT@[91; 97)
35 NAME_REF@[91; 97)
36 IDENT@[91; 97) "String"
37 WHITESPACE@[97; 98)
38 WHERE_CLAUSE@[98; 134)
39 WHERE_KW@[98; 103)
40 WHITESPACE@[103; 108)
41 WHERE_PRED@[108; 133)
42 PATH_TYPE@[108; 129)
43 PATH@[108; 129)
44 PATH@[108; 123)
45 PATH_SEGMENT@[108; 123)
46 L_ANGLE@[108; 109)
47 PATH_TYPE@[109; 110)
48 PATH@[109; 110)
49 PATH_SEGMENT@[109; 110)
50 NAME_REF@[109; 110)
51 IDENT@[109; 110) "S"
52 WHITESPACE@[110; 111)
53 AS_KW@[111; 113)
54 WHITESPACE@[113; 114)
55 PATH_TYPE@[114; 122)
56 PATH@[114; 122)
57 PATH_SEGMENT@[114; 122)
58 NAME_REF@[114; 122)
59 IDENT@[114; 122) "Iterator"
60 R_ANGLE@[122; 123)
61 COLONCOLON@[123; 125)
62 PATH_SEGMENT@[125; 129)
63 NAME_REF@[125; 129)
64 IDENT@[125; 129) "Item"
65 COLON@[129; 130)
66 WHITESPACE@[130; 131)
67 PATH_TYPE@[131; 133)
68 PATH@[131; 133)
69 PATH_SEGMENT@[131; 133)
70 NAME_REF@[131; 133)
71 IDENT@[131; 133) "Eq"
72 COMMA@[133; 134)
73 WHITESPACE@[134; 135)
74 BLOCK@[135; 156)
75 L_CURLY@[135; 136)
76 WHITESPACE@[136; 141)
77 METHOD_CALL_EXPR@[141; 154)
78 LITERAL@[141; 143)
79 STRING@[141; 143)
80 DOT@[143; 144)
81 NAME_REF@[144; 152)
82 IDENT@[144; 152) "to_owned"
83 ARG_LIST@[152; 154)
84 L_PAREN@[152; 153)
85 R_PAREN@[153; 154)
86 WHITESPACE@[154; 155)
87 R_CURLY@[155; 156)
88 WHITESPACE@[156; 157)
diff --git a/crates/ra_syntax/tests/test.rs b/crates/ra_syntax/tests/test.rs
index 14ad836b5..4266864bd 100644
--- a/crates/ra_syntax/tests/test.rs
+++ b/crates/ra_syntax/tests/test.rs
@@ -65,14 +65,10 @@ fn self_hosting_parsing() {
65 for entry in walkdir::WalkDir::new(dir) 65 for entry in walkdir::WalkDir::new(dir)
66 .into_iter() 66 .into_iter()
67 .filter_entry(|entry| { 67 .filter_entry(|entry| {
68 !entry 68 !entry.path().components().any(|component| {
69 .path() 69 // Get all files which are not in the crates/ra_syntax/tests/data folder
70 .components() 70 component == Component::Normal(OsStr::new("data"))
71 // TODO: this more neatly 71 })
72 .any(|component| {
73 // Get all files which are not in the crates/ra_syntax/tests/data folder
74 component == Component::Normal(OsStr::new("data"))
75 })
76 }) 72 })
77 .map(|e| e.unwrap()) 73 .map(|e| e.unwrap())
78 .filter(|entry| { 74 .filter(|entry| {
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 }};