aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2019-11-27 18:32:33 +0000
committerAleksey Kladov <[email protected]>2019-11-27 18:35:06 +0000
commit757e593b253b4df7e6fc8bf15a4d4f34c9d484c5 (patch)
treed972d3a7e6457efdb5e0c558a8350db1818d07ae /crates/ra_ide_api
parentd9a36a736bfb91578a36505e7237212959bb55fe (diff)
rename ra_ide_api -> ra_ide
Diffstat (limited to 'crates/ra_ide_api')
-rw-r--r--crates/ra_ide_api/Cargo.toml46
-rw-r--r--crates/ra_ide_api/src/assists.rs28
-rw-r--r--crates/ra_ide_api/src/call_info.rs592
-rw-r--r--crates/ra_ide_api/src/change.rs354
-rw-r--r--crates/ra_ide_api/src/completion.rs77
-rw-r--r--crates/ra_ide_api/src/completion/complete_dot.rs456
-rw-r--r--crates/ra_ide_api/src/completion/complete_fn_param.rs136
-rw-r--r--crates/ra_ide_api/src/completion/complete_keyword.rs781
-rw-r--r--crates/ra_ide_api/src/completion/complete_macro_in_item_position.rs143
-rw-r--r--crates/ra_ide_api/src/completion/complete_path.rs785
-rw-r--r--crates/ra_ide_api/src/completion/complete_pattern.rs89
-rw-r--r--crates/ra_ide_api/src/completion/complete_postfix.rs282
-rw-r--r--crates/ra_ide_api/src/completion/complete_record_literal.rs159
-rw-r--r--crates/ra_ide_api/src/completion/complete_record_pattern.rs93
-rw-r--r--crates/ra_ide_api/src/completion/complete_scope.rs876
-rw-r--r--crates/ra_ide_api/src/completion/complete_snippet.rs120
-rw-r--r--crates/ra_ide_api/src/completion/completion_context.rs274
-rw-r--r--crates/ra_ide_api/src/completion/completion_item.rs322
-rw-r--r--crates/ra_ide_api/src/completion/presentation.rs676
-rw-r--r--crates/ra_ide_api/src/db.rs144
-rw-r--r--crates/ra_ide_api/src/diagnostics.rs652
-rw-r--r--crates/ra_ide_api/src/display.rs84
-rw-r--r--crates/ra_ide_api/src/display/function_signature.rs215
-rw-r--r--crates/ra_ide_api/src/display/navigation_target.rs411
-rw-r--r--crates/ra_ide_api/src/display/short_label.rs97
-rw-r--r--crates/ra_ide_api/src/display/structure.rs401
-rw-r--r--crates/ra_ide_api/src/expand.rs63
-rw-r--r--crates/ra_ide_api/src/expand_macro.rs295
-rw-r--r--crates/ra_ide_api/src/extend_selection.rs452
-rw-r--r--crates/ra_ide_api/src/feature_flags.rs70
-rw-r--r--crates/ra_ide_api/src/folding_ranges.rs378
-rw-r--r--crates/ra_ide_api/src/goto_definition.rs696
-rw-r--r--crates/ra_ide_api/src/goto_type_definition.rs105
-rw-r--r--crates/ra_ide_api/src/hover.rs730
-rw-r--r--crates/ra_ide_api/src/impls.rs206
-rw-r--r--crates/ra_ide_api/src/inlay_hints.rs543
-rw-r--r--crates/ra_ide_api/src/join_lines.rs611
-rw-r--r--crates/ra_ide_api/src/lib.rs489
-rw-r--r--crates/ra_ide_api/src/line_index.rs283
-rw-r--r--crates/ra_ide_api/src/line_index_utils.rs331
-rw-r--r--crates/ra_ide_api/src/marks.rs13
-rw-r--r--crates/ra_ide_api/src/matching_brace.rs43
-rw-r--r--crates/ra_ide_api/src/mock_analysis.rs149
-rw-r--r--crates/ra_ide_api/src/parent_module.rs104
-rw-r--r--crates/ra_ide_api/src/references.rs389
-rw-r--r--crates/ra_ide_api/src/references/classify.rs186
-rw-r--r--crates/ra_ide_api/src/references/name_definition.rs83
-rw-r--r--crates/ra_ide_api/src/references/rename.rs328
-rw-r--r--crates/ra_ide_api/src/references/search_scope.rs145
-rw-r--r--crates/ra_ide_api/src/runnables.rs242
-rw-r--r--crates/ra_ide_api/src/snapshots/highlighting.html48
-rw-r--r--crates/ra_ide_api/src/snapshots/rainbow_highlighting.html33
-rw-r--r--crates/ra_ide_api/src/source_change.rs119
-rw-r--r--crates/ra_ide_api/src/status.rs136
-rw-r--r--crates/ra_ide_api/src/symbol_index.rs405
-rw-r--r--crates/ra_ide_api/src/syntax_highlighting.rs342
-rw-r--r--crates/ra_ide_api/src/syntax_tree.rs359
-rw-r--r--crates/ra_ide_api/src/test_utils.rs21
-rw-r--r--crates/ra_ide_api/src/typing.rs490
-rw-r--r--crates/ra_ide_api/src/wasm_shims.rs19
60 files changed, 0 insertions, 17199 deletions
diff --git a/crates/ra_ide_api/Cargo.toml b/crates/ra_ide_api/Cargo.toml
deleted file mode 100644
index 15346f388..000000000
--- a/crates/ra_ide_api/Cargo.toml
+++ /dev/null
@@ -1,46 +0,0 @@
1[package]
2edition = "2018"
3name = "ra_ide_api"
4version = "0.1.0"
5authors = ["rust-analyzer developers"]
6
7[lib]
8doctest = false
9
10[features]
11wasm = []
12
13[dependencies]
14format-buf = "1.0.0"
15itertools = "0.8.0"
16join_to_string = "0.1.3"
17log = "0.4.5"
18rayon = "1.0.2"
19fst = { version = "0.3.1", default-features = false }
20rustc-hash = "1.0"
21unicase = "2.2.0"
22superslice = "1.0.0"
23rand = { version = "0.7.0", features = ["small_rng"] }
24once_cell = "1.2.0"
25
26ra_syntax = { path = "../ra_syntax" }
27ra_text_edit = { path = "../ra_text_edit" }
28ra_db = { path = "../ra_db" }
29ra_cfg = { path = "../ra_cfg" }
30ra_fmt = { path = "../ra_fmt" }
31ra_prof = { path = "../ra_prof" }
32test_utils = { path = "../test_utils" }
33ra_assists = { path = "../ra_assists" }
34
35# ra_ide_api should depend only on the top-level `hir` package. if you need
36# something from some `hir_xxx` subpackage, reexport the API via `hir`.
37hir = { path = "../ra_hir", package = "ra_hir" }
38
39[dev-dependencies]
40insta = "0.12.0"
41
42[dev-dependencies.proptest]
43version = "0.9.0"
44# Disable `fork` feature to allow compiling on webassembly
45default-features = false
46features = ["std", "bit-set", "break-dead-code"]
diff --git a/crates/ra_ide_api/src/assists.rs b/crates/ra_ide_api/src/assists.rs
deleted file mode 100644
index e00589733..000000000
--- a/crates/ra_ide_api/src/assists.rs
+++ /dev/null
@@ -1,28 +0,0 @@
1//! FIXME: write short doc here
2
3use ra_db::{FilePosition, FileRange};
4
5use crate::{db::RootDatabase, SourceChange, SourceFileEdit};
6
7pub use ra_assists::AssistId;
8
9#[derive(Debug)]
10pub struct Assist {
11 pub id: AssistId,
12 pub change: SourceChange,
13}
14
15pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> {
16 ra_assists::assists(db, frange)
17 .into_iter()
18 .map(|(label, action)| {
19 let file_id = frange.file_id;
20 let file_edit = SourceFileEdit { file_id, edit: action.edit };
21 let id = label.id;
22 let change = SourceChange::source_file_edit(label.label, file_edit).with_cursor_opt(
23 action.cursor_position.map(|offset| FilePosition { offset, file_id }),
24 );
25 Assist { id, change }
26 })
27 .collect()
28}
diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs
deleted file mode 100644
index d559dc4d0..000000000
--- a/crates/ra_ide_api/src/call_info.rs
+++ /dev/null
@@ -1,592 +0,0 @@
1//! FIXME: write short doc here
2
3use ra_db::SourceDatabase;
4use ra_syntax::{
5 algo::ancestors_at_offset,
6 ast::{self, ArgListOwner},
7 match_ast, AstNode, SyntaxNode, TextUnit,
8};
9use test_utils::tested_by;
10
11use crate::{db::RootDatabase, CallInfo, FilePosition, FunctionSignature};
12
13/// Computes parameter information for the given call expression.
14pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<CallInfo> {
15 let parse = db.parse(position.file_id);
16 let syntax = parse.tree().syntax().clone();
17
18 // Find the calling expression and it's NameRef
19 let calling_node = FnCallNode::with_node(&syntax, position.offset)?;
20 let name_ref = calling_node.name_ref()?;
21 let name_ref = hir::Source::new(position.file_id.into(), name_ref.syntax());
22
23 let analyzer = hir::SourceAnalyzer::new(db, name_ref, None);
24 let (mut call_info, has_self) = match &calling_node {
25 FnCallNode::CallExpr(expr) => {
26 //FIXME: Type::as_callable is broken
27 let callable_def = analyzer.type_of(db, &expr.expr()?)?.as_callable()?;
28 match callable_def {
29 hir::CallableDef::FunctionId(it) => {
30 let fn_def = it.into();
31 (CallInfo::with_fn(db, fn_def), fn_def.has_self_param(db))
32 }
33 hir::CallableDef::StructId(it) => (CallInfo::with_struct(db, it.into())?, false),
34 hir::CallableDef::EnumVariantId(it) => {
35 (CallInfo::with_enum_variant(db, it.into())?, false)
36 }
37 }
38 }
39 FnCallNode::MethodCallExpr(expr) => {
40 let function = analyzer.resolve_method_call(&expr)?;
41 (CallInfo::with_fn(db, function), function.has_self_param(db))
42 }
43 FnCallNode::MacroCallExpr(expr) => {
44 let macro_def = analyzer.resolve_macro_call(db, name_ref.with_value(&expr))?;
45 (CallInfo::with_macro(db, macro_def)?, false)
46 }
47 };
48
49 // If we have a calling expression let's find which argument we are on
50 let num_params = call_info.parameters().len();
51
52 if num_params == 1 {
53 if !has_self {
54 call_info.active_parameter = Some(0);
55 }
56 } else if num_params > 1 {
57 // Count how many parameters into the call we are.
58 if let Some(arg_list) = calling_node.arg_list() {
59 // Number of arguments specified at the call site
60 let num_args_at_callsite = arg_list.args().count();
61
62 let arg_list_range = arg_list.syntax().text_range();
63 if !arg_list_range.contains_inclusive(position.offset) {
64 tested_by!(call_info_bad_offset);
65 return None;
66 }
67
68 let mut param = std::cmp::min(
69 num_args_at_callsite,
70 arg_list
71 .args()
72 .take_while(|arg| arg.syntax().text_range().end() < position.offset)
73 .count(),
74 );
75
76 // If we are in a method account for `self`
77 if has_self {
78 param += 1;
79 }
80
81 call_info.active_parameter = Some(param);
82 }
83 }
84
85 Some(call_info)
86}
87
88#[derive(Debug)]
89enum FnCallNode {
90 CallExpr(ast::CallExpr),
91 MethodCallExpr(ast::MethodCallExpr),
92 MacroCallExpr(ast::MacroCall),
93}
94
95impl FnCallNode {
96 fn with_node(syntax: &SyntaxNode, offset: TextUnit) -> Option<FnCallNode> {
97 ancestors_at_offset(syntax, offset).find_map(|node| {
98 match_ast! {
99 match node {
100 ast::CallExpr(it) => { Some(FnCallNode::CallExpr(it)) },
101 ast::MethodCallExpr(it) => { Some(FnCallNode::MethodCallExpr(it)) },
102 ast::MacroCall(it) => { Some(FnCallNode::MacroCallExpr(it)) },
103 _ => { None },
104 }
105 }
106 })
107 }
108
109 fn name_ref(&self) -> Option<ast::NameRef> {
110 match self {
111 FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()? {
112 ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?,
113 _ => return None,
114 }),
115
116 FnCallNode::MethodCallExpr(call_expr) => {
117 call_expr.syntax().children().filter_map(ast::NameRef::cast).nth(0)
118 }
119
120 FnCallNode::MacroCallExpr(call_expr) => call_expr.path()?.segment()?.name_ref(),
121 }
122 }
123
124 fn arg_list(&self) -> Option<ast::ArgList> {
125 match self {
126 FnCallNode::CallExpr(expr) => expr.arg_list(),
127 FnCallNode::MethodCallExpr(expr) => expr.arg_list(),
128 FnCallNode::MacroCallExpr(_) => None,
129 }
130 }
131}
132
133impl CallInfo {
134 fn with_fn(db: &RootDatabase, function: hir::Function) -> Self {
135 let signature = FunctionSignature::from_hir(db, function);
136
137 CallInfo { signature, active_parameter: None }
138 }
139
140 fn with_struct(db: &RootDatabase, st: hir::Struct) -> Option<Self> {
141 let signature = FunctionSignature::from_struct(db, st)?;
142
143 Some(CallInfo { signature, active_parameter: None })
144 }
145
146 fn with_enum_variant(db: &RootDatabase, variant: hir::EnumVariant) -> Option<Self> {
147 let signature = FunctionSignature::from_enum_variant(db, variant)?;
148
149 Some(CallInfo { signature, active_parameter: None })
150 }
151
152 fn with_macro(db: &RootDatabase, macro_def: hir::MacroDef) -> Option<Self> {
153 let signature = FunctionSignature::from_macro(db, macro_def)?;
154
155 Some(CallInfo { signature, active_parameter: None })
156 }
157
158 fn parameters(&self) -> &[String] {
159 &self.signature.parameters
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use test_utils::covers;
166
167 use crate::mock_analysis::single_file_with_position;
168
169 use super::*;
170
171 // These are only used when testing
172 impl CallInfo {
173 fn doc(&self) -> Option<hir::Documentation> {
174 self.signature.doc.clone()
175 }
176
177 fn label(&self) -> String {
178 self.signature.to_string()
179 }
180 }
181
182 fn call_info(text: &str) -> CallInfo {
183 let (analysis, position) = single_file_with_position(text);
184 analysis.call_info(position).unwrap().unwrap()
185 }
186
187 #[test]
188 fn test_fn_signature_two_args_firstx() {
189 let info = call_info(
190 r#"fn foo(x: u32, y: u32) -> u32 {x + y}
191fn bar() { foo(<|>3, ); }"#,
192 );
193
194 assert_eq!(info.parameters(), ["x: u32", "y: u32"]);
195 assert_eq!(info.active_parameter, Some(0));
196 }
197
198 #[test]
199 fn test_fn_signature_two_args_second() {
200 let info = call_info(
201 r#"fn foo(x: u32, y: u32) -> u32 {x + y}
202fn bar() { foo(3, <|>); }"#,
203 );
204
205 assert_eq!(info.parameters(), ["x: u32", "y: u32"]);
206 assert_eq!(info.active_parameter, Some(1));
207 }
208
209 #[test]
210 fn test_fn_signature_two_args_empty() {
211 let info = call_info(
212 r#"fn foo(x: u32, y: u32) -> u32 {x + y}
213fn bar() { foo(<|>); }"#,
214 );
215
216 assert_eq!(info.parameters(), ["x: u32", "y: u32"]);
217 assert_eq!(info.active_parameter, Some(0));
218 }
219
220 #[test]
221 fn test_fn_signature_two_args_first_generics() {
222 let info = call_info(
223 r#"fn foo<T, U: Copy + Display>(x: T, y: U) -> u32 where T: Copy + Display, U: Debug {x + y}
224fn bar() { foo(<|>3, ); }"#,
225 );
226
227 assert_eq!(info.parameters(), ["x: T", "y: U"]);
228 assert_eq!(
229 info.label(),
230 r#"
231fn foo<T, U: Copy + Display>(x: T, y: U) -> u32
232where T: Copy + Display,
233 U: Debug
234 "#
235 .trim()
236 );
237 assert_eq!(info.active_parameter, Some(0));
238 }
239
240 #[test]
241 fn test_fn_signature_no_params() {
242 let info = call_info(
243 r#"fn foo<T>() -> T where T: Copy + Display {}
244fn bar() { foo(<|>); }"#,
245 );
246
247 assert!(info.parameters().is_empty());
248 assert_eq!(
249 info.label(),
250 r#"
251fn foo<T>() -> T
252where T: Copy + Display
253 "#
254 .trim()
255 );
256 assert!(info.active_parameter.is_none());
257 }
258
259 #[test]
260 fn test_fn_signature_for_impl() {
261 let info = call_info(
262 r#"struct F; impl F { pub fn new() { F{}} }
263fn bar() {let _ : F = F::new(<|>);}"#,
264 );
265
266 assert!(info.parameters().is_empty());
267 assert_eq!(info.active_parameter, None);
268 }
269
270 #[test]
271 fn test_fn_signature_for_method_self() {
272 let info = call_info(
273 r#"struct F;
274impl F {
275 pub fn new() -> F{
276 F{}
277 }
278
279 pub fn do_it(&self) {}
280}
281
282fn bar() {
283 let f : F = F::new();
284 f.do_it(<|>);
285}"#,
286 );
287
288 assert_eq!(info.parameters(), ["&self"]);
289 assert_eq!(info.active_parameter, None);
290 }
291
292 #[test]
293 fn test_fn_signature_for_method_with_arg() {
294 let info = call_info(
295 r#"struct F;
296impl F {
297 pub fn new() -> F{
298 F{}
299 }
300
301 pub fn do_it(&self, x: i32) {}
302}
303
304fn bar() {
305 let f : F = F::new();
306 f.do_it(<|>);
307}"#,
308 );
309
310 assert_eq!(info.parameters(), ["&self", "x: i32"]);
311 assert_eq!(info.active_parameter, Some(1));
312 }
313
314 #[test]
315 fn test_fn_signature_with_docs_simple() {
316 let info = call_info(
317 r#"
318/// test
319// non-doc-comment
320fn foo(j: u32) -> u32 {
321 j
322}
323
324fn bar() {
325 let _ = foo(<|>);
326}
327"#,
328 );
329
330 assert_eq!(info.parameters(), ["j: u32"]);
331 assert_eq!(info.active_parameter, Some(0));
332 assert_eq!(info.label(), "fn foo(j: u32) -> u32");
333 assert_eq!(info.doc().map(|it| it.into()), Some("test".to_string()));
334 }
335
336 #[test]
337 fn test_fn_signature_with_docs() {
338 let info = call_info(
339 r#"
340/// Adds one to the number given.
341///
342/// # Examples
343///
344/// ```
345/// let five = 5;
346///
347/// assert_eq!(6, my_crate::add_one(5));
348/// ```
349pub fn add_one(x: i32) -> i32 {
350 x + 1
351}
352
353pub fn do() {
354 add_one(<|>
355}"#,
356 );
357
358 assert_eq!(info.parameters(), ["x: i32"]);
359 assert_eq!(info.active_parameter, Some(0));
360 assert_eq!(info.label(), "pub fn add_one(x: i32) -> i32");
361 assert_eq!(
362 info.doc().map(|it| it.into()),
363 Some(
364 r#"Adds one to the number given.
365
366# Examples
367
368```
369let five = 5;
370
371assert_eq!(6, my_crate::add_one(5));
372```"#
373 .to_string()
374 )
375 );
376 }
377
378 #[test]
379 fn test_fn_signature_with_docs_impl() {
380 let info = call_info(
381 r#"
382struct addr;
383impl addr {
384 /// Adds one to the number given.
385 ///
386 /// # Examples
387 ///
388 /// ```
389 /// let five = 5;
390 ///
391 /// assert_eq!(6, my_crate::add_one(5));
392 /// ```
393 pub fn add_one(x: i32) -> i32 {
394 x + 1
395 }
396}
397
398pub fn do_it() {
399 addr {};
400 addr::add_one(<|>);
401}"#,
402 );
403
404 assert_eq!(info.parameters(), ["x: i32"]);
405 assert_eq!(info.active_parameter, Some(0));
406 assert_eq!(info.label(), "pub fn add_one(x: i32) -> i32");
407 assert_eq!(
408 info.doc().map(|it| it.into()),
409 Some(
410 r#"Adds one to the number given.
411
412# Examples
413
414```
415let five = 5;
416
417assert_eq!(6, my_crate::add_one(5));
418```"#
419 .to_string()
420 )
421 );
422 }
423
424 #[test]
425 fn test_fn_signature_with_docs_from_actix() {
426 let info = call_info(
427 r#"
428struct WriteHandler<E>;
429
430impl<E> WriteHandler<E> {
431 /// Method is called when writer emits error.
432 ///
433 /// If this method returns `ErrorAction::Continue` writer processing
434 /// continues otherwise stream processing stops.
435 fn error(&mut self, err: E, ctx: &mut Self::Context) -> Running {
436 Running::Stop
437 }
438
439 /// Method is called when writer finishes.
440 ///
441 /// By default this method stops actor's `Context`.
442 fn finished(&mut self, ctx: &mut Self::Context) {
443 ctx.stop()
444 }
445}
446
447pub fn foo(mut r: WriteHandler<()>) {
448 r.finished(<|>);
449}
450
451"#,
452 );
453
454 assert_eq!(info.label(), "fn finished(&mut self, ctx: &mut Self::Context)".to_string());
455 assert_eq!(info.parameters(), ["&mut self", "ctx: &mut Self::Context"]);
456 assert_eq!(info.active_parameter, Some(1));
457 assert_eq!(
458 info.doc().map(|it| it.into()),
459 Some(
460 r#"Method is called when writer finishes.
461
462By default this method stops actor's `Context`."#
463 .to_string()
464 )
465 );
466 }
467
468 #[test]
469 fn call_info_bad_offset() {
470 covers!(call_info_bad_offset);
471 let (analysis, position) = single_file_with_position(
472 r#"fn foo(x: u32, y: u32) -> u32 {x + y}
473 fn bar() { foo <|> (3, ); }"#,
474 );
475 let call_info = analysis.call_info(position).unwrap();
476 assert!(call_info.is_none());
477 }
478
479 #[test]
480 fn test_nested_method_in_lamba() {
481 let info = call_info(
482 r#"struct Foo;
483
484impl Foo {
485 fn bar(&self, _: u32) { }
486}
487
488fn bar(_: u32) { }
489
490fn main() {
491 let foo = Foo;
492 std::thread::spawn(move || foo.bar(<|>));
493}"#,
494 );
495
496 assert_eq!(info.parameters(), ["&self", "_: u32"]);
497 assert_eq!(info.active_parameter, Some(1));
498 assert_eq!(info.label(), "fn bar(&self, _: u32)");
499 }
500
501 #[test]
502 fn works_for_tuple_structs() {
503 let info = call_info(
504 r#"
505/// A cool tuple struct
506struct TS(u32, i32);
507fn main() {
508 let s = TS(0, <|>);
509}"#,
510 );
511
512 assert_eq!(info.label(), "struct TS(u32, i32) -> TS");
513 assert_eq!(info.doc().map(|it| it.into()), Some("A cool tuple struct".to_string()));
514 assert_eq!(info.active_parameter, Some(1));
515 }
516
517 #[test]
518 #[should_panic]
519 fn cant_call_named_structs() {
520 let _ = call_info(
521 r#"
522struct TS { x: u32, y: i32 }
523fn main() {
524 let s = TS(<|>);
525}"#,
526 );
527 }
528
529 #[test]
530 fn works_for_enum_variants() {
531 let info = call_info(
532 r#"
533enum E {
534 /// A Variant
535 A(i32),
536 /// Another
537 B,
538 /// And C
539 C { a: i32, b: i32 }
540}
541
542fn main() {
543 let a = E::A(<|>);
544}
545 "#,
546 );
547
548 assert_eq!(info.label(), "E::A(0: i32)");
549 assert_eq!(info.doc().map(|it| it.into()), Some("A Variant".to_string()));
550 assert_eq!(info.active_parameter, Some(0));
551 }
552
553 #[test]
554 #[should_panic]
555 fn cant_call_enum_records() {
556 let _ = call_info(
557 r#"
558enum E {
559 /// A Variant
560 A(i32),
561 /// Another
562 B,
563 /// And C
564 C { a: i32, b: i32 }
565}
566
567fn main() {
568 let a = E::C(<|>);
569}
570 "#,
571 );
572 }
573
574 #[test]
575 fn fn_signature_for_macro() {
576 let info = call_info(
577 r#"
578/// empty macro
579macro_rules! foo {
580 () => {}
581}
582
583fn f() {
584 foo!(<|>);
585}
586 "#,
587 );
588
589 assert_eq!(info.label(), "foo!()");
590 assert_eq!(info.doc().map(|it| it.into()), Some("empty macro".to_string()));
591 }
592}
diff --git a/crates/ra_ide_api/src/change.rs b/crates/ra_ide_api/src/change.rs
deleted file mode 100644
index 4a76d1dd8..000000000
--- a/crates/ra_ide_api/src/change.rs
+++ /dev/null
@@ -1,354 +0,0 @@
1//! FIXME: write short doc here
2
3use std::{fmt, sync::Arc, time};
4
5use ra_db::{
6 salsa::{Database, Durability, SweepStrategy},
7 CrateGraph, CrateId, FileId, RelativePathBuf, SourceDatabase, SourceDatabaseExt, SourceRoot,
8 SourceRootId,
9};
10use ra_prof::{memory_usage, profile, Bytes};
11use ra_syntax::SourceFile;
12#[cfg(not(feature = "wasm"))]
13use rayon::prelude::*;
14use rustc_hash::FxHashMap;
15
16use crate::{
17 db::{DebugData, RootDatabase},
18 symbol_index::{SymbolIndex, SymbolsDatabase},
19};
20
21#[derive(Default)]
22pub struct AnalysisChange {
23 new_roots: Vec<(SourceRootId, bool)>,
24 roots_changed: FxHashMap<SourceRootId, RootChange>,
25 files_changed: Vec<(FileId, Arc<String>)>,
26 libraries_added: Vec<LibraryData>,
27 crate_graph: Option<CrateGraph>,
28 debug_data: DebugData,
29}
30
31impl fmt::Debug for AnalysisChange {
32 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
33 let mut d = fmt.debug_struct("AnalysisChange");
34 if !self.new_roots.is_empty() {
35 d.field("new_roots", &self.new_roots);
36 }
37 if !self.roots_changed.is_empty() {
38 d.field("roots_changed", &self.roots_changed);
39 }
40 if !self.files_changed.is_empty() {
41 d.field("files_changed", &self.files_changed.len());
42 }
43 if !self.libraries_added.is_empty() {
44 d.field("libraries_added", &self.libraries_added.len());
45 }
46 if !self.crate_graph.is_none() {
47 d.field("crate_graph", &self.crate_graph);
48 }
49 d.finish()
50 }
51}
52
53impl AnalysisChange {
54 pub fn new() -> AnalysisChange {
55 AnalysisChange::default()
56 }
57
58 pub fn add_root(&mut self, root_id: SourceRootId, is_local: bool) {
59 self.new_roots.push((root_id, is_local));
60 }
61
62 pub fn add_file(
63 &mut self,
64 root_id: SourceRootId,
65 file_id: FileId,
66 path: RelativePathBuf,
67 text: Arc<String>,
68 ) {
69 let file = AddFile { file_id, path, text };
70 self.roots_changed.entry(root_id).or_default().added.push(file);
71 }
72
73 pub fn change_file(&mut self, file_id: FileId, new_text: Arc<String>) {
74 self.files_changed.push((file_id, new_text))
75 }
76
77 pub fn remove_file(&mut self, root_id: SourceRootId, file_id: FileId, path: RelativePathBuf) {
78 let file = RemoveFile { file_id, path };
79 self.roots_changed.entry(root_id).or_default().removed.push(file);
80 }
81
82 pub fn add_library(&mut self, data: LibraryData) {
83 self.libraries_added.push(data)
84 }
85
86 pub fn set_crate_graph(&mut self, graph: CrateGraph) {
87 self.crate_graph = Some(graph);
88 }
89
90 pub fn set_debug_crate_name(&mut self, crate_id: CrateId, name: String) {
91 self.debug_data.crate_names.insert(crate_id, name);
92 }
93
94 pub fn set_debug_root_path(&mut self, source_root_id: SourceRootId, path: String) {
95 self.debug_data.root_paths.insert(source_root_id, path);
96 }
97}
98
99#[derive(Debug)]
100struct AddFile {
101 file_id: FileId,
102 path: RelativePathBuf,
103 text: Arc<String>,
104}
105
106#[derive(Debug)]
107struct RemoveFile {
108 file_id: FileId,
109 path: RelativePathBuf,
110}
111
112#[derive(Default)]
113struct RootChange {
114 added: Vec<AddFile>,
115 removed: Vec<RemoveFile>,
116}
117
118impl fmt::Debug for RootChange {
119 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
120 fmt.debug_struct("AnalysisChange")
121 .field("added", &self.added.len())
122 .field("removed", &self.removed.len())
123 .finish()
124 }
125}
126
127pub struct LibraryData {
128 root_id: SourceRootId,
129 root_change: RootChange,
130 symbol_index: SymbolIndex,
131}
132
133impl fmt::Debug for LibraryData {
134 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135 f.debug_struct("LibraryData")
136 .field("root_id", &self.root_id)
137 .field("root_change", &self.root_change)
138 .field("n_symbols", &self.symbol_index.len())
139 .finish()
140 }
141}
142
143impl LibraryData {
144 pub fn prepare(
145 root_id: SourceRootId,
146 files: Vec<(FileId, RelativePathBuf, Arc<String>)>,
147 ) -> LibraryData {
148 #[cfg(not(feature = "wasm"))]
149 let iter = files.par_iter();
150 #[cfg(feature = "wasm")]
151 let iter = files.iter();
152
153 let symbol_index = SymbolIndex::for_files(iter.map(|(file_id, _, text)| {
154 let parse = SourceFile::parse(text);
155 (*file_id, parse)
156 }));
157 let mut root_change = RootChange::default();
158 root_change.added = files
159 .into_iter()
160 .map(|(file_id, path, text)| AddFile { file_id, path, text })
161 .collect();
162 LibraryData { root_id, root_change, symbol_index }
163 }
164}
165
166const GC_COOLDOWN: time::Duration = time::Duration::from_millis(100);
167
168impl RootDatabase {
169 pub(crate) fn apply_change(&mut self, change: AnalysisChange) {
170 let _p = profile("RootDatabase::apply_change");
171 log::info!("apply_change {:?}", change);
172 {
173 let _p = profile("RootDatabase::apply_change/cancellation");
174 self.salsa_runtime_mut().synthetic_write(Durability::LOW);
175 }
176 if !change.new_roots.is_empty() {
177 let mut local_roots = Vec::clone(&self.local_roots());
178 for (root_id, is_local) in change.new_roots {
179 let root = if is_local { SourceRoot::new() } else { SourceRoot::new_library() };
180 let durability = durability(&root);
181 self.set_source_root_with_durability(root_id, Arc::new(root), durability);
182 if is_local {
183 local_roots.push(root_id);
184 }
185 }
186 self.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
187 }
188
189 for (root_id, root_change) in change.roots_changed {
190 self.apply_root_change(root_id, root_change);
191 }
192 for (file_id, text) in change.files_changed {
193 let source_root_id = self.file_source_root(file_id);
194 let source_root = self.source_root(source_root_id);
195 let durability = durability(&source_root);
196 self.set_file_text_with_durability(file_id, text, durability)
197 }
198 if !change.libraries_added.is_empty() {
199 let mut libraries = Vec::clone(&self.library_roots());
200 for library in change.libraries_added {
201 libraries.push(library.root_id);
202 self.set_source_root_with_durability(
203 library.root_id,
204 Default::default(),
205 Durability::HIGH,
206 );
207 self.set_library_symbols_with_durability(
208 library.root_id,
209 Arc::new(library.symbol_index),
210 Durability::HIGH,
211 );
212 self.apply_root_change(library.root_id, library.root_change);
213 }
214 self.set_library_roots_with_durability(Arc::new(libraries), Durability::HIGH);
215 }
216 if let Some(crate_graph) = change.crate_graph {
217 self.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH)
218 }
219
220 Arc::make_mut(&mut self.debug_data).merge(change.debug_data)
221 }
222
223 fn apply_root_change(&mut self, root_id: SourceRootId, root_change: RootChange) {
224 let mut source_root = SourceRoot::clone(&self.source_root(root_id));
225 let durability = durability(&source_root);
226 for add_file in root_change.added {
227 self.set_file_text_with_durability(add_file.file_id, add_file.text, durability);
228 self.set_file_relative_path_with_durability(
229 add_file.file_id,
230 add_file.path.clone(),
231 durability,
232 );
233 self.set_file_source_root_with_durability(add_file.file_id, root_id, durability);
234 source_root.insert_file(add_file.path, add_file.file_id);
235 }
236 for remove_file in root_change.removed {
237 self.set_file_text_with_durability(remove_file.file_id, Default::default(), durability);
238 source_root.remove_file(&remove_file.path);
239 }
240 self.set_source_root_with_durability(root_id, Arc::new(source_root), durability);
241 }
242
243 pub(crate) fn maybe_collect_garbage(&mut self) {
244 if cfg!(feature = "wasm") {
245 return;
246 }
247
248 if self.last_gc_check.elapsed() > GC_COOLDOWN {
249 self.last_gc_check = crate::wasm_shims::Instant::now();
250 }
251 }
252
253 pub(crate) fn collect_garbage(&mut self) {
254 if cfg!(feature = "wasm") {
255 return;
256 }
257
258 let _p = profile("RootDatabase::collect_garbage");
259 self.last_gc = crate::wasm_shims::Instant::now();
260
261 let sweep = SweepStrategy::default().discard_values().sweep_all_revisions();
262
263 self.query(ra_db::ParseQuery).sweep(sweep);
264 self.query(hir::db::ParseMacroQuery).sweep(sweep);
265
266 // Macros do take significant space, but less then the syntax trees
267 // self.query(hir::db::MacroDefQuery).sweep(sweep);
268 // self.query(hir::db::MacroArgQuery).sweep(sweep);
269 // self.query(hir::db::MacroExpandQuery).sweep(sweep);
270
271 self.query(hir::db::AstIdMapQuery).sweep(sweep);
272
273 self.query(hir::db::RawItemsWithSourceMapQuery).sweep(sweep);
274 self.query(hir::db::BodyWithSourceMapQuery).sweep(sweep);
275
276 self.query(hir::db::ExprScopesQuery).sweep(sweep);
277 self.query(hir::db::InferQuery).sweep(sweep);
278 self.query(hir::db::BodyQuery).sweep(sweep);
279 }
280
281 pub(crate) fn per_query_memory_usage(&mut self) -> Vec<(String, Bytes)> {
282 let mut acc: Vec<(String, Bytes)> = vec![];
283 let sweep = SweepStrategy::default().discard_values().sweep_all_revisions();
284 macro_rules! sweep_each_query {
285 ($($q:path)*) => {$(
286 let before = memory_usage().allocated;
287 self.query($q).sweep(sweep);
288 let after = memory_usage().allocated;
289 let q: $q = Default::default();
290 let name = format!("{:?}", q);
291 acc.push((name, before - after));
292
293 let before = memory_usage().allocated;
294 self.query($q).sweep(sweep.discard_everything());
295 let after = memory_usage().allocated;
296 let q: $q = Default::default();
297 let name = format!("{:?} (deps)", q);
298 acc.push((name, before - after));
299 )*}
300 }
301 sweep_each_query![
302 ra_db::ParseQuery
303 ra_db::SourceRootCratesQuery
304 hir::db::AstIdMapQuery
305 hir::db::ParseMacroQuery
306 hir::db::MacroDefQuery
307 hir::db::MacroArgQuery
308 hir::db::MacroExpandQuery
309 hir::db::StructDataQuery
310 hir::db::EnumDataQuery
311 hir::db::TraitDataQuery
312 hir::db::RawItemsWithSourceMapQuery
313 hir::db::RawItemsQuery
314 hir::db::CrateDefMapQuery
315 hir::db::GenericParamsQuery
316 hir::db::FunctionDataQuery
317 hir::db::TypeAliasDataQuery
318 hir::db::ConstDataQuery
319 hir::db::StaticDataQuery
320 hir::db::ModuleLangItemsQuery
321 hir::db::CrateLangItemsQuery
322 hir::db::LangItemQuery
323 hir::db::DocumentationQuery
324 hir::db::ExprScopesQuery
325 hir::db::InferQuery
326 hir::db::TyQuery
327 hir::db::ValueTyQuery
328 hir::db::FieldTypesQuery
329 hir::db::CallableItemSignatureQuery
330 hir::db::GenericPredicatesQuery
331 hir::db::GenericDefaultsQuery
332 hir::db::BodyWithSourceMapQuery
333 hir::db::BodyQuery
334 hir::db::ImplsInCrateQuery
335 hir::db::ImplsForTraitQuery
336 hir::db::AssociatedTyDataQuery
337 hir::db::TraitDatumQuery
338 hir::db::StructDatumQuery
339 hir::db::ImplDatumQuery
340 hir::db::ImplDataQuery
341 hir::db::TraitSolveQuery
342 ];
343 acc.sort_by_key(|it| std::cmp::Reverse(it.1));
344 acc
345 }
346}
347
348fn durability(source_root: &SourceRoot) -> Durability {
349 if source_root.is_library {
350 Durability::HIGH
351 } else {
352 Durability::LOW
353 }
354}
diff --git a/crates/ra_ide_api/src/completion.rs b/crates/ra_ide_api/src/completion.rs
deleted file mode 100644
index abe1f36ce..000000000
--- a/crates/ra_ide_api/src/completion.rs
+++ /dev/null
@@ -1,77 +0,0 @@
1//! FIXME: write short doc here
2
3mod completion_item;
4mod completion_context;
5mod presentation;
6
7mod complete_dot;
8mod complete_record_literal;
9mod complete_record_pattern;
10mod complete_pattern;
11mod complete_fn_param;
12mod complete_keyword;
13mod complete_snippet;
14mod complete_path;
15mod complete_scope;
16mod complete_postfix;
17mod complete_macro_in_item_position;
18
19use ra_db::SourceDatabase;
20
21#[cfg(test)]
22use crate::completion::completion_item::do_completion;
23use crate::{
24 completion::{
25 completion_context::CompletionContext,
26 completion_item::{CompletionKind, Completions},
27 },
28 db, FilePosition,
29};
30
31pub use crate::completion::completion_item::{
32 CompletionItem, CompletionItemKind, InsertTextFormat,
33};
34
35/// Main entry point for completion. We run completion as a two-phase process.
36///
37/// First, we look at the position and collect a so-called `CompletionContext.
38/// This is a somewhat messy process, because, during completion, syntax tree is
39/// incomplete and can look really weird.
40///
41/// Once the context is collected, we run a series of completion routines which
42/// look at the context and produce completion items. One subtlety about this
43/// phase is that completion engine should not filter by the substring which is
44/// already present, it should give all possible variants for the identifier at
45/// the caret. In other words, for
46///
47/// ```no-run
48/// fn f() {
49/// let foo = 92;
50/// let _ = bar<|>
51/// }
52/// ```
53///
54/// `foo` *should* be present among the completion variants. Filtering by
55/// identifier prefix/fuzzy match should be done higher in the stack, together
56/// with ordering of completions (currently this is done by the client).
57pub(crate) fn completions(db: &db::RootDatabase, position: FilePosition) -> Option<Completions> {
58 let original_parse = db.parse(position.file_id);
59 let ctx = CompletionContext::new(db, &original_parse, position)?;
60
61 let mut acc = Completions::default();
62
63 complete_fn_param::complete_fn_param(&mut acc, &ctx);
64 complete_keyword::complete_expr_keyword(&mut acc, &ctx);
65 complete_keyword::complete_use_tree_keyword(&mut acc, &ctx);
66 complete_snippet::complete_expr_snippet(&mut acc, &ctx);
67 complete_snippet::complete_item_snippet(&mut acc, &ctx);
68 complete_path::complete_path(&mut acc, &ctx);
69 complete_scope::complete_scope(&mut acc, &ctx);
70 complete_dot::complete_dot(&mut acc, &ctx);
71 complete_record_literal::complete_record_literal(&mut acc, &ctx);
72 complete_record_pattern::complete_record_pattern(&mut acc, &ctx);
73 complete_pattern::complete_pattern(&mut acc, &ctx);
74 complete_postfix::complete_postfix(&mut acc, &ctx);
75 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
76 Some(acc)
77}
diff --git a/crates/ra_ide_api/src/completion/complete_dot.rs b/crates/ra_ide_api/src/completion/complete_dot.rs
deleted file mode 100644
index b6fe48627..000000000
--- a/crates/ra_ide_api/src/completion/complete_dot.rs
+++ /dev/null
@@ -1,456 +0,0 @@
1//! FIXME: write short doc here
2
3use hir::Type;
4
5use crate::completion::completion_item::CompletionKind;
6use crate::{
7 completion::{completion_context::CompletionContext, completion_item::Completions},
8 CompletionItem,
9};
10use rustc_hash::FxHashSet;
11
12/// Complete dot accesses, i.e. fields or methods (and .await syntax).
13pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
14 let dot_receiver = match &ctx.dot_receiver {
15 Some(expr) => expr,
16 _ => return,
17 };
18
19 let receiver_ty = match ctx.analyzer.type_of(ctx.db, &dot_receiver) {
20 Some(ty) => ty,
21 _ => return,
22 };
23
24 if !ctx.is_call {
25 complete_fields(acc, ctx, &receiver_ty);
26 }
27 complete_methods(acc, ctx, &receiver_ty);
28
29 // Suggest .await syntax for types that implement Future trait
30 if ctx.analyzer.impls_future(ctx.db, receiver_ty.into_ty()) {
31 CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await")
32 .detail("expr.await")
33 .insert_text("await")
34 .add_to(acc);
35 }
36}
37
38fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
39 for receiver in receiver.autoderef(ctx.db) {
40 for (field, ty) in receiver.fields(ctx.db) {
41 acc.add_field(ctx, field, &ty);
42 }
43 for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() {
44 acc.add_tuple_field(ctx, i, &ty);
45 }
46 }
47}
48
49fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
50 let mut seen_methods = FxHashSet::default();
51 ctx.analyzer.iterate_method_candidates(ctx.db, receiver, None, |_ty, func| {
52 if func.has_self_param(ctx.db) && seen_methods.insert(func.name(ctx.db)) {
53 acc.add_function(ctx, func);
54 }
55 None::<()>
56 });
57}
58
59#[cfg(test)]
60mod tests {
61 use crate::completion::{do_completion, CompletionItem, CompletionKind};
62 use insta::assert_debug_snapshot;
63
64 fn do_ref_completion(code: &str) -> Vec<CompletionItem> {
65 do_completion(code, CompletionKind::Reference)
66 }
67
68 #[test]
69 fn test_struct_field_completion() {
70 assert_debug_snapshot!(
71 do_ref_completion(
72 r"
73 struct A { the_field: u32 }
74 fn foo(a: A) {
75 a.<|>
76 }
77 ",
78 ),
79 @r###"
80 [
81 CompletionItem {
82 label: "the_field",
83 source_range: [94; 94),
84 delete: [94; 94),
85 insert: "the_field",
86 kind: Field,
87 detail: "u32",
88 },
89 ]
90 "###
91 );
92 }
93
94 #[test]
95 fn test_struct_field_completion_self() {
96 assert_debug_snapshot!(
97 do_ref_completion(
98 r"
99 struct A {
100 /// This is the_field
101 the_field: (u32,)
102 }
103 impl A {
104 fn foo(self) {
105 self.<|>
106 }
107 }
108 ",
109 ),
110 @r###"
111 [
112 CompletionItem {
113 label: "foo()",
114 source_range: [187; 187),
115 delete: [187; 187),
116 insert: "foo()$0",
117 kind: Method,
118 lookup: "foo",
119 detail: "fn foo(self)",
120 },
121 CompletionItem {
122 label: "the_field",
123 source_range: [187; 187),
124 delete: [187; 187),
125 insert: "the_field",
126 kind: Field,
127 detail: "(u32,)",
128 documentation: Documentation(
129 "This is the_field",
130 ),
131 },
132 ]
133 "###
134 );
135 }
136
137 #[test]
138 fn test_struct_field_completion_autoderef() {
139 assert_debug_snapshot!(
140 do_ref_completion(
141 r"
142 struct A { the_field: (u32, i32) }
143 impl A {
144 fn foo(&self) {
145 self.<|>
146 }
147 }
148 ",
149 ),
150 @r###"
151 [
152 CompletionItem {
153 label: "foo()",
154 source_range: [126; 126),
155 delete: [126; 126),
156 insert: "foo()$0",
157 kind: Method,
158 lookup: "foo",
159 detail: "fn foo(&self)",
160 },
161 CompletionItem {
162 label: "the_field",
163 source_range: [126; 126),
164 delete: [126; 126),
165 insert: "the_field",
166 kind: Field,
167 detail: "(u32, i32)",
168 },
169 ]
170 "###
171 );
172 }
173
174 #[test]
175 fn test_no_struct_field_completion_for_method_call() {
176 assert_debug_snapshot!(
177 do_ref_completion(
178 r"
179 struct A { the_field: u32 }
180 fn foo(a: A) {
181 a.<|>()
182 }
183 ",
184 ),
185 @"[]"
186 );
187 }
188
189 #[test]
190 fn test_method_completion() {
191 assert_debug_snapshot!(
192 do_ref_completion(
193 r"
194 struct A {}
195 impl A {
196 fn the_method(&self) {}
197 }
198 fn foo(a: A) {
199 a.<|>
200 }
201 ",
202 ),
203 @r###"
204 [
205 CompletionItem {
206 label: "the_method()",
207 source_range: [144; 144),
208 delete: [144; 144),
209 insert: "the_method()$0",
210 kind: Method,
211 lookup: "the_method",
212 detail: "fn the_method(&self)",
213 },
214 ]
215 "###
216 );
217 }
218
219 #[test]
220 fn test_trait_method_completion() {
221 assert_debug_snapshot!(
222 do_ref_completion(
223 r"
224 struct A {}
225 trait Trait { fn the_method(&self); }
226 impl Trait for A {}
227 fn foo(a: A) {
228 a.<|>
229 }
230 ",
231 ),
232 @r###"
233 [
234 CompletionItem {
235 label: "the_method()",
236 source_range: [151; 151),
237 delete: [151; 151),
238 insert: "the_method()$0",
239 kind: Method,
240 lookup: "the_method",
241 detail: "fn the_method(&self)",
242 },
243 ]
244 "###
245 );
246 }
247
248 #[test]
249 fn test_trait_method_completion_deduplicated() {
250 assert_debug_snapshot!(
251 do_ref_completion(
252 r"
253 struct A {}
254 trait Trait { fn the_method(&self); }
255 impl<T> Trait for T {}
256 fn foo(a: &A) {
257 a.<|>
258 }
259 ",
260 ),
261 @r###"
262 [
263 CompletionItem {
264 label: "the_method()",
265 source_range: [155; 155),
266 delete: [155; 155),
267 insert: "the_method()$0",
268 kind: Method,
269 lookup: "the_method",
270 detail: "fn the_method(&self)",
271 },
272 ]
273 "###
274 );
275 }
276
277 #[test]
278 fn test_no_non_self_method() {
279 assert_debug_snapshot!(
280 do_ref_completion(
281 r"
282 struct A {}
283 impl A {
284 fn the_method() {}
285 }
286 fn foo(a: A) {
287 a.<|>
288 }
289 ",
290 ),
291 @"[]"
292 );
293 }
294
295 #[test]
296 fn test_method_attr_filtering() {
297 assert_debug_snapshot!(
298 do_ref_completion(
299 r"
300 struct A {}
301 impl A {
302 #[inline]
303 fn the_method(&self) {
304 let x = 1;
305 let y = 2;
306 }
307 }
308 fn foo(a: A) {
309 a.<|>
310 }
311 ",
312 ),
313 @r###"
314 [
315 CompletionItem {
316 label: "the_method()",
317 source_range: [249; 249),
318 delete: [249; 249),
319 insert: "the_method()$0",
320 kind: Method,
321 lookup: "the_method",
322 detail: "fn the_method(&self)",
323 },
324 ]
325 "###
326 );
327 }
328
329 #[test]
330 fn test_tuple_field_completion() {
331 assert_debug_snapshot!(
332 do_ref_completion(
333 r"
334 fn foo() {
335 let b = (0, 3.14);
336 b.<|>
337 }
338 ",
339 ),
340 @r###"
341 [
342 CompletionItem {
343 label: "0",
344 source_range: [75; 75),
345 delete: [75; 75),
346 insert: "0",
347 kind: Field,
348 detail: "i32",
349 },
350 CompletionItem {
351 label: "1",
352 source_range: [75; 75),
353 delete: [75; 75),
354 insert: "1",
355 kind: Field,
356 detail: "f64",
357 },
358 ]
359 "###
360 );
361 }
362
363 #[test]
364 fn test_tuple_field_inference() {
365 assert_debug_snapshot!(
366 do_ref_completion(
367 r"
368 pub struct S;
369 impl S {
370 pub fn blah(&self) {}
371 }
372
373 struct T(S);
374
375 impl T {
376 fn foo(&self) {
377 // FIXME: This doesn't work without the trailing `a` as `0.` is a float
378 self.0.a<|>
379 }
380 }
381 ",
382 ),
383 @r###"
384 [
385 CompletionItem {
386 label: "blah()",
387 source_range: [299; 300),
388 delete: [299; 300),
389 insert: "blah()$0",
390 kind: Method,
391 lookup: "blah",
392 detail: "pub fn blah(&self)",
393 },
394 ]
395 "###
396 );
397 }
398
399 #[test]
400 fn test_completion_works_in_consts() {
401 assert_debug_snapshot!(
402 do_ref_completion(
403 r"
404 struct A { the_field: u32 }
405 const X: u32 = {
406 A { the_field: 92 }.<|>
407 };
408 ",
409 ),
410 @r###"
411 [
412 CompletionItem {
413 label: "the_field",
414 source_range: [106; 106),
415 delete: [106; 106),
416 insert: "the_field",
417 kind: Field,
418 detail: "u32",
419 },
420 ]
421 "###
422 );
423 }
424
425 #[test]
426 fn test_completion_await_impls_future() {
427 assert_debug_snapshot!(
428 do_completion(
429 r###"
430 //- /main.rs
431 use std::future::*;
432 struct A {}
433 impl Future for A {}
434 fn foo(a: A) {
435 a.<|>
436 }
437
438 //- /std/lib.rs
439 pub mod future {
440 pub trait Future {}
441 }
442 "###, CompletionKind::Keyword),
443 @r###"
444 [
445 CompletionItem {
446 label: "await",
447 source_range: [74; 74),
448 delete: [74; 74),
449 insert: "await",
450 detail: "expr.await",
451 },
452 ]
453 "###
454 )
455 }
456}
diff --git a/crates/ra_ide_api/src/completion/complete_fn_param.rs b/crates/ra_ide_api/src/completion/complete_fn_param.rs
deleted file mode 100644
index 502458706..000000000
--- a/crates/ra_ide_api/src/completion/complete_fn_param.rs
+++ /dev/null
@@ -1,136 +0,0 @@
1//! FIXME: write short doc here
2
3use ra_syntax::{ast, match_ast, AstNode};
4use rustc_hash::FxHashMap;
5
6use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions};
7
8/// Complete repeated parameters, both name and type. For example, if all
9/// functions in a file have a `spam: &mut Spam` parameter, a completion with
10/// `spam: &mut Spam` insert text/label and `spam` lookup string will be
11/// suggested.
12pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) {
13 if !ctx.is_param {
14 return;
15 }
16
17 let mut params = FxHashMap::default();
18 for node in ctx.token.parent().ancestors() {
19 match_ast! {
20 match node {
21 ast::SourceFile(it) => { process(it, &mut params) },
22 ast::ItemList(it) => { process(it, &mut params) },
23 _ => (),
24 }
25 }
26 }
27 params
28 .into_iter()
29 .filter_map(|(label, (count, param))| {
30 let lookup = param.pat()?.syntax().text().to_string();
31 if count < 2 {
32 None
33 } else {
34 Some((label, lookup))
35 }
36 })
37 .for_each(|(label, lookup)| {
38 CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label)
39 .lookup_by(lookup)
40 .add_to(acc)
41 });
42
43 fn process<N: ast::FnDefOwner>(node: N, params: &mut FxHashMap<String, (u32, ast::Param)>) {
44 node.functions().filter_map(|it| it.param_list()).flat_map(|it| it.params()).for_each(
45 |param| {
46 let text = param.syntax().text().to_string();
47 params.entry(text).or_insert((0, param)).0 += 1;
48 },
49 )
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use crate::completion::{do_completion, CompletionItem, CompletionKind};
56 use insta::assert_debug_snapshot;
57
58 fn do_magic_completion(code: &str) -> Vec<CompletionItem> {
59 do_completion(code, CompletionKind::Magic)
60 }
61
62 #[test]
63 fn test_param_completion_last_param() {
64 assert_debug_snapshot!(
65 do_magic_completion(
66 r"
67 fn foo(file_id: FileId) {}
68 fn bar(file_id: FileId) {}
69 fn baz(file<|>) {}
70 ",
71 ),
72 @r###"
73 [
74 CompletionItem {
75 label: "file_id: FileId",
76 source_range: [110; 114),
77 delete: [110; 114),
78 insert: "file_id: FileId",
79 lookup: "file_id",
80 },
81 ]
82 "###
83 );
84 }
85
86 #[test]
87 fn test_param_completion_nth_param() {
88 assert_debug_snapshot!(
89 do_magic_completion(
90 r"
91 fn foo(file_id: FileId) {}
92 fn bar(file_id: FileId) {}
93 fn baz(file<|>, x: i32) {}
94 ",
95 ),
96 @r###"
97 [
98 CompletionItem {
99 label: "file_id: FileId",
100 source_range: [110; 114),
101 delete: [110; 114),
102 insert: "file_id: FileId",
103 lookup: "file_id",
104 },
105 ]
106 "###
107 );
108 }
109
110 #[test]
111 fn test_param_completion_trait_param() {
112 assert_debug_snapshot!(
113 do_magic_completion(
114 r"
115 pub(crate) trait SourceRoot {
116 pub fn contains(&self, file_id: FileId) -> bool;
117 pub fn module_map(&self) -> &ModuleMap;
118 pub fn lines(&self, file_id: FileId) -> &LineIndex;
119 pub fn syntax(&self, file<|>)
120 }
121 ",
122 ),
123 @r###"
124 [
125 CompletionItem {
126 label: "file_id: FileId",
127 source_range: [289; 293),
128 delete: [289; 293),
129 insert: "file_id: FileId",
130 lookup: "file_id",
131 },
132 ]
133 "###
134 );
135 }
136}
diff --git a/crates/ra_ide_api/src/completion/complete_keyword.rs b/crates/ra_ide_api/src/completion/complete_keyword.rs
deleted file mode 100644
index eb7cd9ac2..000000000
--- a/crates/ra_ide_api/src/completion/complete_keyword.rs
+++ /dev/null
@@ -1,781 +0,0 @@
1//! FIXME: write short doc here
2
3use ra_syntax::{
4 ast::{self, LoopBodyOwner},
5 match_ast, AstNode,
6 SyntaxKind::*,
7 SyntaxToken,
8};
9
10use crate::completion::{
11 CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions,
12};
13
14pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) {
15 // complete keyword "crate" in use stmt
16 let source_range = ctx.source_range();
17 match (ctx.use_item_syntax.as_ref(), ctx.path_prefix.as_ref()) {
18 (Some(_), None) => {
19 CompletionItem::new(CompletionKind::Keyword, source_range, "crate")
20 .kind(CompletionItemKind::Keyword)
21 .insert_text("crate::")
22 .add_to(acc);
23 CompletionItem::new(CompletionKind::Keyword, source_range, "self")
24 .kind(CompletionItemKind::Keyword)
25 .add_to(acc);
26 CompletionItem::new(CompletionKind::Keyword, source_range, "super")
27 .kind(CompletionItemKind::Keyword)
28 .insert_text("super::")
29 .add_to(acc);
30 }
31 (Some(_), Some(_)) => {
32 CompletionItem::new(CompletionKind::Keyword, source_range, "self")
33 .kind(CompletionItemKind::Keyword)
34 .add_to(acc);
35 CompletionItem::new(CompletionKind::Keyword, source_range, "super")
36 .kind(CompletionItemKind::Keyword)
37 .insert_text("super::")
38 .add_to(acc);
39 }
40 _ => {}
41 }
42}
43
44fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem {
45 CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw)
46 .kind(CompletionItemKind::Keyword)
47 .insert_snippet(snippet)
48 .build()
49}
50
51pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
52 if !ctx.is_trivial_path {
53 return;
54 }
55
56 let fn_def = match &ctx.function_syntax {
57 Some(it) => it,
58 None => return,
59 };
60 acc.add(keyword(ctx, "if", "if $0 {}"));
61 acc.add(keyword(ctx, "match", "match $0 {}"));
62 acc.add(keyword(ctx, "while", "while $0 {}"));
63 acc.add(keyword(ctx, "loop", "loop {$0}"));
64
65 if ctx.after_if {
66 acc.add(keyword(ctx, "else", "else {$0}"));
67 acc.add(keyword(ctx, "else if", "else if $0 {}"));
68 }
69 if is_in_loop_body(&ctx.token) {
70 if ctx.can_be_stmt {
71 acc.add(keyword(ctx, "continue", "continue;"));
72 acc.add(keyword(ctx, "break", "break;"));
73 } else {
74 acc.add(keyword(ctx, "continue", "continue"));
75 acc.add(keyword(ctx, "break", "break"));
76 }
77 }
78 acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt));
79}
80
81fn is_in_loop_body(leaf: &SyntaxToken) -> bool {
82 for node in leaf.parent().ancestors() {
83 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
84 break;
85 }
86 let loop_body = match_ast! {
87 match node {
88 ast::ForExpr(it) => { it.loop_body() },
89 ast::WhileExpr(it) => { it.loop_body() },
90 ast::LoopExpr(it) => { it.loop_body() },
91 _ => None,
92 }
93 };
94 if let Some(body) = loop_body {
95 if leaf.text_range().is_subrange(&body.syntax().text_range()) {
96 return true;
97 }
98 }
99 }
100 false
101}
102
103fn complete_return(
104 ctx: &CompletionContext,
105 fn_def: &ast::FnDef,
106 can_be_stmt: bool,
107) -> Option<CompletionItem> {
108 let snip = match (can_be_stmt, fn_def.ret_type().is_some()) {
109 (true, true) => "return $0;",
110 (true, false) => "return;",
111 (false, true) => "return $0",
112 (false, false) => "return",
113 };
114 Some(keyword(ctx, "return", snip))
115}
116
117#[cfg(test)]
118mod tests {
119 use crate::completion::{do_completion, CompletionItem, CompletionKind};
120 use insta::assert_debug_snapshot;
121
122 fn do_keyword_completion(code: &str) -> Vec<CompletionItem> {
123 do_completion(code, CompletionKind::Keyword)
124 }
125
126 #[test]
127 fn completes_keywords_in_use_stmt() {
128 assert_debug_snapshot!(
129 do_keyword_completion(
130 r"
131 use <|>
132 ",
133 ),
134 @r###"
135 [
136 CompletionItem {
137 label: "crate",
138 source_range: [21; 21),
139 delete: [21; 21),
140 insert: "crate::",
141 kind: Keyword,
142 },
143 CompletionItem {
144 label: "self",
145 source_range: [21; 21),
146 delete: [21; 21),
147 insert: "self",
148 kind: Keyword,
149 },
150 CompletionItem {
151 label: "super",
152 source_range: [21; 21),
153 delete: [21; 21),
154 insert: "super::",
155 kind: Keyword,
156 },
157 ]
158 "###
159 );
160
161 assert_debug_snapshot!(
162 do_keyword_completion(
163 r"
164 use a::<|>
165 ",
166 ),
167 @r###"
168 [
169 CompletionItem {
170 label: "self",
171 source_range: [24; 24),
172 delete: [24; 24),
173 insert: "self",
174 kind: Keyword,
175 },
176 CompletionItem {
177 label: "super",
178 source_range: [24; 24),
179 delete: [24; 24),
180 insert: "super::",
181 kind: Keyword,
182 },
183 ]
184 "###
185 );
186
187 assert_debug_snapshot!(
188 do_keyword_completion(
189 r"
190 use a::{b, <|>}
191 ",
192 ),
193 @r###"
194 [
195 CompletionItem {
196 label: "self",
197 source_range: [28; 28),
198 delete: [28; 28),
199 insert: "self",
200 kind: Keyword,
201 },
202 CompletionItem {
203 label: "super",
204 source_range: [28; 28),
205 delete: [28; 28),
206 insert: "super::",
207 kind: Keyword,
208 },
209 ]
210 "###
211 );
212 }
213
214 #[test]
215 fn completes_various_keywords_in_function() {
216 assert_debug_snapshot!(
217 do_keyword_completion(
218 r"
219 fn quux() {
220 <|>
221 }
222 ",
223 ),
224 @r###"
225 [
226 CompletionItem {
227 label: "if",
228 source_range: [49; 49),
229 delete: [49; 49),
230 insert: "if $0 {}",
231 kind: Keyword,
232 },
233 CompletionItem {
234 label: "loop",
235 source_range: [49; 49),
236 delete: [49; 49),
237 insert: "loop {$0}",
238 kind: Keyword,
239 },
240 CompletionItem {
241 label: "match",
242 source_range: [49; 49),
243 delete: [49; 49),
244 insert: "match $0 {}",
245 kind: Keyword,
246 },
247 CompletionItem {
248 label: "return",
249 source_range: [49; 49),
250 delete: [49; 49),
251 insert: "return;",
252 kind: Keyword,
253 },
254 CompletionItem {
255 label: "while",
256 source_range: [49; 49),
257 delete: [49; 49),
258 insert: "while $0 {}",
259 kind: Keyword,
260 },
261 ]
262 "###
263 );
264 }
265
266 #[test]
267 fn completes_else_after_if() {
268 assert_debug_snapshot!(
269 do_keyword_completion(
270 r"
271 fn quux() {
272 if true {
273 ()
274 } <|>
275 }
276 ",
277 ),
278 @r###"
279 [
280 CompletionItem {
281 label: "else",
282 source_range: [108; 108),
283 delete: [108; 108),
284 insert: "else {$0}",
285 kind: Keyword,
286 },
287 CompletionItem {
288 label: "else if",
289 source_range: [108; 108),
290 delete: [108; 108),
291 insert: "else if $0 {}",
292 kind: Keyword,
293 },
294 CompletionItem {
295 label: "if",
296 source_range: [108; 108),
297 delete: [108; 108),
298 insert: "if $0 {}",
299 kind: Keyword,
300 },
301 CompletionItem {
302 label: "loop",
303 source_range: [108; 108),
304 delete: [108; 108),
305 insert: "loop {$0}",
306 kind: Keyword,
307 },
308 CompletionItem {
309 label: "match",
310 source_range: [108; 108),
311 delete: [108; 108),
312 insert: "match $0 {}",
313 kind: Keyword,
314 },
315 CompletionItem {
316 label: "return",
317 source_range: [108; 108),
318 delete: [108; 108),
319 insert: "return;",
320 kind: Keyword,
321 },
322 CompletionItem {
323 label: "while",
324 source_range: [108; 108),
325 delete: [108; 108),
326 insert: "while $0 {}",
327 kind: Keyword,
328 },
329 ]
330 "###
331 );
332 }
333
334 #[test]
335 fn test_completion_return_value() {
336 assert_debug_snapshot!(
337 do_keyword_completion(
338 r"
339 fn quux() -> i32 {
340 <|>
341 92
342 }
343 ",
344 ),
345 @r###"
346 [
347 CompletionItem {
348 label: "if",
349 source_range: [56; 56),
350 delete: [56; 56),
351 insert: "if $0 {}",
352 kind: Keyword,
353 },
354 CompletionItem {
355 label: "loop",
356 source_range: [56; 56),
357 delete: [56; 56),
358 insert: "loop {$0}",
359 kind: Keyword,
360 },
361 CompletionItem {
362 label: "match",
363 source_range: [56; 56),
364 delete: [56; 56),
365 insert: "match $0 {}",
366 kind: Keyword,
367 },
368 CompletionItem {
369 label: "return",
370 source_range: [56; 56),
371 delete: [56; 56),
372 insert: "return $0;",
373 kind: Keyword,
374 },
375 CompletionItem {
376 label: "while",
377 source_range: [56; 56),
378 delete: [56; 56),
379 insert: "while $0 {}",
380 kind: Keyword,
381 },
382 ]
383 "###
384 );
385 assert_debug_snapshot!(
386 do_keyword_completion(
387 r"
388 fn quux() {
389 <|>
390 92
391 }
392 ",
393 ),
394 @r###"
395 [
396 CompletionItem {
397 label: "if",
398 source_range: [49; 49),
399 delete: [49; 49),
400 insert: "if $0 {}",
401 kind: Keyword,
402 },
403 CompletionItem {
404 label: "loop",
405 source_range: [49; 49),
406 delete: [49; 49),
407 insert: "loop {$0}",
408 kind: Keyword,
409 },
410 CompletionItem {
411 label: "match",
412 source_range: [49; 49),
413 delete: [49; 49),
414 insert: "match $0 {}",
415 kind: Keyword,
416 },
417 CompletionItem {
418 label: "return",
419 source_range: [49; 49),
420 delete: [49; 49),
421 insert: "return;",
422 kind: Keyword,
423 },
424 CompletionItem {
425 label: "while",
426 source_range: [49; 49),
427 delete: [49; 49),
428 insert: "while $0 {}",
429 kind: Keyword,
430 },
431 ]
432 "###
433 );
434 }
435
436 #[test]
437 fn dont_add_semi_after_return_if_not_a_statement() {
438 assert_debug_snapshot!(
439 do_keyword_completion(
440 r"
441 fn quux() -> i32 {
442 match () {
443 () => <|>
444 }
445 }
446 ",
447 ),
448 @r###"
449 [
450 CompletionItem {
451 label: "if",
452 source_range: [97; 97),
453 delete: [97; 97),
454 insert: "if $0 {}",
455 kind: Keyword,
456 },
457 CompletionItem {
458 label: "loop",
459 source_range: [97; 97),
460 delete: [97; 97),
461 insert: "loop {$0}",
462 kind: Keyword,
463 },
464 CompletionItem {
465 label: "match",
466 source_range: [97; 97),
467 delete: [97; 97),
468 insert: "match $0 {}",
469 kind: Keyword,
470 },
471 CompletionItem {
472 label: "return",
473 source_range: [97; 97),
474 delete: [97; 97),
475 insert: "return $0",
476 kind: Keyword,
477 },
478 CompletionItem {
479 label: "while",
480 source_range: [97; 97),
481 delete: [97; 97),
482 insert: "while $0 {}",
483 kind: Keyword,
484 },
485 ]
486 "###
487 );
488 }
489
490 #[test]
491 fn last_return_in_block_has_semi() {
492 assert_debug_snapshot!(
493 do_keyword_completion(
494 r"
495 fn quux() -> i32 {
496 if condition {
497 <|>
498 }
499 }
500 ",
501 ),
502 @r###"
503 [
504 CompletionItem {
505 label: "if",
506 source_range: [95; 95),
507 delete: [95; 95),
508 insert: "if $0 {}",
509 kind: Keyword,
510 },
511 CompletionItem {
512 label: "loop",
513 source_range: [95; 95),
514 delete: [95; 95),
515 insert: "loop {$0}",
516 kind: Keyword,
517 },
518 CompletionItem {
519 label: "match",
520 source_range: [95; 95),
521 delete: [95; 95),
522 insert: "match $0 {}",
523 kind: Keyword,
524 },
525 CompletionItem {
526 label: "return",
527 source_range: [95; 95),
528 delete: [95; 95),
529 insert: "return $0;",
530 kind: Keyword,
531 },
532 CompletionItem {
533 label: "while",
534 source_range: [95; 95),
535 delete: [95; 95),
536 insert: "while $0 {}",
537 kind: Keyword,
538 },
539 ]
540 "###
541 );
542 assert_debug_snapshot!(
543 do_keyword_completion(
544 r"
545 fn quux() -> i32 {
546 if condition {
547 <|>
548 }
549 let x = 92;
550 x
551 }
552 ",
553 ),
554 @r###"
555 [
556 CompletionItem {
557 label: "if",
558 source_range: [95; 95),
559 delete: [95; 95),
560 insert: "if $0 {}",
561 kind: Keyword,
562 },
563 CompletionItem {
564 label: "loop",
565 source_range: [95; 95),
566 delete: [95; 95),
567 insert: "loop {$0}",
568 kind: Keyword,
569 },
570 CompletionItem {
571 label: "match",
572 source_range: [95; 95),
573 delete: [95; 95),
574 insert: "match $0 {}",
575 kind: Keyword,
576 },
577 CompletionItem {
578 label: "return",
579 source_range: [95; 95),
580 delete: [95; 95),
581 insert: "return $0;",
582 kind: Keyword,
583 },
584 CompletionItem {
585 label: "while",
586 source_range: [95; 95),
587 delete: [95; 95),
588 insert: "while $0 {}",
589 kind: Keyword,
590 },
591 ]
592 "###
593 );
594 }
595
596 #[test]
597 fn completes_break_and_continue_in_loops() {
598 assert_debug_snapshot!(
599 do_keyword_completion(
600 r"
601 fn quux() -> i32 {
602 loop { <|> }
603 }
604 ",
605 ),
606 @r###"
607 [
608 CompletionItem {
609 label: "break",
610 source_range: [63; 63),
611 delete: [63; 63),
612 insert: "break;",
613 kind: Keyword,
614 },
615 CompletionItem {
616 label: "continue",
617 source_range: [63; 63),
618 delete: [63; 63),
619 insert: "continue;",
620 kind: Keyword,
621 },
622 CompletionItem {
623 label: "if",
624 source_range: [63; 63),
625 delete: [63; 63),
626 insert: "if $0 {}",
627 kind: Keyword,
628 },
629 CompletionItem {
630 label: "loop",
631 source_range: [63; 63),
632 delete: [63; 63),
633 insert: "loop {$0}",
634 kind: Keyword,
635 },
636 CompletionItem {
637 label: "match",
638 source_range: [63; 63),
639 delete: [63; 63),
640 insert: "match $0 {}",
641 kind: Keyword,
642 },
643 CompletionItem {
644 label: "return",
645 source_range: [63; 63),
646 delete: [63; 63),
647 insert: "return $0;",
648 kind: Keyword,
649 },
650 CompletionItem {
651 label: "while",
652 source_range: [63; 63),
653 delete: [63; 63),
654 insert: "while $0 {}",
655 kind: Keyword,
656 },
657 ]
658 "###
659 );
660
661 // No completion: lambda isolates control flow
662 assert_debug_snapshot!(
663 do_keyword_completion(
664 r"
665 fn quux() -> i32 {
666 loop { || { <|> } }
667 }
668 ",
669 ),
670 @r###"
671 [
672 CompletionItem {
673 label: "if",
674 source_range: [68; 68),
675 delete: [68; 68),
676 insert: "if $0 {}",
677 kind: Keyword,
678 },
679 CompletionItem {
680 label: "loop",
681 source_range: [68; 68),
682 delete: [68; 68),
683 insert: "loop {$0}",
684 kind: Keyword,
685 },
686 CompletionItem {
687 label: "match",
688 source_range: [68; 68),
689 delete: [68; 68),
690 insert: "match $0 {}",
691 kind: Keyword,
692 },
693 CompletionItem {
694 label: "return",
695 source_range: [68; 68),
696 delete: [68; 68),
697 insert: "return $0;",
698 kind: Keyword,
699 },
700 CompletionItem {
701 label: "while",
702 source_range: [68; 68),
703 delete: [68; 68),
704 insert: "while $0 {}",
705 kind: Keyword,
706 },
707 ]
708 "###
709 );
710 }
711
712 #[test]
713 fn no_semi_after_break_continue_in_expr() {
714 assert_debug_snapshot!(
715 do_keyword_completion(
716 r"
717 fn f() {
718 loop {
719 match () {
720 () => br<|>
721 }
722 }
723 }
724 ",
725 ),
726 @r###"
727 [
728 CompletionItem {
729 label: "break",
730 source_range: [122; 124),
731 delete: [122; 124),
732 insert: "break",
733 kind: Keyword,
734 },
735 CompletionItem {
736 label: "continue",
737 source_range: [122; 124),
738 delete: [122; 124),
739 insert: "continue",
740 kind: Keyword,
741 },
742 CompletionItem {
743 label: "if",
744 source_range: [122; 124),
745 delete: [122; 124),
746 insert: "if $0 {}",
747 kind: Keyword,
748 },
749 CompletionItem {
750 label: "loop",
751 source_range: [122; 124),
752 delete: [122; 124),
753 insert: "loop {$0}",
754 kind: Keyword,
755 },
756 CompletionItem {
757 label: "match",
758 source_range: [122; 124),
759 delete: [122; 124),
760 insert: "match $0 {}",
761 kind: Keyword,
762 },
763 CompletionItem {
764 label: "return",
765 source_range: [122; 124),
766 delete: [122; 124),
767 insert: "return",
768 kind: Keyword,
769 },
770 CompletionItem {
771 label: "while",
772 source_range: [122; 124),
773 delete: [122; 124),
774 insert: "while $0 {}",
775 kind: Keyword,
776 },
777 ]
778 "###
779 )
780 }
781}
diff --git a/crates/ra_ide_api/src/completion/complete_macro_in_item_position.rs b/crates/ra_ide_api/src/completion/complete_macro_in_item_position.rs
deleted file mode 100644
index faadd1e3f..000000000
--- a/crates/ra_ide_api/src/completion/complete_macro_in_item_position.rs
+++ /dev/null
@@ -1,143 +0,0 @@
1//! FIXME: write short doc here
2
3use crate::completion::{CompletionContext, Completions};
4
5pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) {
6 // Show only macros in top level.
7 if ctx.is_new_item {
8 ctx.analyzer.process_all_names(ctx.db, &mut |name, res| {
9 if let hir::ScopeDef::MacroDef(mac) = res {
10 acc.add_macro(ctx, Some(name.to_string()), mac);
11 }
12 })
13 }
14}
15
16#[cfg(test)]
17mod tests {
18 use crate::completion::{do_completion, CompletionItem, CompletionKind};
19 use insta::assert_debug_snapshot;
20
21 fn do_reference_completion(code: &str) -> Vec<CompletionItem> {
22 do_completion(code, CompletionKind::Reference)
23 }
24
25 #[test]
26 fn completes_macros_as_item() {
27 assert_debug_snapshot!(
28 do_reference_completion(
29 "
30 //- /main.rs
31 macro_rules! foo {
32 () => {}
33 }
34
35 fn foo() {}
36
37 <|>
38 "
39 ),
40 @r###"
41 [
42 CompletionItem {
43 label: "foo!",
44 source_range: [46; 46),
45 delete: [46; 46),
46 insert: "foo!($0)",
47 kind: Macro,
48 detail: "macro_rules! foo",
49 },
50 ]
51 "###
52 );
53 }
54
55 #[test]
56 fn completes_vec_macros_with_square_brackets() {
57 assert_debug_snapshot!(
58 do_reference_completion(
59 "
60 //- /main.rs
61 /// Creates a [`Vec`] containing the arguments.
62 ///
63 /// - Create a [`Vec`] containing a given list of elements:
64 ///
65 /// ```
66 /// let v = vec![1, 2, 3];
67 /// assert_eq!(v[0], 1);
68 /// assert_eq!(v[1], 2);
69 /// assert_eq!(v[2], 3);
70 /// ```
71 macro_rules! vec {
72 () => {}
73 }
74
75 fn foo() {}
76
77 <|>
78 "
79 ),
80 @r###"
81 [
82 CompletionItem {
83 label: "vec!",
84 source_range: [280; 280),
85 delete: [280; 280),
86 insert: "vec![$0]",
87 kind: Macro,
88 detail: "macro_rules! vec",
89 documentation: Documentation(
90 "Creates a [`Vec`] containing the arguments.\n\n- Create a [`Vec`] containing a given list of elements:\n\n```\nlet v = vec![1, 2, 3];\nassert_eq!(v[0], 1);\nassert_eq!(v[1], 2);\nassert_eq!(v[2], 3);\n```",
91 ),
92 },
93 ]
94 "###
95 );
96 }
97
98 #[test]
99 fn completes_macros_braces_guessing() {
100 assert_debug_snapshot!(
101 do_reference_completion(
102 "
103 //- /main.rs
104 /// Foo
105 ///
106 /// Not call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`.
107 /// Call as `let _=foo! { hello world };`
108 macro_rules! foo {
109 () => {}
110 }
111
112 fn main() {
113 <|>
114 }
115 "
116 ),
117 @r###"
118 [
119 CompletionItem {
120 label: "foo!",
121 source_range: [163; 163),
122 delete: [163; 163),
123 insert: "foo! {$0}",
124 kind: Macro,
125 detail: "macro_rules! foo",
126 documentation: Documentation(
127 "Foo\n\nNot call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`.\nCall as `let _=foo! { hello world };`",
128 ),
129 },
130 CompletionItem {
131 label: "main()",
132 source_range: [163; 163),
133 delete: [163; 163),
134 insert: "main()$0",
135 kind: Function,
136 lookup: "main",
137 detail: "fn main()",
138 },
139 ]
140 "###
141 );
142 }
143}
diff --git a/crates/ra_ide_api/src/completion/complete_path.rs b/crates/ra_ide_api/src/completion/complete_path.rs
deleted file mode 100644
index 89e0009a1..000000000
--- a/crates/ra_ide_api/src/completion/complete_path.rs
+++ /dev/null
@@ -1,785 +0,0 @@
1//! FIXME: write short doc here
2
3use hir::{Adt, Either, HasSource, PathResolution};
4use ra_syntax::AstNode;
5use test_utils::tested_by;
6
7use crate::completion::{CompletionContext, Completions};
8
9pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
10 let path = match &ctx.path_prefix {
11 Some(path) => path.clone(),
12 _ => return,
13 };
14 let def = match ctx.analyzer.resolve_hir_path(ctx.db, &path) {
15 Some(PathResolution::Def(def)) => def,
16 _ => return,
17 };
18 match def {
19 hir::ModuleDef::Module(module) => {
20 let module_scope = module.scope(ctx.db);
21 for (name, def, import) in module_scope {
22 if let hir::ScopeDef::ModuleDef(hir::ModuleDef::BuiltinType(..)) = def {
23 if ctx.use_item_syntax.is_some() {
24 tested_by!(dont_complete_primitive_in_use);
25 continue;
26 }
27 }
28 if Some(module) == ctx.module {
29 if let Some(import) = import {
30 if let Either::A(use_tree) = import.source(ctx.db).value {
31 if use_tree.syntax().text_range().contains_inclusive(ctx.offset) {
32 // for `use self::foo<|>`, don't suggest `foo` as a completion
33 tested_by!(dont_complete_current_use);
34 continue;
35 }
36 }
37 }
38 }
39 acc.add_resolution(ctx, name.to_string(), &def);
40 }
41 }
42 hir::ModuleDef::Adt(_) | hir::ModuleDef::TypeAlias(_) => {
43 if let hir::ModuleDef::Adt(Adt::Enum(e)) = def {
44 for variant in e.variants(ctx.db) {
45 acc.add_enum_variant(ctx, variant);
46 }
47 }
48 let ty = match def {
49 hir::ModuleDef::Adt(adt) => adt.ty(ctx.db),
50 hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
51 _ => unreachable!(),
52 };
53 ctx.analyzer.iterate_path_candidates(ctx.db, &ty, None, |_ty, item| {
54 match item {
55 hir::AssocItem::Function(func) => {
56 if !func.has_self_param(ctx.db) {
57 acc.add_function(ctx, func);
58 }
59 }
60 hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
61 hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
62 }
63 None::<()>
64 });
65 // Iterate assoc types separately
66 // FIXME: complete T::AssocType
67 let krate = ctx.module.map(|m| m.krate());
68 if let Some(krate) = krate {
69 ty.iterate_impl_items(ctx.db, krate, |item| {
70 match item {
71 hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => {}
72 hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
73 }
74 None::<()>
75 });
76 }
77 }
78 hir::ModuleDef::Trait(t) => {
79 for item in t.items(ctx.db) {
80 match item {
81 hir::AssocItem::Function(func) => {
82 if !func.has_self_param(ctx.db) {
83 acc.add_function(ctx, func);
84 }
85 }
86 hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
87 hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
88 }
89 }
90 }
91 _ => {}
92 };
93}
94
95#[cfg(test)]
96mod tests {
97 use test_utils::covers;
98
99 use crate::completion::{do_completion, CompletionItem, CompletionKind};
100 use insta::assert_debug_snapshot;
101
102 fn do_reference_completion(code: &str) -> Vec<CompletionItem> {
103 do_completion(code, CompletionKind::Reference)
104 }
105
106 #[test]
107 fn dont_complete_current_use() {
108 covers!(dont_complete_current_use);
109 let completions = do_completion(r"use self::foo<|>;", CompletionKind::Reference);
110 assert!(completions.is_empty());
111 }
112
113 #[test]
114 fn dont_complete_current_use_in_braces_with_glob() {
115 let completions = do_completion(
116 r"
117 mod foo { pub struct S; }
118 use self::{foo::*, bar<|>};
119 ",
120 CompletionKind::Reference,
121 );
122 assert_eq!(completions.len(), 2);
123 }
124
125 #[test]
126 fn dont_complete_primitive_in_use() {
127 covers!(dont_complete_primitive_in_use);
128 let completions = do_completion(r"use self::<|>;", CompletionKind::BuiltinType);
129 assert!(completions.is_empty());
130 }
131
132 #[test]
133 fn completes_primitives() {
134 let completions =
135 do_completion(r"fn main() { let _: <|> = 92; }", CompletionKind::BuiltinType);
136 assert_eq!(completions.len(), 17);
137 }
138
139 #[test]
140 fn completes_mod_with_docs() {
141 assert_debug_snapshot!(
142 do_reference_completion(
143 r"
144 use self::my<|>;
145
146 /// Some simple
147 /// docs describing `mod my`.
148 mod my {
149 struct Bar;
150 }
151 "
152 ),
153 @r###"
154 [
155 CompletionItem {
156 label: "my",
157 source_range: [27; 29),
158 delete: [27; 29),
159 insert: "my",
160 kind: Module,
161 documentation: Documentation(
162 "Some simple\ndocs describing `mod my`.",
163 ),
164 },
165 ]
166 "###
167 );
168 }
169
170 #[test]
171 fn completes_use_item_starting_with_self() {
172 assert_debug_snapshot!(
173 do_reference_completion(
174 r"
175 use self::m::<|>;
176
177 mod m {
178 struct Bar;
179 }
180 "
181 ),
182 @r###"
183 [
184 CompletionItem {
185 label: "Bar",
186 source_range: [30; 30),
187 delete: [30; 30),
188 insert: "Bar",
189 kind: Struct,
190 },
191 ]
192 "###
193 );
194 }
195
196 #[test]
197 fn completes_use_item_starting_with_crate() {
198 assert_debug_snapshot!(
199 do_reference_completion(
200 "
201 //- /lib.rs
202 mod foo;
203 struct Spam;
204 //- /foo.rs
205 use crate::Sp<|>
206 "
207 ),
208 @r###"
209 [
210 CompletionItem {
211 label: "Spam",
212 source_range: [11; 13),
213 delete: [11; 13),
214 insert: "Spam",
215 kind: Struct,
216 },
217 CompletionItem {
218 label: "foo",
219 source_range: [11; 13),
220 delete: [11; 13),
221 insert: "foo",
222 kind: Module,
223 },
224 ]
225 "###
226 );
227 }
228
229 #[test]
230 fn completes_nested_use_tree() {
231 assert_debug_snapshot!(
232 do_reference_completion(
233 "
234 //- /lib.rs
235 mod foo;
236 struct Spam;
237 //- /foo.rs
238 use crate::{Sp<|>};
239 "
240 ),
241 @r###"
242 [
243 CompletionItem {
244 label: "Spam",
245 source_range: [12; 14),
246 delete: [12; 14),
247 insert: "Spam",
248 kind: Struct,
249 },
250 CompletionItem {
251 label: "foo",
252 source_range: [12; 14),
253 delete: [12; 14),
254 insert: "foo",
255 kind: Module,
256 },
257 ]
258 "###
259 );
260 }
261
262 #[test]
263 fn completes_deeply_nested_use_tree() {
264 assert_debug_snapshot!(
265 do_reference_completion(
266 "
267 //- /lib.rs
268 mod foo;
269 pub mod bar {
270 pub mod baz {
271 pub struct Spam;
272 }
273 }
274 //- /foo.rs
275 use crate::{bar::{baz::Sp<|>}};
276 "
277 ),
278 @r###"
279 [
280 CompletionItem {
281 label: "Spam",
282 source_range: [23; 25),
283 delete: [23; 25),
284 insert: "Spam",
285 kind: Struct,
286 },
287 ]
288 "###
289 );
290 }
291
292 #[test]
293 fn completes_enum_variant() {
294 assert_debug_snapshot!(
295 do_reference_completion(
296 "
297 //- /lib.rs
298 /// An enum
299 enum E {
300 /// Foo Variant
301 Foo,
302 /// Bar Variant with i32
303 Bar(i32)
304 }
305 fn foo() { let _ = E::<|> }
306 "
307 ),
308 @r###"
309 [
310 CompletionItem {
311 label: "Bar",
312 source_range: [116; 116),
313 delete: [116; 116),
314 insert: "Bar",
315 kind: EnumVariant,
316 detail: "(i32)",
317 documentation: Documentation(
318 "Bar Variant with i32",
319 ),
320 },
321 CompletionItem {
322 label: "Foo",
323 source_range: [116; 116),
324 delete: [116; 116),
325 insert: "Foo",
326 kind: EnumVariant,
327 detail: "()",
328 documentation: Documentation(
329 "Foo Variant",
330 ),
331 },
332 ]
333 "###
334 );
335 }
336
337 #[test]
338 fn completes_enum_variant_with_details() {
339 assert_debug_snapshot!(
340 do_reference_completion(
341 "
342 //- /lib.rs
343 struct S { field: u32 }
344 /// An enum
345 enum E {
346 /// Foo Variant (empty)
347 Foo,
348 /// Bar Variant with i32 and u32
349 Bar(i32, u32),
350 ///
351 S(S),
352 }
353 fn foo() { let _ = E::<|> }
354 "
355 ),
356 @r###"
357 [
358 CompletionItem {
359 label: "Bar",
360 source_range: [180; 180),
361 delete: [180; 180),
362 insert: "Bar",
363 kind: EnumVariant,
364 detail: "(i32, u32)",
365 documentation: Documentation(
366 "Bar Variant with i32 and u32",
367 ),
368 },
369 CompletionItem {
370 label: "Foo",
371 source_range: [180; 180),
372 delete: [180; 180),
373 insert: "Foo",
374 kind: EnumVariant,
375 detail: "()",
376 documentation: Documentation(
377 "Foo Variant (empty)",
378 ),
379 },
380 CompletionItem {
381 label: "S",
382 source_range: [180; 180),
383 delete: [180; 180),
384 insert: "S",
385 kind: EnumVariant,
386 detail: "(S)",
387 documentation: Documentation(
388 "",
389 ),
390 },
391 ]
392 "###
393 );
394 }
395
396 #[test]
397 fn completes_struct_associated_method() {
398 assert_debug_snapshot!(
399 do_reference_completion(
400 "
401 //- /lib.rs
402 /// A Struct
403 struct S;
404
405 impl S {
406 /// An associated method
407 fn m() { }
408 }
409
410 fn foo() { let _ = S::<|> }
411 "
412 ),
413 @r###"
414 [
415 CompletionItem {
416 label: "m()",
417 source_range: [100; 100),
418 delete: [100; 100),
419 insert: "m()$0",
420 kind: Function,
421 lookup: "m",
422 detail: "fn m()",
423 documentation: Documentation(
424 "An associated method",
425 ),
426 },
427 ]
428 "###
429 );
430 }
431
432 #[test]
433 fn completes_struct_associated_const() {
434 assert_debug_snapshot!(
435 do_reference_completion(
436 "
437 //- /lib.rs
438 /// A Struct
439 struct S;
440
441 impl S {
442 /// An associated const
443 const C: i32 = 42;
444 }
445
446 fn foo() { let _ = S::<|> }
447 "
448 ),
449 @r###"
450 [
451 CompletionItem {
452 label: "C",
453 source_range: [107; 107),
454 delete: [107; 107),
455 insert: "C",
456 kind: Const,
457 detail: "const C: i32 = 42;",
458 documentation: Documentation(
459 "An associated const",
460 ),
461 },
462 ]
463 "###
464 );
465 }
466
467 #[test]
468 fn completes_struct_associated_type() {
469 assert_debug_snapshot!(
470 do_reference_completion(
471 "
472 //- /lib.rs
473 /// A Struct
474 struct S;
475
476 impl S {
477 /// An associated type
478 type T = i32;
479 }
480
481 fn foo() { let _ = S::<|> }
482 "
483 ),
484 @r###"
485 [
486 CompletionItem {
487 label: "T",
488 source_range: [101; 101),
489 delete: [101; 101),
490 insert: "T",
491 kind: TypeAlias,
492 detail: "type T = i32;",
493 documentation: Documentation(
494 "An associated type",
495 ),
496 },
497 ]
498 "###
499 );
500 }
501
502 #[test]
503 fn completes_enum_associated_method() {
504 assert_debug_snapshot!(
505 do_reference_completion(
506 "
507 //- /lib.rs
508 /// An enum
509 enum S {};
510
511 impl S {
512 /// An associated method
513 fn m() { }
514 }
515
516 fn foo() { let _ = S::<|> }
517 "
518 ),
519 @r###"
520 [
521 CompletionItem {
522 label: "m()",
523 source_range: [100; 100),
524 delete: [100; 100),
525 insert: "m()$0",
526 kind: Function,
527 lookup: "m",
528 detail: "fn m()",
529 documentation: Documentation(
530 "An associated method",
531 ),
532 },
533 ]
534 "###
535 );
536 }
537
538 #[test]
539 fn completes_union_associated_method() {
540 assert_debug_snapshot!(
541 do_reference_completion(
542 "
543 //- /lib.rs
544 /// A union
545 union U {};
546
547 impl U {
548 /// An associated method
549 fn m() { }
550 }
551
552 fn foo() { let _ = U::<|> }
553 "
554 ),
555 @r###"
556 [
557 CompletionItem {
558 label: "m()",
559 source_range: [101; 101),
560 delete: [101; 101),
561 insert: "m()$0",
562 kind: Function,
563 lookup: "m",
564 detail: "fn m()",
565 documentation: Documentation(
566 "An associated method",
567 ),
568 },
569 ]
570 "###
571 );
572 }
573
574 #[test]
575 fn completes_use_paths_across_crates() {
576 assert_debug_snapshot!(
577 do_reference_completion(
578 "
579 //- /main.rs
580 use foo::<|>;
581
582 //- /foo/lib.rs
583 pub mod bar {
584 pub struct S;
585 }
586 "
587 ),
588 @r###"
589 [
590 CompletionItem {
591 label: "bar",
592 source_range: [9; 9),
593 delete: [9; 9),
594 insert: "bar",
595 kind: Module,
596 },
597 ]
598 "###
599 );
600 }
601
602 #[test]
603 fn completes_trait_associated_method_1() {
604 assert_debug_snapshot!(
605 do_reference_completion(
606 "
607 //- /lib.rs
608 trait Trait {
609 /// A trait method
610 fn m();
611 }
612
613 fn foo() { let _ = Trait::<|> }
614 "
615 ),
616 @r###"
617 [
618 CompletionItem {
619 label: "m()",
620 source_range: [73; 73),
621 delete: [73; 73),
622 insert: "m()$0",
623 kind: Function,
624 lookup: "m",
625 detail: "fn m()",
626 documentation: Documentation(
627 "A trait method",
628 ),
629 },
630 ]
631 "###
632 );
633 }
634
635 #[test]
636 fn completes_trait_associated_method_2() {
637 assert_debug_snapshot!(
638 do_reference_completion(
639 "
640 //- /lib.rs
641 trait Trait {
642 /// A trait method
643 fn m();
644 }
645
646 struct S;
647 impl Trait for S {}
648
649 fn foo() { let _ = S::<|> }
650 "
651 ),
652 @r###"
653 [
654 CompletionItem {
655 label: "m()",
656 source_range: [99; 99),
657 delete: [99; 99),
658 insert: "m()$0",
659 kind: Function,
660 lookup: "m",
661 detail: "fn m()",
662 documentation: Documentation(
663 "A trait method",
664 ),
665 },
666 ]
667 "###
668 );
669 }
670
671 #[test]
672 fn completes_trait_associated_method_3() {
673 assert_debug_snapshot!(
674 do_reference_completion(
675 "
676 //- /lib.rs
677 trait Trait {
678 /// A trait method
679 fn m();
680 }
681
682 struct S;
683 impl Trait for S {}
684
685 fn foo() { let _ = <S as Trait>::<|> }
686 "
687 ),
688 @r###"
689 [
690 CompletionItem {
691 label: "m()",
692 source_range: [110; 110),
693 delete: [110; 110),
694 insert: "m()$0",
695 kind: Function,
696 lookup: "m",
697 detail: "fn m()",
698 documentation: Documentation(
699 "A trait method",
700 ),
701 },
702 ]
703 "###
704 );
705 }
706
707 #[test]
708 fn completes_type_alias() {
709 assert_debug_snapshot!(
710 do_reference_completion(
711 "
712 struct S;
713 impl S { fn foo() {} }
714 type T = S;
715 impl T { fn bar() {} }
716
717 fn main() {
718 T::<|>;
719 }
720 "
721 ),
722 @r###"
723 [
724 CompletionItem {
725 label: "bar()",
726 source_range: [185; 185),
727 delete: [185; 185),
728 insert: "bar()$0",
729 kind: Function,
730 lookup: "bar",
731 detail: "fn bar()",
732 },
733 CompletionItem {
734 label: "foo()",
735 source_range: [185; 185),
736 delete: [185; 185),
737 insert: "foo()$0",
738 kind: Function,
739 lookup: "foo",
740 detail: "fn foo()",
741 },
742 ]
743 "###
744 );
745 }
746
747 #[test]
748 fn completes_qualified_macros() {
749 assert_debug_snapshot!(
750 do_reference_completion(
751 "
752 #[macro_export]
753 macro_rules! foo {
754 () => {}
755 }
756
757 fn main() {
758 let _ = crate::<|>
759 }
760 "
761 ),
762 @r###"
763 [
764 CompletionItem {
765 label: "foo!",
766 source_range: [179; 179),
767 delete: [179; 179),
768 insert: "foo!($0)",
769 kind: Macro,
770 detail: "#[macro_export]\nmacro_rules! foo",
771 },
772 CompletionItem {
773 label: "main()",
774 source_range: [179; 179),
775 delete: [179; 179),
776 insert: "main()$0",
777 kind: Function,
778 lookup: "main",
779 detail: "fn main()",
780 },
781 ]
782 "###
783 );
784 }
785}
diff --git a/crates/ra_ide_api/src/completion/complete_pattern.rs b/crates/ra_ide_api/src/completion/complete_pattern.rs
deleted file mode 100644
index fd03b1c40..000000000
--- a/crates/ra_ide_api/src/completion/complete_pattern.rs
+++ /dev/null
@@ -1,89 +0,0 @@
1//! FIXME: write short doc here
2
3use crate::completion::{CompletionContext, Completions};
4
5/// Completes constats and paths in patterns.
6pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
7 if !ctx.is_pat_binding {
8 return;
9 }
10 // FIXME: ideally, we should look at the type we are matching against and
11 // suggest variants + auto-imports
12 ctx.analyzer.process_all_names(ctx.db, &mut |name, res| {
13 let def = match &res {
14 hir::ScopeDef::ModuleDef(def) => def,
15 _ => return,
16 };
17 match def {
18 hir::ModuleDef::Adt(hir::Adt::Enum(..))
19 | hir::ModuleDef::EnumVariant(..)
20 | hir::ModuleDef::Const(..)
21 | hir::ModuleDef::Module(..) => (),
22 _ => return,
23 }
24 acc.add_resolution(ctx, name.to_string(), &res)
25 });
26}
27
28#[cfg(test)]
29mod tests {
30 use crate::completion::{do_completion, CompletionItem, CompletionKind};
31 use insta::assert_debug_snapshot;
32
33 fn complete(code: &str) -> Vec<CompletionItem> {
34 do_completion(code, CompletionKind::Reference)
35 }
36
37 #[test]
38 fn completes_enum_variants_and_modules() {
39 let completions = complete(
40 r"
41 enum E { X }
42 use self::E::X;
43 const Z: E = E::X;
44 mod m {}
45
46 static FOO: E = E::X;
47 struct Bar { f: u32 }
48
49 fn foo() {
50 match E::X {
51 <|>
52 }
53 }
54 ",
55 );
56 assert_debug_snapshot!(completions, @r###"
57 [
58 CompletionItem {
59 label: "E",
60 source_range: [246; 246),
61 delete: [246; 246),
62 insert: "E",
63 kind: Enum,
64 },
65 CompletionItem {
66 label: "X",
67 source_range: [246; 246),
68 delete: [246; 246),
69 insert: "X",
70 kind: EnumVariant,
71 },
72 CompletionItem {
73 label: "Z",
74 source_range: [246; 246),
75 delete: [246; 246),
76 insert: "Z",
77 kind: Const,
78 },
79 CompletionItem {
80 label: "m",
81 source_range: [246; 246),
82 delete: [246; 246),
83 insert: "m",
84 kind: Module,
85 },
86 ]
87 "###);
88 }
89}
diff --git a/crates/ra_ide_api/src/completion/complete_postfix.rs b/crates/ra_ide_api/src/completion/complete_postfix.rs
deleted file mode 100644
index 646a30c76..000000000
--- a/crates/ra_ide_api/src/completion/complete_postfix.rs
+++ /dev/null
@@ -1,282 +0,0 @@
1//! FIXME: write short doc here
2
3use ra_syntax::{ast::AstNode, TextRange, TextUnit};
4use ra_text_edit::TextEdit;
5
6use crate::{
7 completion::{
8 completion_context::CompletionContext,
9 completion_item::{Builder, CompletionKind, Completions},
10 },
11 CompletionItem,
12};
13
14pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
15 if ctx.db.feature_flags.get("completion.enable-postfix") == false {
16 return;
17 }
18
19 let dot_receiver = match &ctx.dot_receiver {
20 Some(it) => it,
21 None => return,
22 };
23
24 let receiver_text = if ctx.dot_receiver_is_ambiguous_float_literal {
25 let text = dot_receiver.syntax().text();
26 let without_dot = ..text.len() - TextUnit::of_char('.');
27 text.slice(without_dot).to_string()
28 } else {
29 dot_receiver.syntax().text().to_string()
30 };
31
32 let receiver_ty = match ctx.analyzer.type_of(ctx.db, &dot_receiver) {
33 Some(it) => it,
34 None => return,
35 };
36
37 if receiver_ty.is_bool() || receiver_ty.is_unknown() {
38 postfix_snippet(ctx, "if", "if expr {}", &format!("if {} {{$0}}", receiver_text))
39 .add_to(acc);
40 postfix_snippet(
41 ctx,
42 "while",
43 "while expr {}",
44 &format!("while {} {{\n$0\n}}", receiver_text),
45 )
46 .add_to(acc);
47 }
48
49 postfix_snippet(ctx, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc);
50
51 postfix_snippet(ctx, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc);
52 postfix_snippet(ctx, "refm", "&mut expr", &format!("&mut {}", receiver_text)).add_to(acc);
53
54 postfix_snippet(
55 ctx,
56 "match",
57 "match expr {}",
58 &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text),
59 )
60 .add_to(acc);
61
62 postfix_snippet(ctx, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc);
63
64 postfix_snippet(ctx, "box", "Box::new(expr)", &format!("Box::new({})", receiver_text))
65 .add_to(acc);
66}
67
68fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder {
69 let edit = {
70 let receiver_range =
71 ctx.dot_receiver.as_ref().expect("no receiver available").syntax().text_range();
72 let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end());
73 TextEdit::replace(delete_range, snippet.to_string())
74 };
75 CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label)
76 .detail(detail)
77 .snippet_edit(edit)
78}
79
80#[cfg(test)]
81mod tests {
82 use insta::assert_debug_snapshot;
83
84 use crate::completion::{do_completion, CompletionItem, CompletionKind};
85
86 fn do_postfix_completion(code: &str) -> Vec<CompletionItem> {
87 do_completion(code, CompletionKind::Postfix)
88 }
89
90 #[test]
91 fn postfix_completion_works_for_trivial_path_expression() {
92 assert_debug_snapshot!(
93 do_postfix_completion(
94 r#"
95 fn main() {
96 let bar = true;
97 bar.<|>
98 }
99 "#,
100 ),
101 @r###"
102 [
103 CompletionItem {
104 label: "box",
105 source_range: [89; 89),
106 delete: [85; 89),
107 insert: "Box::new(bar)",
108 detail: "Box::new(expr)",
109 },
110 CompletionItem {
111 label: "dbg",
112 source_range: [89; 89),
113 delete: [85; 89),
114 insert: "dbg!(bar)",
115 detail: "dbg!(expr)",
116 },
117 CompletionItem {
118 label: "if",
119 source_range: [89; 89),
120 delete: [85; 89),
121 insert: "if bar {$0}",
122 detail: "if expr {}",
123 },
124 CompletionItem {
125 label: "match",
126 source_range: [89; 89),
127 delete: [85; 89),
128 insert: "match bar {\n ${1:_} => {$0\\},\n}",
129 detail: "match expr {}",
130 },
131 CompletionItem {
132 label: "not",
133 source_range: [89; 89),
134 delete: [85; 89),
135 insert: "!bar",
136 detail: "!expr",
137 },
138 CompletionItem {
139 label: "ref",
140 source_range: [89; 89),
141 delete: [85; 89),
142 insert: "&bar",
143 detail: "&expr",
144 },
145 CompletionItem {
146 label: "refm",
147 source_range: [89; 89),
148 delete: [85; 89),
149 insert: "&mut bar",
150 detail: "&mut expr",
151 },
152 CompletionItem {
153 label: "while",
154 source_range: [89; 89),
155 delete: [85; 89),
156 insert: "while bar {\n$0\n}",
157 detail: "while expr {}",
158 },
159 ]
160 "###
161 );
162 }
163
164 #[test]
165 fn some_postfix_completions_ignored() {
166 assert_debug_snapshot!(
167 do_postfix_completion(
168 r#"
169 fn main() {
170 let bar: u8 = 12;
171 bar.<|>
172 }
173 "#,
174 ),
175 @r###"
176 [
177 CompletionItem {
178 label: "box",
179 source_range: [91; 91),
180 delete: [87; 91),
181 insert: "Box::new(bar)",
182 detail: "Box::new(expr)",
183 },
184 CompletionItem {
185 label: "dbg",
186 source_range: [91; 91),
187 delete: [87; 91),
188 insert: "dbg!(bar)",
189 detail: "dbg!(expr)",
190 },
191 CompletionItem {
192 label: "match",
193 source_range: [91; 91),
194 delete: [87; 91),
195 insert: "match bar {\n ${1:_} => {$0\\},\n}",
196 detail: "match expr {}",
197 },
198 CompletionItem {
199 label: "not",
200 source_range: [91; 91),
201 delete: [87; 91),
202 insert: "!bar",
203 detail: "!expr",
204 },
205 CompletionItem {
206 label: "ref",
207 source_range: [91; 91),
208 delete: [87; 91),
209 insert: "&bar",
210 detail: "&expr",
211 },
212 CompletionItem {
213 label: "refm",
214 source_range: [91; 91),
215 delete: [87; 91),
216 insert: "&mut bar",
217 detail: "&mut expr",
218 },
219 ]
220 "###
221 );
222 }
223
224 #[test]
225 fn postfix_completion_works_for_ambiguous_float_literal() {
226 assert_debug_snapshot!(
227 do_postfix_completion(
228 r#"
229 fn main() {
230 42.<|>
231 }
232 "#,
233 ),
234 @r###"
235 [
236 CompletionItem {
237 label: "box",
238 source_range: [52; 52),
239 delete: [49; 52),
240 insert: "Box::new(42)",
241 detail: "Box::new(expr)",
242 },
243 CompletionItem {
244 label: "dbg",
245 source_range: [52; 52),
246 delete: [49; 52),
247 insert: "dbg!(42)",
248 detail: "dbg!(expr)",
249 },
250 CompletionItem {
251 label: "match",
252 source_range: [52; 52),
253 delete: [49; 52),
254 insert: "match 42 {\n ${1:_} => {$0\\},\n}",
255 detail: "match expr {}",
256 },
257 CompletionItem {
258 label: "not",
259 source_range: [52; 52),
260 delete: [49; 52),
261 insert: "!42",
262 detail: "!expr",
263 },
264 CompletionItem {
265 label: "ref",
266 source_range: [52; 52),
267 delete: [49; 52),
268 insert: "&42",
269 detail: "&expr",
270 },
271 CompletionItem {
272 label: "refm",
273 source_range: [52; 52),
274 delete: [49; 52),
275 insert: "&mut 42",
276 detail: "&mut expr",
277 },
278 ]
279 "###
280 );
281 }
282}
diff --git a/crates/ra_ide_api/src/completion/complete_record_literal.rs b/crates/ra_ide_api/src/completion/complete_record_literal.rs
deleted file mode 100644
index 577c394d2..000000000
--- a/crates/ra_ide_api/src/completion/complete_record_literal.rs
+++ /dev/null
@@ -1,159 +0,0 @@
1//! FIXME: write short doc here
2
3use crate::completion::{CompletionContext, Completions};
4
5/// Complete fields in fields literals.
6pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionContext) {
7 let (ty, variant) = match ctx.record_lit_syntax.as_ref().and_then(|it| {
8 Some((
9 ctx.analyzer.type_of(ctx.db, &it.clone().into())?,
10 ctx.analyzer.resolve_record_literal(it)?,
11 ))
12 }) {
13 Some(it) => it,
14 _ => return,
15 };
16
17 for (field, field_ty) in ty.variant_fields(ctx.db, variant) {
18 acc.add_field(ctx, field, &field_ty);
19 }
20}
21
22#[cfg(test)]
23mod tests {
24 use crate::completion::{do_completion, CompletionItem, CompletionKind};
25 use insta::assert_debug_snapshot;
26
27 fn complete(code: &str) -> Vec<CompletionItem> {
28 do_completion(code, CompletionKind::Reference)
29 }
30
31 #[test]
32 fn test_record_literal_deprecated_field() {
33 let completions = complete(
34 r"
35 struct A {
36 #[deprecated]
37 the_field: u32,
38 }
39 fn foo() {
40 A { the<|> }
41 }
42 ",
43 );
44 assert_debug_snapshot!(completions, @r###"
45 [
46 CompletionItem {
47 label: "the_field",
48 source_range: [142; 145),
49 delete: [142; 145),
50 insert: "the_field",
51 kind: Field,
52 detail: "u32",
53 deprecated: true,
54 },
55 ]
56 "###);
57 }
58
59 #[test]
60 fn test_record_literal_field() {
61 let completions = complete(
62 r"
63 struct A { the_field: u32 }
64 fn foo() {
65 A { the<|> }
66 }
67 ",
68 );
69 assert_debug_snapshot!(completions, @r###"
70 [
71 CompletionItem {
72 label: "the_field",
73 source_range: [83; 86),
74 delete: [83; 86),
75 insert: "the_field",
76 kind: Field,
77 detail: "u32",
78 },
79 ]
80 "###);
81 }
82
83 #[test]
84 fn test_record_literal_enum_variant() {
85 let completions = complete(
86 r"
87 enum E {
88 A { a: u32 }
89 }
90 fn foo() {
91 let _ = E::A { <|> }
92 }
93 ",
94 );
95 assert_debug_snapshot!(completions, @r###"
96 [
97 CompletionItem {
98 label: "a",
99 source_range: [119; 119),
100 delete: [119; 119),
101 insert: "a",
102 kind: Field,
103 detail: "u32",
104 },
105 ]
106 "###);
107 }
108
109 #[test]
110 fn test_record_literal_two_structs() {
111 let completions = complete(
112 r"
113 struct A { a: u32 }
114 struct B { b: u32 }
115
116 fn foo() {
117 let _: A = B { <|> }
118 }
119 ",
120 );
121 assert_debug_snapshot!(completions, @r###"
122 [
123 CompletionItem {
124 label: "b",
125 source_range: [119; 119),
126 delete: [119; 119),
127 insert: "b",
128 kind: Field,
129 detail: "u32",
130 },
131 ]
132 "###);
133 }
134
135 #[test]
136 fn test_record_literal_generic_struct() {
137 let completions = complete(
138 r"
139 struct A<T> { a: T }
140
141 fn foo() {
142 let _: A<u32> = A { <|> }
143 }
144 ",
145 );
146 assert_debug_snapshot!(completions, @r###"
147 [
148 CompletionItem {
149 label: "a",
150 source_range: [93; 93),
151 delete: [93; 93),
152 insert: "a",
153 kind: Field,
154 detail: "u32",
155 },
156 ]
157 "###);
158 }
159}
diff --git a/crates/ra_ide_api/src/completion/complete_record_pattern.rs b/crates/ra_ide_api/src/completion/complete_record_pattern.rs
deleted file mode 100644
index a56c7e3a1..000000000
--- a/crates/ra_ide_api/src/completion/complete_record_pattern.rs
+++ /dev/null
@@ -1,93 +0,0 @@
1//! FIXME: write short doc here
2
3use crate::completion::{CompletionContext, Completions};
4
5pub(super) fn complete_record_pattern(acc: &mut Completions, ctx: &CompletionContext) {
6 let (ty, variant) = match ctx.record_lit_pat.as_ref().and_then(|it| {
7 Some((
8 ctx.analyzer.type_of_pat(ctx.db, &it.clone().into())?,
9 ctx.analyzer.resolve_record_pattern(it)?,
10 ))
11 }) {
12 Some(it) => it,
13 _ => return,
14 };
15
16 for (field, field_ty) in ty.variant_fields(ctx.db, variant) {
17 acc.add_field(ctx, field, &field_ty);
18 }
19}
20
21#[cfg(test)]
22mod tests {
23 use crate::completion::{do_completion, CompletionItem, CompletionKind};
24 use insta::assert_debug_snapshot;
25
26 fn complete(code: &str) -> Vec<CompletionItem> {
27 do_completion(code, CompletionKind::Reference)
28 }
29
30 #[test]
31 fn test_record_pattern_field() {
32 let completions = complete(
33 r"
34 struct S { foo: u32 }
35
36 fn process(f: S) {
37 match f {
38 S { f<|>: 92 } => (),
39 }
40 }
41 ",
42 );
43 assert_debug_snapshot!(completions, @r###"
44 [
45 CompletionItem {
46 label: "foo",
47 source_range: [117; 118),
48 delete: [117; 118),
49 insert: "foo",
50 kind: Field,
51 detail: "u32",
52 },
53 ]
54 "###);
55 }
56
57 #[test]
58 fn test_record_pattern_enum_variant() {
59 let completions = complete(
60 r"
61 enum E {
62 S { foo: u32, bar: () }
63 }
64
65 fn process(e: E) {
66 match e {
67 E::S { <|> } => (),
68 }
69 }
70 ",
71 );
72 assert_debug_snapshot!(completions, @r###"
73 [
74 CompletionItem {
75 label: "bar",
76 source_range: [161; 161),
77 delete: [161; 161),
78 insert: "bar",
79 kind: Field,
80 detail: "()",
81 },
82 CompletionItem {
83 label: "foo",
84 source_range: [161; 161),
85 delete: [161; 161),
86 insert: "foo",
87 kind: Field,
88 detail: "u32",
89 },
90 ]
91 "###);
92 }
93}
diff --git a/crates/ra_ide_api/src/completion/complete_scope.rs b/crates/ra_ide_api/src/completion/complete_scope.rs
deleted file mode 100644
index d5739b58a..000000000
--- a/crates/ra_ide_api/src/completion/complete_scope.rs
+++ /dev/null
@@ -1,876 +0,0 @@
1//! FIXME: write short doc here
2
3use ra_assists::auto_import_text_edit;
4use ra_syntax::{ast, AstNode, SmolStr};
5use ra_text_edit::TextEditBuilder;
6use rustc_hash::FxHashMap;
7
8use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions};
9
10pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) {
11 if !ctx.is_trivial_path {
12 return;
13 }
14
15 ctx.analyzer.process_all_names(ctx.db, &mut |name, res| {
16 acc.add_resolution(ctx, name.to_string(), &res)
17 });
18
19 // auto-import
20 // We fetch ident from the original file, because we need to pre-filter auto-imports
21 if ast::NameRef::cast(ctx.token.parent()).is_some() {
22 let import_resolver = ImportResolver::new();
23 let import_names = import_resolver.all_names(ctx.token.text());
24 import_names.into_iter().for_each(|(name, path)| {
25 let edit = {
26 let mut builder = TextEditBuilder::default();
27 builder.replace(ctx.source_range(), name.to_string());
28 auto_import_text_edit(
29 &ctx.token.parent(),
30 &ctx.token.parent(),
31 &path,
32 &mut builder,
33 );
34 builder.finish()
35 };
36
37 // Hack: copied this check form conv.rs beacause auto import can produce edits
38 // that invalidate assert in conv_with.
39 if edit
40 .as_atoms()
41 .iter()
42 .filter(|atom| !ctx.source_range().is_subrange(&atom.delete))
43 .all(|atom| ctx.source_range().intersection(&atom.delete).is_none())
44 {
45 CompletionItem::new(
46 CompletionKind::Reference,
47 ctx.source_range(),
48 build_import_label(&name, &path),
49 )
50 .text_edit(edit)
51 .add_to(acc);
52 }
53 });
54 }
55}
56
57fn build_import_label(name: &str, path: &[SmolStr]) -> String {
58 let mut buf = String::with_capacity(64);
59 buf.push_str(name);
60 buf.push_str(" (");
61 fmt_import_path(path, &mut buf);
62 buf.push_str(")");
63 buf
64}
65
66fn fmt_import_path(path: &[SmolStr], buf: &mut String) {
67 let mut segments = path.iter();
68 if let Some(s) = segments.next() {
69 buf.push_str(&s);
70 }
71 for s in segments {
72 buf.push_str("::");
73 buf.push_str(&s);
74 }
75}
76
77#[derive(Debug, Clone, Default)]
78pub(crate) struct ImportResolver {
79 // todo: use fst crate or something like that
80 dummy_names: Vec<(SmolStr, Vec<SmolStr>)>,
81}
82
83impl ImportResolver {
84 pub(crate) fn new() -> Self {
85 let dummy_names = vec![
86 (SmolStr::new("fmt"), vec![SmolStr::new("std"), SmolStr::new("fmt")]),
87 (SmolStr::new("io"), vec![SmolStr::new("std"), SmolStr::new("io")]),
88 (SmolStr::new("iter"), vec![SmolStr::new("std"), SmolStr::new("iter")]),
89 (SmolStr::new("hash"), vec![SmolStr::new("std"), SmolStr::new("hash")]),
90 (
91 SmolStr::new("Debug"),
92 vec![SmolStr::new("std"), SmolStr::new("fmt"), SmolStr::new("Debug")],
93 ),
94 (
95 SmolStr::new("Display"),
96 vec![SmolStr::new("std"), SmolStr::new("fmt"), SmolStr::new("Display")],
97 ),
98 (
99 SmolStr::new("Hash"),
100 vec![SmolStr::new("std"), SmolStr::new("hash"), SmolStr::new("Hash")],
101 ),
102 (
103 SmolStr::new("Hasher"),
104 vec![SmolStr::new("std"), SmolStr::new("hash"), SmolStr::new("Hasher")],
105 ),
106 (
107 SmolStr::new("Iterator"),
108 vec![SmolStr::new("std"), SmolStr::new("iter"), SmolStr::new("Iterator")],
109 ),
110 ];
111
112 ImportResolver { dummy_names }
113 }
114
115 // Returns a map of importable items filtered by name.
116 // The map associates item name with its full path.
117 // todo: should return Resolutions
118 pub(crate) fn all_names(&self, name: &str) -> FxHashMap<SmolStr, Vec<SmolStr>> {
119 if name.len() > 1 {
120 self.dummy_names.iter().filter(|(n, _)| n.contains(name)).cloned().collect()
121 } else {
122 FxHashMap::default()
123 }
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use crate::completion::{do_completion, CompletionItem, CompletionKind};
130 use insta::assert_debug_snapshot;
131
132 fn do_reference_completion(code: &str) -> Vec<CompletionItem> {
133 do_completion(code, CompletionKind::Reference)
134 }
135
136 #[test]
137 fn completes_bindings_from_let() {
138 assert_debug_snapshot!(
139 do_reference_completion(
140 r"
141 fn quux(x: i32) {
142 let y = 92;
143 1 + <|>;
144 let z = ();
145 }
146 "
147 ),
148 @r###"
149 [
150 CompletionItem {
151 label: "quux(…)",
152 source_range: [91; 91),
153 delete: [91; 91),
154 insert: "quux($0)",
155 kind: Function,
156 lookup: "quux",
157 detail: "fn quux(x: i32)",
158 },
159 CompletionItem {
160 label: "x",
161 source_range: [91; 91),
162 delete: [91; 91),
163 insert: "x",
164 kind: Binding,
165 detail: "i32",
166 },
167 CompletionItem {
168 label: "y",
169 source_range: [91; 91),
170 delete: [91; 91),
171 insert: "y",
172 kind: Binding,
173 detail: "i32",
174 },
175 ]
176 "###
177 );
178 }
179
180 #[test]
181 fn completes_bindings_from_if_let() {
182 assert_debug_snapshot!(
183 do_reference_completion(
184 r"
185 fn quux() {
186 if let Some(x) = foo() {
187 let y = 92;
188 };
189 if let Some(a) = bar() {
190 let b = 62;
191 1 + <|>
192 }
193 }
194 "
195 ),
196 @r###"
197 [
198 CompletionItem {
199 label: "a",
200 source_range: [242; 242),
201 delete: [242; 242),
202 insert: "a",
203 kind: Binding,
204 },
205 CompletionItem {
206 label: "b",
207 source_range: [242; 242),
208 delete: [242; 242),
209 insert: "b",
210 kind: Binding,
211 detail: "i32",
212 },
213 CompletionItem {
214 label: "quux()",
215 source_range: [242; 242),
216 delete: [242; 242),
217 insert: "quux()$0",
218 kind: Function,
219 lookup: "quux",
220 detail: "fn quux()",
221 },
222 ]
223 "###
224 );
225 }
226
227 #[test]
228 fn completes_bindings_from_for() {
229 assert_debug_snapshot!(
230 do_reference_completion(
231 r"
232 fn quux() {
233 for x in &[1, 2, 3] {
234 <|>
235 }
236 }
237 "
238 ),
239 @r###"
240 [
241 CompletionItem {
242 label: "quux()",
243 source_range: [95; 95),
244 delete: [95; 95),
245 insert: "quux()$0",
246 kind: Function,
247 lookup: "quux",
248 detail: "fn quux()",
249 },
250 CompletionItem {
251 label: "x",
252 source_range: [95; 95),
253 delete: [95; 95),
254 insert: "x",
255 kind: Binding,
256 },
257 ]
258 "###
259 );
260 }
261
262 #[test]
263 fn completes_generic_params() {
264 assert_debug_snapshot!(
265 do_reference_completion(
266 r"
267 fn quux<T>() {
268 <|>
269 }
270 "
271 ),
272 @r###"
273 [
274 CompletionItem {
275 label: "T",
276 source_range: [52; 52),
277 delete: [52; 52),
278 insert: "T",
279 kind: TypeParam,
280 },
281 CompletionItem {
282 label: "quux()",
283 source_range: [52; 52),
284 delete: [52; 52),
285 insert: "quux()$0",
286 kind: Function,
287 lookup: "quux",
288 detail: "fn quux<T>()",
289 },
290 ]
291 "###
292 );
293 }
294
295 #[test]
296 fn completes_generic_params_in_struct() {
297 assert_debug_snapshot!(
298 do_reference_completion(
299 r"
300 struct X<T> {
301 x: <|>
302 }
303 "
304 ),
305 @r###"
306 [
307 CompletionItem {
308 label: "Self",
309 source_range: [54; 54),
310 delete: [54; 54),
311 insert: "Self",
312 kind: TypeParam,
313 },
314 CompletionItem {
315 label: "T",
316 source_range: [54; 54),
317 delete: [54; 54),
318 insert: "T",
319 kind: TypeParam,
320 },
321 CompletionItem {
322 label: "X<…>",
323 source_range: [54; 54),
324 delete: [54; 54),
325 insert: "X<$0>",
326 kind: Struct,
327 lookup: "X",
328 },
329 ]
330 "###
331 );
332 }
333
334 #[test]
335 fn completes_self_in_enum() {
336 assert_debug_snapshot!(
337 do_reference_completion(
338 r"
339 enum X {
340 Y(<|>)
341 }
342 "
343 ),
344 @r###"
345 [
346 CompletionItem {
347 label: "Self",
348 source_range: [48; 48),
349 delete: [48; 48),
350 insert: "Self",
351 kind: TypeParam,
352 },
353 CompletionItem {
354 label: "X",
355 source_range: [48; 48),
356 delete: [48; 48),
357 insert: "X",
358 kind: Enum,
359 },
360 ]
361 "###
362 );
363 }
364
365 #[test]
366 fn completes_module_items() {
367 assert_debug_snapshot!(
368 do_reference_completion(
369 r"
370 struct Foo;
371 enum Baz {}
372 fn quux() {
373 <|>
374 }
375 "
376 ),
377 @r###"
378 [
379 CompletionItem {
380 label: "Baz",
381 source_range: [105; 105),
382 delete: [105; 105),
383 insert: "Baz",
384 kind: Enum,
385 },
386 CompletionItem {
387 label: "Foo",
388 source_range: [105; 105),
389 delete: [105; 105),
390 insert: "Foo",
391 kind: Struct,
392 },
393 CompletionItem {
394 label: "quux()",
395 source_range: [105; 105),
396 delete: [105; 105),
397 insert: "quux()$0",
398 kind: Function,
399 lookup: "quux",
400 detail: "fn quux()",
401 },
402 ]
403 "###
404 );
405 }
406
407 #[test]
408 fn completes_extern_prelude() {
409 assert_debug_snapshot!(
410 do_reference_completion(
411 r"
412 //- /lib.rs
413 use <|>;
414
415 //- /other_crate/lib.rs
416 // nothing here
417 "
418 ),
419 @r###"
420 [
421 CompletionItem {
422 label: "other_crate",
423 source_range: [4; 4),
424 delete: [4; 4),
425 insert: "other_crate",
426 kind: Module,
427 },
428 ]
429 "###
430 );
431 }
432
433 #[test]
434 fn completes_module_items_in_nested_modules() {
435 assert_debug_snapshot!(
436 do_reference_completion(
437 r"
438 struct Foo;
439 mod m {
440 struct Bar;
441 fn quux() { <|> }
442 }
443 "
444 ),
445 @r###"
446 [
447 CompletionItem {
448 label: "Bar",
449 source_range: [117; 117),
450 delete: [117; 117),
451 insert: "Bar",
452 kind: Struct,
453 },
454 CompletionItem {
455 label: "quux()",
456 source_range: [117; 117),
457 delete: [117; 117),
458 insert: "quux()$0",
459 kind: Function,
460 lookup: "quux",
461 detail: "fn quux()",
462 },
463 ]
464 "###
465 );
466 }
467
468 #[test]
469 fn completes_return_type() {
470 assert_debug_snapshot!(
471 do_reference_completion(
472 r"
473 struct Foo;
474 fn x() -> <|>
475 "
476 ),
477 @r###"
478 [
479 CompletionItem {
480 label: "Foo",
481 source_range: [55; 55),
482 delete: [55; 55),
483 insert: "Foo",
484 kind: Struct,
485 },
486 CompletionItem {
487 label: "x()",
488 source_range: [55; 55),
489 delete: [55; 55),
490 insert: "x()$0",
491 kind: Function,
492 lookup: "x",
493 detail: "fn x()",
494 },
495 ]
496 "###
497 );
498 }
499
500 #[test]
501 fn dont_show_both_completions_for_shadowing() {
502 assert_debug_snapshot!(
503 do_reference_completion(
504 r"
505 fn foo() {
506 let bar = 92;
507 {
508 let bar = 62;
509 <|>
510 }
511 }
512 "
513 ),
514 @r###"
515 [
516 CompletionItem {
517 label: "bar",
518 source_range: [146; 146),
519 delete: [146; 146),
520 insert: "bar",
521 kind: Binding,
522 detail: "i32",
523 },
524 CompletionItem {
525 label: "foo()",
526 source_range: [146; 146),
527 delete: [146; 146),
528 insert: "foo()$0",
529 kind: Function,
530 lookup: "foo",
531 detail: "fn foo()",
532 },
533 ]
534 "###
535 );
536 }
537
538 #[test]
539 fn completes_self_in_methods() {
540 assert_debug_snapshot!(
541 do_reference_completion(r"impl S { fn foo(&self) { <|> } }"),
542 @r###"
543 [
544 CompletionItem {
545 label: "Self",
546 source_range: [25; 25),
547 delete: [25; 25),
548 insert: "Self",
549 kind: TypeParam,
550 },
551 CompletionItem {
552 label: "self",
553 source_range: [25; 25),
554 delete: [25; 25),
555 insert: "self",
556 kind: Binding,
557 detail: "&{unknown}",
558 },
559 ]
560 "###
561 );
562 }
563
564 #[test]
565 fn completes_prelude() {
566 assert_debug_snapshot!(
567 do_reference_completion(
568 "
569 //- /main.rs
570 fn foo() { let x: <|> }
571
572 //- /std/lib.rs
573 #[prelude_import]
574 use prelude::*;
575
576 mod prelude {
577 struct Option;
578 }
579 "
580 ),
581 @r###"
582 [
583 CompletionItem {
584 label: "Option",
585 source_range: [18; 18),
586 delete: [18; 18),
587 insert: "Option",
588 kind: Struct,
589 },
590 CompletionItem {
591 label: "foo()",
592 source_range: [18; 18),
593 delete: [18; 18),
594 insert: "foo()$0",
595 kind: Function,
596 lookup: "foo",
597 detail: "fn foo()",
598 },
599 CompletionItem {
600 label: "std",
601 source_range: [18; 18),
602 delete: [18; 18),
603 insert: "std",
604 kind: Module,
605 },
606 ]
607 "###
608 );
609 }
610
611 #[test]
612 fn completes_std_prelude_if_core_is_defined() {
613 assert_debug_snapshot!(
614 do_reference_completion(
615 "
616 //- /main.rs
617 fn foo() { let x: <|> }
618
619 //- /core/lib.rs
620 #[prelude_import]
621 use prelude::*;
622
623 mod prelude {
624 struct Option;
625 }
626
627 //- /std/lib.rs
628 #[prelude_import]
629 use prelude::*;
630
631 mod prelude {
632 struct String;
633 }
634 "
635 ),
636 @r###"
637 [
638 CompletionItem {
639 label: "String",
640 source_range: [18; 18),
641 delete: [18; 18),
642 insert: "String",
643 kind: Struct,
644 },
645 CompletionItem {
646 label: "core",
647 source_range: [18; 18),
648 delete: [18; 18),
649 insert: "core",
650 kind: Module,
651 },
652 CompletionItem {
653 label: "foo()",
654 source_range: [18; 18),
655 delete: [18; 18),
656 insert: "foo()$0",
657 kind: Function,
658 lookup: "foo",
659 detail: "fn foo()",
660 },
661 CompletionItem {
662 label: "std",
663 source_range: [18; 18),
664 delete: [18; 18),
665 insert: "std",
666 kind: Module,
667 },
668 ]
669 "###
670 );
671 }
672
673 #[test]
674 fn completes_macros_as_value() {
675 assert_debug_snapshot!(
676 do_reference_completion(
677 "
678 //- /main.rs
679 macro_rules! foo {
680 () => {}
681 }
682
683 #[macro_use]
684 mod m1 {
685 macro_rules! bar {
686 () => {}
687 }
688 }
689
690 mod m2 {
691 macro_rules! nope {
692 () => {}
693 }
694
695 #[macro_export]
696 macro_rules! baz {
697 () => {}
698 }
699 }
700
701 fn main() {
702 let v = <|>
703 }
704 "
705 ),
706 @r###"
707 [
708 CompletionItem {
709 label: "bar!",
710 source_range: [252; 252),
711 delete: [252; 252),
712 insert: "bar!($0)",
713 kind: Macro,
714 detail: "macro_rules! bar",
715 },
716 CompletionItem {
717 label: "baz!",
718 source_range: [252; 252),
719 delete: [252; 252),
720 insert: "baz!($0)",
721 kind: Macro,
722 detail: "#[macro_export]\nmacro_rules! baz",
723 },
724 CompletionItem {
725 label: "foo!",
726 source_range: [252; 252),
727 delete: [252; 252),
728 insert: "foo!($0)",
729 kind: Macro,
730 detail: "macro_rules! foo",
731 },
732 CompletionItem {
733 label: "m1",
734 source_range: [252; 252),
735 delete: [252; 252),
736 insert: "m1",
737 kind: Module,
738 },
739 CompletionItem {
740 label: "m2",
741 source_range: [252; 252),
742 delete: [252; 252),
743 insert: "m2",
744 kind: Module,
745 },
746 CompletionItem {
747 label: "main()",
748 source_range: [252; 252),
749 delete: [252; 252),
750 insert: "main()$0",
751 kind: Function,
752 lookup: "main",
753 detail: "fn main()",
754 },
755 ]
756 "###
757 );
758 }
759
760 #[test]
761 fn completes_both_macro_and_value() {
762 assert_debug_snapshot!(
763 do_reference_completion(
764 "
765 //- /main.rs
766 macro_rules! foo {
767 () => {}
768 }
769
770 fn foo() {
771 <|>
772 }
773 "
774 ),
775 @r###"
776 [
777 CompletionItem {
778 label: "foo!",
779 source_range: [49; 49),
780 delete: [49; 49),
781 insert: "foo!($0)",
782 kind: Macro,
783 detail: "macro_rules! foo",
784 },
785 CompletionItem {
786 label: "foo()",
787 source_range: [49; 49),
788 delete: [49; 49),
789 insert: "foo()$0",
790 kind: Function,
791 lookup: "foo",
792 detail: "fn foo()",
793 },
794 ]
795 "###
796 );
797 }
798
799 #[test]
800 fn completes_macros_as_type() {
801 assert_debug_snapshot!(
802 do_reference_completion(
803 "
804 //- /main.rs
805 macro_rules! foo {
806 () => {}
807 }
808
809 fn main() {
810 let x: <|>
811 }
812 "
813 ),
814 @r###"
815 [
816 CompletionItem {
817 label: "foo!",
818 source_range: [57; 57),
819 delete: [57; 57),
820 insert: "foo!($0)",
821 kind: Macro,
822 detail: "macro_rules! foo",
823 },
824 CompletionItem {
825 label: "main()",
826 source_range: [57; 57),
827 delete: [57; 57),
828 insert: "main()$0",
829 kind: Function,
830 lookup: "main",
831 detail: "fn main()",
832 },
833 ]
834 "###
835 );
836 }
837
838 #[test]
839 fn completes_macros_as_stmt() {
840 assert_debug_snapshot!(
841 do_reference_completion(
842 "
843 //- /main.rs
844 macro_rules! foo {
845 () => {}
846 }
847
848 fn main() {
849 <|>
850 }
851 "
852 ),
853 @r###"
854 [
855 CompletionItem {
856 label: "foo!",
857 source_range: [50; 50),
858 delete: [50; 50),
859 insert: "foo!($0)",
860 kind: Macro,
861 detail: "macro_rules! foo",
862 },
863 CompletionItem {
864 label: "main()",
865 source_range: [50; 50),
866 delete: [50; 50),
867 insert: "main()$0",
868 kind: Function,
869 lookup: "main",
870 detail: "fn main()",
871 },
872 ]
873 "###
874 );
875 }
876}
diff --git a/crates/ra_ide_api/src/completion/complete_snippet.rs b/crates/ra_ide_api/src/completion/complete_snippet.rs
deleted file mode 100644
index 1f2988b36..000000000
--- a/crates/ra_ide_api/src/completion/complete_snippet.rs
+++ /dev/null
@@ -1,120 +0,0 @@
1//! FIXME: write short doc here
2
3use crate::completion::{
4 completion_item::Builder, CompletionContext, CompletionItem, CompletionItemKind,
5 CompletionKind, Completions,
6};
7
8fn snippet(ctx: &CompletionContext, label: &str, snippet: &str) -> Builder {
9 CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), label)
10 .insert_snippet(snippet)
11 .kind(CompletionItemKind::Snippet)
12}
13
14pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) {
15 if !(ctx.is_trivial_path && ctx.function_syntax.is_some()) {
16 return;
17 }
18
19 snippet(ctx, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
20 snippet(ctx, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
21}
22
23pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
24 if !ctx.is_new_item {
25 return;
26 }
27 snippet(
28 ctx,
29 "Test function",
30 "\
31#[test]
32fn ${1:feature}() {
33 $0
34}",
35 )
36 .lookup_by("tfn")
37 .add_to(acc);
38
39 snippet(ctx, "pub(crate)", "pub(crate) $0").add_to(acc);
40}
41
42#[cfg(test)]
43mod tests {
44 use crate::completion::{do_completion, CompletionItem, CompletionKind};
45 use insta::assert_debug_snapshot;
46
47 fn do_snippet_completion(code: &str) -> Vec<CompletionItem> {
48 do_completion(code, CompletionKind::Snippet)
49 }
50
51 #[test]
52 fn completes_snippets_in_expressions() {
53 assert_debug_snapshot!(
54 do_snippet_completion(r"fn foo(x: i32) { <|> }"),
55 @r###"
56 [
57 CompletionItem {
58 label: "pd",
59 source_range: [17; 17),
60 delete: [17; 17),
61 insert: "eprintln!(\"$0 = {:?}\", $0);",
62 kind: Snippet,
63 },
64 CompletionItem {
65 label: "ppd",
66 source_range: [17; 17),
67 delete: [17; 17),
68 insert: "eprintln!(\"$0 = {:#?}\", $0);",
69 kind: Snippet,
70 },
71 ]
72 "###
73 );
74 }
75
76 #[test]
77 fn should_not_complete_snippets_in_path() {
78 assert_debug_snapshot!(
79 do_snippet_completion(r"fn foo(x: i32) { ::foo<|> }"),
80 @"[]"
81 );
82 assert_debug_snapshot!(
83 do_snippet_completion(r"fn foo(x: i32) { ::<|> }"),
84 @"[]"
85 );
86 }
87
88 #[test]
89 fn completes_snippets_in_items() {
90 assert_debug_snapshot!(
91 do_snippet_completion(
92 r"
93 #[cfg(test)]
94 mod tests {
95 <|>
96 }
97 "
98 ),
99 @r###"
100 [
101 CompletionItem {
102 label: "Test function",
103 source_range: [78; 78),
104 delete: [78; 78),
105 insert: "#[test]\nfn ${1:feature}() {\n $0\n}",
106 kind: Snippet,
107 lookup: "tfn",
108 },
109 CompletionItem {
110 label: "pub(crate)",
111 source_range: [78; 78),
112 delete: [78; 78),
113 insert: "pub(crate) $0",
114 kind: Snippet,
115 },
116 ]
117 "###
118 );
119 }
120}
diff --git a/crates/ra_ide_api/src/completion/completion_context.rs b/crates/ra_ide_api/src/completion/completion_context.rs
deleted file mode 100644
index b8345c91d..000000000
--- a/crates/ra_ide_api/src/completion/completion_context.rs
+++ /dev/null
@@ -1,274 +0,0 @@
1//! FIXME: write short doc here
2
3use ra_syntax::{
4 algo::{find_covering_element, find_node_at_offset},
5 ast, AstNode, Parse, SourceFile,
6 SyntaxKind::*,
7 SyntaxNode, SyntaxToken, TextRange, TextUnit,
8};
9use ra_text_edit::AtomTextEdit;
10
11use crate::{db, FilePosition};
12
13/// `CompletionContext` is created early during completion to figure out, where
14/// exactly is the cursor, syntax-wise.
15#[derive(Debug)]
16pub(crate) struct CompletionContext<'a> {
17 pub(super) db: &'a db::RootDatabase,
18 pub(super) analyzer: hir::SourceAnalyzer,
19 pub(super) offset: TextUnit,
20 pub(super) token: SyntaxToken,
21 pub(super) module: Option<hir::Module>,
22 pub(super) function_syntax: Option<ast::FnDef>,
23 pub(super) use_item_syntax: Option<ast::UseItem>,
24 pub(super) record_lit_syntax: Option<ast::RecordLit>,
25 pub(super) record_lit_pat: Option<ast::RecordPat>,
26 pub(super) is_param: bool,
27 /// If a name-binding or reference to a const in a pattern.
28 /// Irrefutable patterns (like let) are excluded.
29 pub(super) is_pat_binding: bool,
30 /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path.
31 pub(super) is_trivial_path: bool,
32 /// If not a trivial path, the prefix (qualifier).
33 pub(super) path_prefix: Option<hir::Path>,
34 pub(super) after_if: bool,
35 /// `true` if we are a statement or a last expr in the block.
36 pub(super) can_be_stmt: bool,
37 /// Something is typed at the "top" level, in module or impl/trait.
38 pub(super) is_new_item: bool,
39 /// The receiver if this is a field or method access, i.e. writing something.<|>
40 pub(super) dot_receiver: Option<ast::Expr>,
41 pub(super) dot_receiver_is_ambiguous_float_literal: bool,
42 /// If this is a call (method or function) in particular, i.e. the () are already there.
43 pub(super) is_call: bool,
44 pub(super) is_path_type: bool,
45 pub(super) has_type_args: bool,
46}
47
48impl<'a> CompletionContext<'a> {
49 pub(super) fn new(
50 db: &'a db::RootDatabase,
51 original_parse: &'a Parse<ast::SourceFile>,
52 position: FilePosition,
53 ) -> Option<CompletionContext<'a>> {
54 let src = hir::ModuleSource::from_position(db, position);
55 let module = hir::Module::from_definition(
56 db,
57 hir::Source { file_id: position.file_id.into(), value: src },
58 );
59 let token =
60 original_parse.tree().syntax().token_at_offset(position.offset).left_biased()?;
61 let analyzer = hir::SourceAnalyzer::new(
62 db,
63 hir::Source::new(position.file_id.into(), &token.parent()),
64 Some(position.offset),
65 );
66 let mut ctx = CompletionContext {
67 db,
68 analyzer,
69 token,
70 offset: position.offset,
71 module,
72 function_syntax: None,
73 use_item_syntax: None,
74 record_lit_syntax: None,
75 record_lit_pat: None,
76 is_param: false,
77 is_pat_binding: false,
78 is_trivial_path: false,
79 path_prefix: None,
80 after_if: false,
81 can_be_stmt: false,
82 is_new_item: false,
83 dot_receiver: None,
84 is_call: false,
85 is_path_type: false,
86 has_type_args: false,
87 dot_receiver_is_ambiguous_float_literal: false,
88 };
89 ctx.fill(&original_parse, position.offset);
90 Some(ctx)
91 }
92
93 // The range of the identifier that is being completed.
94 pub(crate) fn source_range(&self) -> TextRange {
95 match self.token.kind() {
96 // workaroud when completion is triggered by trigger characters.
97 IDENT => self.token.text_range(),
98 _ => TextRange::offset_len(self.offset, 0.into()),
99 }
100 }
101
102 fn fill(&mut self, original_parse: &'a Parse<ast::SourceFile>, offset: TextUnit) {
103 // Insert a fake ident to get a valid parse tree. We will use this file
104 // to determine context, though the original_file will be used for
105 // actual completion.
106 let file = {
107 let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string());
108 original_parse.reparse(&edit).tree()
109 };
110
111 // First, let's try to complete a reference to some declaration.
112 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) {
113 // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
114 // See RFC#1685.
115 if is_node::<ast::Param>(name_ref.syntax()) {
116 self.is_param = true;
117 return;
118 }
119 self.classify_name_ref(original_parse.tree(), name_ref);
120 }
121
122 // Otherwise, see if this is a declaration. We can use heuristics to
123 // suggest declaration names, see `CompletionKind::Magic`.
124 if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) {
125 if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) {
126 let parent = bind_pat.syntax().parent();
127 if parent.clone().and_then(ast::MatchArm::cast).is_some()
128 || parent.and_then(ast::Condition::cast).is_some()
129 {
130 self.is_pat_binding = true;
131 }
132 }
133 if is_node::<ast::Param>(name.syntax()) {
134 self.is_param = true;
135 return;
136 }
137 if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() {
138 self.record_lit_pat =
139 find_node_at_offset(original_parse.tree().syntax(), self.offset);
140 }
141 }
142 }
143
144 fn classify_name_ref(&mut self, original_file: SourceFile, name_ref: ast::NameRef) {
145 let name_range = name_ref.syntax().text_range();
146 if name_ref.syntax().parent().and_then(ast::RecordField::cast).is_some() {
147 self.record_lit_syntax = find_node_at_offset(original_file.syntax(), self.offset);
148 }
149
150 let top_node = name_ref
151 .syntax()
152 .ancestors()
153 .take_while(|it| it.text_range() == name_range)
154 .last()
155 .unwrap();
156
157 match top_node.parent().map(|it| it.kind()) {
158 Some(SOURCE_FILE) | Some(ITEM_LIST) => {
159 self.is_new_item = true;
160 return;
161 }
162 _ => (),
163 }
164
165 self.use_item_syntax = self.token.parent().ancestors().find_map(ast::UseItem::cast);
166
167 self.function_syntax = self
168 .token
169 .parent()
170 .ancestors()
171 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
172 .find_map(ast::FnDef::cast);
173
174 let parent = match name_ref.syntax().parent() {
175 Some(it) => it,
176 None => return,
177 };
178
179 if let Some(segment) = ast::PathSegment::cast(parent.clone()) {
180 let path = segment.parent_path();
181 self.is_call = path
182 .syntax()
183 .parent()
184 .and_then(ast::PathExpr::cast)
185 .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast))
186 .is_some();
187
188 self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some();
189 self.has_type_args = segment.type_arg_list().is_some();
190
191 if let Some(mut path) = hir::Path::from_ast(path.clone()) {
192 if !path.is_ident() {
193 path.segments.pop().unwrap();
194 self.path_prefix = Some(path);
195 return;
196 }
197 }
198
199 if path.qualifier().is_none() {
200 self.is_trivial_path = true;
201
202 // Find either enclosing expr statement (thing with `;`) or a
203 // block. If block, check that we are the last expr.
204 self.can_be_stmt = name_ref
205 .syntax()
206 .ancestors()
207 .find_map(|node| {
208 if let Some(stmt) = ast::ExprStmt::cast(node.clone()) {
209 return Some(
210 stmt.syntax().text_range() == name_ref.syntax().text_range(),
211 );
212 }
213 if let Some(block) = ast::Block::cast(node) {
214 return Some(
215 block.expr().map(|e| e.syntax().text_range())
216 == Some(name_ref.syntax().text_range()),
217 );
218 }
219 None
220 })
221 .unwrap_or(false);
222
223 if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) {
224 if let Some(if_expr) =
225 find_node_at_offset::<ast::IfExpr>(original_file.syntax(), off)
226 {
227 if if_expr.syntax().text_range().end()
228 < name_ref.syntax().text_range().start()
229 {
230 self.after_if = true;
231 }
232 }
233 }
234 }
235 }
236 if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
237 // The receiver comes before the point of insertion of the fake
238 // ident, so it should have the same range in the non-modified file
239 self.dot_receiver = field_expr
240 .expr()
241 .map(|e| e.syntax().text_range())
242 .and_then(|r| find_node_with_range(original_file.syntax(), r));
243 self.dot_receiver_is_ambiguous_float_literal = if let Some(ast::Expr::Literal(l)) =
244 &self.dot_receiver
245 {
246 match l.kind() {
247 ast::LiteralKind::FloatNumber { suffix: _ } => l.token().text().ends_with('.'),
248 _ => false,
249 }
250 } else {
251 false
252 }
253 }
254 if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) {
255 // As above
256 self.dot_receiver = method_call_expr
257 .expr()
258 .map(|e| e.syntax().text_range())
259 .and_then(|r| find_node_with_range(original_file.syntax(), r));
260 self.is_call = true;
261 }
262 }
263}
264
265fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> {
266 find_covering_element(syntax, range).ancestors().find_map(N::cast)
267}
268
269fn is_node<N: AstNode>(node: &SyntaxNode) -> bool {
270 match node.ancestors().find_map(N::cast) {
271 None => false,
272 Some(n) => n.syntax().text_range() == node.text_range(),
273 }
274}
diff --git a/crates/ra_ide_api/src/completion/completion_item.rs b/crates/ra_ide_api/src/completion/completion_item.rs
deleted file mode 100644
index 93f336370..000000000
--- a/crates/ra_ide_api/src/completion/completion_item.rs
+++ /dev/null
@@ -1,322 +0,0 @@
1//! FIXME: write short doc here
2
3use std::fmt;
4
5use hir::Documentation;
6use ra_syntax::TextRange;
7use ra_text_edit::TextEdit;
8
9/// `CompletionItem` describes a single completion variant in the editor pop-up.
10/// It is basically a POD with various properties. To construct a
11/// `CompletionItem`, use `new` method and the `Builder` struct.
12pub struct CompletionItem {
13 /// Used only internally in tests, to check only specific kind of
14 /// completion (postfix, keyword, reference, etc).
15 #[allow(unused)]
16 completion_kind: CompletionKind,
17 /// Label in the completion pop up which identifies completion.
18 label: String,
19 /// Range of identifier that is being completed.
20 ///
21 /// It should be used primarily for UI, but we also use this to convert
22 /// genetic TextEdit into LSP's completion edit (see conv.rs).
23 ///
24 /// `source_range` must contain the completion offset. `insert_text` should
25 /// start with what `source_range` points to, or VSCode will filter out the
26 /// completion silently.
27 source_range: TextRange,
28 /// What happens when user selects this item.
29 ///
30 /// Typically, replaces `source_range` with new identifier.
31 text_edit: TextEdit,
32 insert_text_format: InsertTextFormat,
33
34 /// What item (struct, function, etc) are we completing.
35 kind: Option<CompletionItemKind>,
36
37 /// Lookup is used to check if completion item indeed can complete current
38 /// ident.
39 ///
40 /// That is, in `foo.bar<|>` lookup of `abracadabra` will be accepted (it
41 /// contains `bar` sub sequence), and `quux` will rejected.
42 lookup: Option<String>,
43
44 /// Additional info to show in the UI pop up.
45 detail: Option<String>,
46 documentation: Option<Documentation>,
47
48 /// Whether this item is marked as deprecated
49 deprecated: bool,
50}
51
52// We use custom debug for CompletionItem to make `insta`'s diffs more readable.
53impl fmt::Debug for CompletionItem {
54 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
55 let mut s = f.debug_struct("CompletionItem");
56 s.field("label", &self.label()).field("source_range", &self.source_range());
57 if self.text_edit().as_atoms().len() == 1 {
58 let atom = &self.text_edit().as_atoms()[0];
59 s.field("delete", &atom.delete);
60 s.field("insert", &atom.insert);
61 } else {
62 s.field("text_edit", &self.text_edit);
63 }
64 if let Some(kind) = self.kind().as_ref() {
65 s.field("kind", kind);
66 }
67 if self.lookup() != self.label() {
68 s.field("lookup", &self.lookup());
69 }
70 if let Some(detail) = self.detail() {
71 s.field("detail", &detail);
72 }
73 if let Some(documentation) = self.documentation() {
74 s.field("documentation", &documentation);
75 }
76 if self.deprecated {
77 s.field("deprecated", &true);
78 }
79 s.finish()
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum CompletionItemKind {
85 Snippet,
86 Keyword,
87 Module,
88 Function,
89 BuiltinType,
90 Struct,
91 Enum,
92 EnumVariant,
93 Binding,
94 Field,
95 Static,
96 Const,
97 Trait,
98 TypeAlias,
99 Method,
100 TypeParam,
101 Macro,
102}
103
104#[derive(Debug, PartialEq, Eq, Copy, Clone)]
105pub(crate) enum CompletionKind {
106 /// Parser-based keyword completion.
107 Keyword,
108 /// Your usual "complete all valid identifiers".
109 Reference,
110 /// "Secret sauce" completions.
111 Magic,
112 Snippet,
113 Postfix,
114 BuiltinType,
115}
116
117#[derive(Debug, PartialEq, Eq, Copy, Clone)]
118pub enum InsertTextFormat {
119 PlainText,
120 Snippet,
121}
122
123impl CompletionItem {
124 pub(crate) fn new(
125 completion_kind: CompletionKind,
126 source_range: TextRange,
127 label: impl Into<String>,
128 ) -> Builder {
129 let label = label.into();
130 Builder {
131 source_range,
132 completion_kind,
133 label,
134 insert_text: None,
135 insert_text_format: InsertTextFormat::PlainText,
136 detail: None,
137 documentation: None,
138 lookup: None,
139 kind: None,
140 text_edit: None,
141 deprecated: None,
142 }
143 }
144 /// What user sees in pop-up in the UI.
145 pub fn label(&self) -> &str {
146 &self.label
147 }
148 pub fn source_range(&self) -> TextRange {
149 self.source_range
150 }
151
152 pub fn insert_text_format(&self) -> InsertTextFormat {
153 self.insert_text_format
154 }
155
156 pub fn text_edit(&self) -> &TextEdit {
157 &self.text_edit
158 }
159
160 /// Short one-line additional information, like a type
161 pub fn detail(&self) -> Option<&str> {
162 self.detail.as_ref().map(|it| it.as_str())
163 }
164 /// A doc-comment
165 pub fn documentation(&self) -> Option<Documentation> {
166 self.documentation.clone()
167 }
168 /// What string is used for filtering.
169 pub fn lookup(&self) -> &str {
170 self.lookup.as_ref().map(|it| it.as_str()).unwrap_or_else(|| self.label())
171 }
172
173 pub fn kind(&self) -> Option<CompletionItemKind> {
174 self.kind
175 }
176
177 pub fn deprecated(&self) -> bool {
178 self.deprecated
179 }
180}
181
182/// A helper to make `CompletionItem`s.
183#[must_use]
184pub(crate) struct Builder {
185 source_range: TextRange,
186 completion_kind: CompletionKind,
187 label: String,
188 insert_text: Option<String>,
189 insert_text_format: InsertTextFormat,
190 detail: Option<String>,
191 documentation: Option<Documentation>,
192 lookup: Option<String>,
193 kind: Option<CompletionItemKind>,
194 text_edit: Option<TextEdit>,
195 deprecated: Option<bool>,
196}
197
198impl Builder {
199 pub(crate) fn add_to(self, acc: &mut Completions) {
200 acc.add(self.build())
201 }
202
203 pub(crate) fn build(self) -> CompletionItem {
204 let label = self.label;
205 let text_edit = match self.text_edit {
206 Some(it) => it,
207 None => TextEdit::replace(
208 self.source_range,
209 self.insert_text.unwrap_or_else(|| label.clone()),
210 ),
211 };
212
213 CompletionItem {
214 source_range: self.source_range,
215 label,
216 insert_text_format: self.insert_text_format,
217 text_edit,
218 detail: self.detail,
219 documentation: self.documentation,
220 lookup: self.lookup,
221 kind: self.kind,
222 completion_kind: self.completion_kind,
223 deprecated: self.deprecated.unwrap_or(false),
224 }
225 }
226 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
227 self.lookup = Some(lookup.into());
228 self
229 }
230 pub(crate) fn label(mut self, label: impl Into<String>) -> Builder {
231 self.label = label.into();
232 self
233 }
234 pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder {
235 self.insert_text = Some(insert_text.into());
236 self
237 }
238 pub(crate) fn insert_snippet(mut self, snippet: impl Into<String>) -> Builder {
239 self.insert_text_format = InsertTextFormat::Snippet;
240 self.insert_text(snippet)
241 }
242 pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder {
243 self.kind = Some(kind);
244 self
245 }
246 pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder {
247 self.text_edit = Some(edit);
248 self
249 }
250 pub(crate) fn snippet_edit(mut self, edit: TextEdit) -> Builder {
251 self.insert_text_format = InsertTextFormat::Snippet;
252 self.text_edit(edit)
253 }
254 #[allow(unused)]
255 pub(crate) fn detail(self, detail: impl Into<String>) -> Builder {
256 self.set_detail(Some(detail))
257 }
258 pub(crate) fn set_detail(mut self, detail: Option<impl Into<String>>) -> Builder {
259 self.detail = detail.map(Into::into);
260 self
261 }
262 #[allow(unused)]
263 pub(crate) fn documentation(self, docs: Documentation) -> Builder {
264 self.set_documentation(Some(docs))
265 }
266 pub(crate) fn set_documentation(mut self, docs: Option<Documentation>) -> Builder {
267 self.documentation = docs.map(Into::into);
268 self
269 }
270 pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder {
271 self.deprecated = Some(deprecated);
272 self
273 }
274}
275
276impl<'a> Into<CompletionItem> for Builder {
277 fn into(self) -> CompletionItem {
278 self.build()
279 }
280}
281
282/// Represents an in-progress set of completions being built.
283#[derive(Debug, Default)]
284pub(crate) struct Completions {
285 buf: Vec<CompletionItem>,
286}
287
288impl Completions {
289 pub(crate) fn add(&mut self, item: impl Into<CompletionItem>) {
290 self.buf.push(item.into())
291 }
292 pub(crate) fn add_all<I>(&mut self, items: I)
293 where
294 I: IntoIterator,
295 I::Item: Into<CompletionItem>,
296 {
297 items.into_iter().for_each(|item| self.add(item.into()))
298 }
299}
300
301impl Into<Vec<CompletionItem>> for Completions {
302 fn into(self) -> Vec<CompletionItem> {
303 self.buf
304 }
305}
306
307#[cfg(test)]
308pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
309 use crate::completion::completions;
310 use crate::mock_analysis::{analysis_and_position, single_file_with_position};
311 let (analysis, position) = if code.contains("//-") {
312 analysis_and_position(code)
313 } else {
314 single_file_with_position(code)
315 };
316 let completions = completions(&analysis.db, position).unwrap();
317 let completion_items: Vec<CompletionItem> = completions.into();
318 let mut kind_completions: Vec<CompletionItem> =
319 completion_items.into_iter().filter(|c| c.completion_kind == kind).collect();
320 kind_completions.sort_by_key(|c| c.label.clone());
321 kind_completions
322}
diff --git a/crates/ra_ide_api/src/completion/presentation.rs b/crates/ra_ide_api/src/completion/presentation.rs
deleted file mode 100644
index 5f056730a..000000000
--- a/crates/ra_ide_api/src/completion/presentation.rs
+++ /dev/null
@@ -1,676 +0,0 @@
1//! This modules takes care of rendering various definitions as completion items.
2
3use hir::{db::HirDatabase, Docs, HasAttrs, HasSource, HirDisplay, ScopeDef, Type};
4use join_to_string::join;
5use ra_syntax::ast::NameOwner;
6use test_utils::tested_by;
7
8use crate::completion::{
9 CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions,
10};
11
12use crate::display::{const_label, function_label, macro_label, type_label};
13
14impl Completions {
15 pub(crate) fn add_field(
16 &mut self,
17 ctx: &CompletionContext,
18 field: hir::StructField,
19 ty: &Type,
20 ) {
21 let is_deprecated = is_deprecated(field, ctx.db);
22 CompletionItem::new(
23 CompletionKind::Reference,
24 ctx.source_range(),
25 field.name(ctx.db).to_string(),
26 )
27 .kind(CompletionItemKind::Field)
28 .detail(ty.display(ctx.db).to_string())
29 .set_documentation(field.docs(ctx.db))
30 .set_deprecated(is_deprecated)
31 .add_to(self);
32 }
33
34 pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) {
35 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), field.to_string())
36 .kind(CompletionItemKind::Field)
37 .detail(ty.display(ctx.db).to_string())
38 .add_to(self);
39 }
40
41 pub(crate) fn add_resolution(
42 &mut self,
43 ctx: &CompletionContext,
44 local_name: String,
45 resolution: &ScopeDef,
46 ) {
47 use hir::ModuleDef::*;
48
49 let completion_kind = match resolution {
50 ScopeDef::ModuleDef(BuiltinType(..)) => CompletionKind::BuiltinType,
51 _ => CompletionKind::Reference,
52 };
53
54 let kind = match resolution {
55 ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::Module,
56 ScopeDef::ModuleDef(Function(func)) => {
57 return self.add_function_with_name(ctx, Some(local_name), *func);
58 }
59 ScopeDef::ModuleDef(Adt(hir::Adt::Struct(_))) => CompletionItemKind::Struct,
60 // FIXME: add CompletionItemKind::Union
61 ScopeDef::ModuleDef(Adt(hir::Adt::Union(_))) => CompletionItemKind::Struct,
62 ScopeDef::ModuleDef(Adt(hir::Adt::Enum(_))) => CompletionItemKind::Enum,
63
64 ScopeDef::ModuleDef(EnumVariant(..)) => CompletionItemKind::EnumVariant,
65 ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::Const,
66 ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::Static,
67 ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::Trait,
68 ScopeDef::ModuleDef(TypeAlias(..)) => CompletionItemKind::TypeAlias,
69 ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType,
70 ScopeDef::GenericParam(..) => CompletionItemKind::TypeParam,
71 ScopeDef::Local(..) => CompletionItemKind::Binding,
72 // (does this need its own kind?)
73 ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => CompletionItemKind::TypeParam,
74 ScopeDef::MacroDef(mac) => {
75 return self.add_macro(ctx, Some(local_name), *mac);
76 }
77 ScopeDef::Unknown => {
78 return self.add(CompletionItem::new(
79 CompletionKind::Reference,
80 ctx.source_range(),
81 local_name,
82 ));
83 }
84 };
85
86 let docs = match resolution {
87 ScopeDef::ModuleDef(Module(it)) => it.docs(ctx.db),
88 ScopeDef::ModuleDef(Adt(it)) => it.docs(ctx.db),
89 ScopeDef::ModuleDef(EnumVariant(it)) => it.docs(ctx.db),
90 ScopeDef::ModuleDef(Const(it)) => it.docs(ctx.db),
91 ScopeDef::ModuleDef(Static(it)) => it.docs(ctx.db),
92 ScopeDef::ModuleDef(Trait(it)) => it.docs(ctx.db),
93 ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(ctx.db),
94 _ => None,
95 };
96
97 let mut completion_item =
98 CompletionItem::new(completion_kind, ctx.source_range(), local_name.clone());
99 if let ScopeDef::Local(local) = resolution {
100 let ty = local.ty(ctx.db);
101 if !ty.is_unknown() {
102 completion_item = completion_item.detail(ty.display(ctx.db).to_string());
103 }
104 };
105
106 // If not an import, add parenthesis automatically.
107 if ctx.is_path_type
108 && !ctx.has_type_args
109 && ctx.db.feature_flags.get("completion.insertion.add-call-parenthesis")
110 {
111 let has_non_default_type_params = match resolution {
112 ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db),
113 ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db),
114 _ => false,
115 };
116 if has_non_default_type_params {
117 tested_by!(inserts_angle_brackets_for_generics);
118 completion_item = completion_item
119 .lookup_by(local_name.clone())
120 .label(format!("{}<…>", local_name))
121 .insert_snippet(format!("{}<$0>", local_name));
122 }
123 }
124
125 completion_item.kind(kind).set_documentation(docs).add_to(self)
126 }
127
128 pub(crate) fn add_function(&mut self, ctx: &CompletionContext, func: hir::Function) {
129 self.add_function_with_name(ctx, None, func)
130 }
131
132 fn guess_macro_braces(&self, macro_name: &str, docs: &str) -> &'static str {
133 let mut votes = [0, 0, 0];
134 for (idx, s) in docs.match_indices(&macro_name) {
135 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
136 // Ensure to match the full word
137 if after.starts_with('!')
138 && before
139 .chars()
140 .rev()
141 .next()
142 .map_or(true, |c| c != '_' && !c.is_ascii_alphanumeric())
143 {
144 // It may have spaces before the braces like `foo! {}`
145 match after[1..].chars().find(|&c| !c.is_whitespace()) {
146 Some('{') => votes[0] += 1,
147 Some('[') => votes[1] += 1,
148 Some('(') => votes[2] += 1,
149 _ => {}
150 }
151 }
152 }
153
154 // Insert a space before `{}`.
155 // We prefer the last one when some votes equal.
156 *votes.iter().zip(&[" {$0}", "[$0]", "($0)"]).max_by_key(|&(&vote, _)| vote).unwrap().1
157 }
158
159 pub(crate) fn add_macro(
160 &mut self,
161 ctx: &CompletionContext,
162 name: Option<String>,
163 macro_: hir::MacroDef,
164 ) {
165 let name = match name {
166 Some(it) => it,
167 None => return,
168 };
169
170 let ast_node = macro_.source(ctx.db).value;
171 let detail = macro_label(&ast_node);
172
173 let docs = macro_.docs(ctx.db);
174 let macro_declaration = format!("{}!", name);
175
176 let mut builder =
177 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), &macro_declaration)
178 .kind(CompletionItemKind::Macro)
179 .set_documentation(docs.clone())
180 .set_deprecated(is_deprecated(macro_, ctx.db))
181 .detail(detail);
182
183 builder = if ctx.use_item_syntax.is_some() {
184 builder.insert_text(name)
185 } else {
186 let macro_braces_to_insert =
187 self.guess_macro_braces(&name, docs.as_ref().map_or("", |s| s.as_str()));
188 builder.insert_snippet(macro_declaration + macro_braces_to_insert)
189 };
190
191 self.add(builder);
192 }
193
194 fn add_function_with_name(
195 &mut self,
196 ctx: &CompletionContext,
197 name: Option<String>,
198 func: hir::Function,
199 ) {
200 let func_name = func.name(ctx.db);
201 let has_self_param = func.has_self_param(ctx.db);
202 let params = func.params(ctx.db);
203
204 let name = name.unwrap_or_else(|| func_name.to_string());
205 let ast_node = func.source(ctx.db).value;
206 let detail = function_label(&ast_node);
207
208 let mut builder =
209 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.clone())
210 .kind(if has_self_param {
211 CompletionItemKind::Method
212 } else {
213 CompletionItemKind::Function
214 })
215 .set_documentation(func.docs(ctx.db))
216 .set_deprecated(is_deprecated(func, ctx.db))
217 .detail(detail);
218
219 // Add `<>` for generic types
220 if ctx.use_item_syntax.is_none()
221 && !ctx.is_call
222 && ctx.db.feature_flags.get("completion.insertion.add-call-parenthesis")
223 {
224 tested_by!(inserts_parens_for_function_calls);
225 let (snippet, label) = if params.is_empty() || has_self_param && params.len() == 1 {
226 (format!("{}()$0", func_name), format!("{}()", name))
227 } else {
228 (format!("{}($0)", func_name), format!("{}(…)", name))
229 };
230 builder = builder.lookup_by(name).label(label).insert_snippet(snippet);
231 }
232
233 self.add(builder)
234 }
235
236 pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) {
237 let ast_node = constant.source(ctx.db).value;
238 let name = match ast_node.name() {
239 Some(name) => name,
240 _ => return,
241 };
242 let detail = const_label(&ast_node);
243
244 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string())
245 .kind(CompletionItemKind::Const)
246 .set_documentation(constant.docs(ctx.db))
247 .set_deprecated(is_deprecated(constant, ctx.db))
248 .detail(detail)
249 .add_to(self);
250 }
251
252 pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) {
253 let type_def = type_alias.source(ctx.db).value;
254 let name = match type_def.name() {
255 Some(name) => name,
256 _ => return,
257 };
258 let detail = type_label(&type_def);
259
260 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string())
261 .kind(CompletionItemKind::TypeAlias)
262 .set_documentation(type_alias.docs(ctx.db))
263 .set_deprecated(is_deprecated(type_alias, ctx.db))
264 .detail(detail)
265 .add_to(self);
266 }
267
268 pub(crate) fn add_enum_variant(&mut self, ctx: &CompletionContext, variant: hir::EnumVariant) {
269 let is_deprecated = is_deprecated(variant, ctx.db);
270 let name = match variant.name(ctx.db) {
271 Some(it) => it,
272 None => return,
273 };
274 let detail_types = variant.fields(ctx.db).into_iter().map(|field| field.ty(ctx.db));
275 let detail = join(detail_types.map(|t| t.display(ctx.db).to_string()))
276 .separator(", ")
277 .surround_with("(", ")")
278 .to_string();
279 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string())
280 .kind(CompletionItemKind::EnumVariant)
281 .set_documentation(variant.docs(ctx.db))
282 .set_deprecated(is_deprecated)
283 .detail(detail)
284 .add_to(self);
285 }
286}
287
288fn is_deprecated(node: impl HasAttrs, db: &impl HirDatabase) -> bool {
289 node.attrs(db).by_key("deprecated").exists()
290}
291
292#[cfg(test)]
293mod tests {
294 use insta::assert_debug_snapshot;
295 use test_utils::covers;
296
297 use crate::completion::{do_completion, CompletionItem, CompletionKind};
298
299 fn do_reference_completion(code: &str) -> Vec<CompletionItem> {
300 do_completion(code, CompletionKind::Reference)
301 }
302
303 #[test]
304 fn sets_deprecated_flag_in_completion_items() {
305 assert_debug_snapshot!(
306 do_reference_completion(
307 r#"
308 #[deprecated]
309 fn something_deprecated() {}
310
311 #[deprecated(since = "1.0.0")]
312 fn something_else_deprecated() {}
313
314 fn main() { som<|> }
315 "#,
316 ),
317 @r###"
318 [
319 CompletionItem {
320 label: "main()",
321 source_range: [203; 206),
322 delete: [203; 206),
323 insert: "main()$0",
324 kind: Function,
325 lookup: "main",
326 detail: "fn main()",
327 },
328 CompletionItem {
329 label: "something_deprecated()",
330 source_range: [203; 206),
331 delete: [203; 206),
332 insert: "something_deprecated()$0",
333 kind: Function,
334 lookup: "something_deprecated",
335 detail: "fn something_deprecated()",
336 deprecated: true,
337 },
338 CompletionItem {
339 label: "something_else_deprecated()",
340 source_range: [203; 206),
341 delete: [203; 206),
342 insert: "something_else_deprecated()$0",
343 kind: Function,
344 lookup: "something_else_deprecated",
345 detail: "fn something_else_deprecated()",
346 deprecated: true,
347 },
348 ]
349 "###
350 );
351 }
352
353 #[test]
354 fn inserts_parens_for_function_calls() {
355 covers!(inserts_parens_for_function_calls);
356 assert_debug_snapshot!(
357 do_reference_completion(
358 r"
359 fn no_args() {}
360 fn main() { no_<|> }
361 "
362 ),
363 @r###"
364 [
365 CompletionItem {
366 label: "main()",
367 source_range: [61; 64),
368 delete: [61; 64),
369 insert: "main()$0",
370 kind: Function,
371 lookup: "main",
372 detail: "fn main()",
373 },
374 CompletionItem {
375 label: "no_args()",
376 source_range: [61; 64),
377 delete: [61; 64),
378 insert: "no_args()$0",
379 kind: Function,
380 lookup: "no_args",
381 detail: "fn no_args()",
382 },
383 ]
384 "###
385 );
386 assert_debug_snapshot!(
387 do_reference_completion(
388 r"
389 fn with_args(x: i32, y: String) {}
390 fn main() { with_<|> }
391 "
392 ),
393 @r###"
394 [
395 CompletionItem {
396 label: "main()",
397 source_range: [80; 85),
398 delete: [80; 85),
399 insert: "main()$0",
400 kind: Function,
401 lookup: "main",
402 detail: "fn main()",
403 },
404 CompletionItem {
405 label: "with_args(…)",
406 source_range: [80; 85),
407 delete: [80; 85),
408 insert: "with_args($0)",
409 kind: Function,
410 lookup: "with_args",
411 detail: "fn with_args(x: i32, y: String)",
412 },
413 ]
414 "###
415 );
416 assert_debug_snapshot!(
417 do_reference_completion(
418 r"
419 struct S {}
420 impl S {
421 fn foo(&self) {}
422 }
423 fn bar(s: &S) {
424 s.f<|>
425 }
426 "
427 ),
428 @r###"
429 [
430 CompletionItem {
431 label: "foo()",
432 source_range: [163; 164),
433 delete: [163; 164),
434 insert: "foo()$0",
435 kind: Method,
436 lookup: "foo",
437 detail: "fn foo(&self)",
438 },
439 ]
440 "###
441 );
442 }
443
444 #[test]
445 fn dont_render_function_parens_in_use_item() {
446 assert_debug_snapshot!(
447 do_reference_completion(
448 "
449 //- /lib.rs
450 mod m { pub fn foo() {} }
451 use crate::m::f<|>;
452 "
453 ),
454 @r###"
455 [
456 CompletionItem {
457 label: "foo",
458 source_range: [40; 41),
459 delete: [40; 41),
460 insert: "foo",
461 kind: Function,
462 detail: "pub fn foo()",
463 },
464 ]
465 "###
466 );
467 }
468
469 #[test]
470 fn dont_render_function_parens_if_already_call() {
471 assert_debug_snapshot!(
472 do_reference_completion(
473 "
474 //- /lib.rs
475 fn frobnicate() {}
476 fn main() {
477 frob<|>();
478 }
479 "
480 ),
481 @r###"
482 [
483 CompletionItem {
484 label: "frobnicate",
485 source_range: [35; 39),
486 delete: [35; 39),
487 insert: "frobnicate",
488 kind: Function,
489 detail: "fn frobnicate()",
490 },
491 CompletionItem {
492 label: "main",
493 source_range: [35; 39),
494 delete: [35; 39),
495 insert: "main",
496 kind: Function,
497 detail: "fn main()",
498 },
499 ]
500 "###
501 );
502 assert_debug_snapshot!(
503 do_reference_completion(
504 "
505 //- /lib.rs
506 struct Foo {}
507 impl Foo { fn new() -> Foo {} }
508 fn main() {
509 Foo::ne<|>();
510 }
511 "
512 ),
513 @r###"
514 [
515 CompletionItem {
516 label: "new",
517 source_range: [67; 69),
518 delete: [67; 69),
519 insert: "new",
520 kind: Function,
521 detail: "fn new() -> Foo",
522 },
523 ]
524 "###
525 );
526 }
527
528 #[test]
529 fn inserts_angle_brackets_for_generics() {
530 covers!(inserts_angle_brackets_for_generics);
531 assert_debug_snapshot!(
532 do_reference_completion(
533 r"
534 struct Vec<T> {}
535 fn foo(xs: Ve<|>)
536 "
537 ),
538 @r###"
539 [
540 CompletionItem {
541 label: "Vec<…>",
542 source_range: [61; 63),
543 delete: [61; 63),
544 insert: "Vec<$0>",
545 kind: Struct,
546 lookup: "Vec",
547 },
548 CompletionItem {
549 label: "foo(…)",
550 source_range: [61; 63),
551 delete: [61; 63),
552 insert: "foo($0)",
553 kind: Function,
554 lookup: "foo",
555 detail: "fn foo(xs: Ve)",
556 },
557 ]
558 "###
559 );
560 assert_debug_snapshot!(
561 do_reference_completion(
562 r"
563 type Vec<T> = (T,);
564 fn foo(xs: Ve<|>)
565 "
566 ),
567 @r###"
568 [
569 CompletionItem {
570 label: "Vec<…>",
571 source_range: [64; 66),
572 delete: [64; 66),
573 insert: "Vec<$0>",
574 kind: TypeAlias,
575 lookup: "Vec",
576 },
577 CompletionItem {
578 label: "foo(…)",
579 source_range: [64; 66),
580 delete: [64; 66),
581 insert: "foo($0)",
582 kind: Function,
583 lookup: "foo",
584 detail: "fn foo(xs: Ve)",
585 },
586 ]
587 "###
588 );
589 assert_debug_snapshot!(
590 do_reference_completion(
591 r"
592 struct Vec<T = i128> {}
593 fn foo(xs: Ve<|>)
594 "
595 ),
596 @r###"
597 [
598 CompletionItem {
599 label: "Vec",
600 source_range: [68; 70),
601 delete: [68; 70),
602 insert: "Vec",
603 kind: Struct,
604 },
605 CompletionItem {
606 label: "foo(…)",
607 source_range: [68; 70),
608 delete: [68; 70),
609 insert: "foo($0)",
610 kind: Function,
611 lookup: "foo",
612 detail: "fn foo(xs: Ve)",
613 },
614 ]
615 "###
616 );
617 assert_debug_snapshot!(
618 do_reference_completion(
619 r"
620 struct Vec<T> {}
621 fn foo(xs: Ve<|><i128>)
622 "
623 ),
624 @r###"
625 [
626 CompletionItem {
627 label: "Vec",
628 source_range: [61; 63),
629 delete: [61; 63),
630 insert: "Vec",
631 kind: Struct,
632 },
633 CompletionItem {
634 label: "foo(…)",
635 source_range: [61; 63),
636 delete: [61; 63),
637 insert: "foo($0)",
638 kind: Function,
639 lookup: "foo",
640 detail: "fn foo(xs: Ve<i128>)",
641 },
642 ]
643 "###
644 );
645 }
646
647 #[test]
648 fn dont_insert_macro_call_braces_in_use() {
649 assert_debug_snapshot!(
650 do_reference_completion(
651 r"
652 //- /main.rs
653 use foo::<|>;
654
655 //- /foo/lib.rs
656 #[macro_export]
657 macro_rules frobnicate {
658 () => ()
659 }
660 "
661 ),
662 @r###"
663 [
664 CompletionItem {
665 label: "frobnicate!",
666 source_range: [9; 9),
667 delete: [9; 9),
668 insert: "frobnicate",
669 kind: Macro,
670 detail: "#[macro_export]\nmacro_rules! frobnicate",
671 },
672 ]
673 "###
674 )
675 }
676}
diff --git a/crates/ra_ide_api/src/db.rs b/crates/ra_ide_api/src/db.rs
deleted file mode 100644
index f739ebecd..000000000
--- a/crates/ra_ide_api/src/db.rs
+++ /dev/null
@@ -1,144 +0,0 @@
1//! FIXME: write short doc here
2
3use std::sync::Arc;
4
5use ra_db::{
6 salsa::{self, Database, Durability},
7 Canceled, CheckCanceled, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath,
8 SourceDatabase, SourceDatabaseExt, SourceRootId,
9};
10use rustc_hash::FxHashMap;
11
12use crate::{
13 symbol_index::{self, SymbolsDatabase},
14 FeatureFlags, LineIndex,
15};
16
17#[salsa::database(
18 ra_db::SourceDatabaseStorage,
19 ra_db::SourceDatabaseExtStorage,
20 LineIndexDatabaseStorage,
21 symbol_index::SymbolsDatabaseStorage,
22 hir::db::InternDatabaseStorage,
23 hir::db::AstDatabaseStorage,
24 hir::db::DefDatabaseStorage,
25 hir::db::HirDatabaseStorage
26)]
27#[derive(Debug)]
28pub(crate) struct RootDatabase {
29 runtime: salsa::Runtime<RootDatabase>,
30 pub(crate) feature_flags: Arc<FeatureFlags>,
31 pub(crate) debug_data: Arc<DebugData>,
32 pub(crate) last_gc: crate::wasm_shims::Instant,
33 pub(crate) last_gc_check: crate::wasm_shims::Instant,
34}
35
36impl FileLoader for RootDatabase {
37 fn file_text(&self, file_id: FileId) -> Arc<String> {
38 FileLoaderDelegate(self).file_text(file_id)
39 }
40 fn resolve_relative_path(
41 &self,
42 anchor: FileId,
43 relative_path: &RelativePath,
44 ) -> Option<FileId> {
45 FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path)
46 }
47 fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> {
48 FileLoaderDelegate(self).relevant_crates(file_id)
49 }
50}
51
52impl hir::debug::HirDebugHelper for RootDatabase {
53 fn crate_name(&self, krate: CrateId) -> Option<String> {
54 self.debug_data.crate_names.get(&krate).cloned()
55 }
56 fn file_path(&self, file_id: FileId) -> Option<String> {
57 let source_root_id = self.file_source_root(file_id);
58 let source_root_path = self.debug_data.root_paths.get(&source_root_id)?;
59 let file_path = self.file_relative_path(file_id);
60 Some(format!("{}/{}", source_root_path, file_path))
61 }
62}
63
64impl salsa::Database for RootDatabase {
65 fn salsa_runtime(&self) -> &salsa::Runtime<RootDatabase> {
66 &self.runtime
67 }
68 fn salsa_runtime_mut(&mut self) -> &mut salsa::Runtime<Self> {
69 &mut self.runtime
70 }
71 fn on_propagated_panic(&self) -> ! {
72 Canceled::throw()
73 }
74 fn salsa_event(&self, event: impl Fn() -> salsa::Event<RootDatabase>) {
75 match event().kind {
76 salsa::EventKind::DidValidateMemoizedValue { .. }
77 | salsa::EventKind::WillExecute { .. } => {
78 self.check_canceled();
79 }
80 _ => (),
81 }
82 }
83}
84
85impl Default for RootDatabase {
86 fn default() -> RootDatabase {
87 RootDatabase::new(None, FeatureFlags::default())
88 }
89}
90
91impl RootDatabase {
92 pub fn new(lru_capacity: Option<usize>, feature_flags: FeatureFlags) -> RootDatabase {
93 let mut db = RootDatabase {
94 runtime: salsa::Runtime::default(),
95 last_gc: crate::wasm_shims::Instant::now(),
96 last_gc_check: crate::wasm_shims::Instant::now(),
97 feature_flags: Arc::new(feature_flags),
98 debug_data: Default::default(),
99 };
100 db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
101 db.set_local_roots_with_durability(Default::default(), Durability::HIGH);
102 db.set_library_roots_with_durability(Default::default(), Durability::HIGH);
103 let lru_capacity = lru_capacity.unwrap_or(ra_db::DEFAULT_LRU_CAP);
104 db.query_mut(ra_db::ParseQuery).set_lru_capacity(lru_capacity);
105 db.query_mut(hir::db::ParseMacroQuery).set_lru_capacity(lru_capacity);
106 db.query_mut(hir::db::MacroExpandQuery).set_lru_capacity(lru_capacity);
107 db
108 }
109}
110
111impl salsa::ParallelDatabase for RootDatabase {
112 fn snapshot(&self) -> salsa::Snapshot<RootDatabase> {
113 salsa::Snapshot::new(RootDatabase {
114 runtime: self.runtime.snapshot(self),
115 last_gc: self.last_gc,
116 last_gc_check: self.last_gc_check,
117 feature_flags: Arc::clone(&self.feature_flags),
118 debug_data: Arc::clone(&self.debug_data),
119 })
120 }
121}
122
123#[salsa::query_group(LineIndexDatabaseStorage)]
124pub(crate) trait LineIndexDatabase: ra_db::SourceDatabase + CheckCanceled {
125 fn line_index(&self, file_id: FileId) -> Arc<LineIndex>;
126}
127
128fn line_index(db: &impl LineIndexDatabase, file_id: FileId) -> Arc<LineIndex> {
129 let text = db.file_text(file_id);
130 Arc::new(LineIndex::new(&*text))
131}
132
133#[derive(Debug, Default, Clone)]
134pub(crate) struct DebugData {
135 pub(crate) root_paths: FxHashMap<SourceRootId, String>,
136 pub(crate) crate_names: FxHashMap<CrateId, String>,
137}
138
139impl DebugData {
140 pub(crate) fn merge(&mut self, other: DebugData) {
141 self.root_paths.extend(other.root_paths.into_iter());
142 self.crate_names.extend(other.crate_names.into_iter());
143 }
144}
diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs
deleted file mode 100644
index cc1ccab4b..000000000
--- a/crates/ra_ide_api/src/diagnostics.rs
+++ /dev/null
@@ -1,652 +0,0 @@
1//! FIXME: write short doc here
2
3use std::cell::RefCell;
4
5use hir::diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink};
6use itertools::Itertools;
7use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt};
8use ra_prof::profile;
9use ra_syntax::{
10 algo,
11 ast::{self, make, AstNode},
12 Location, SyntaxNode, TextRange, T,
13};
14use ra_text_edit::{TextEdit, TextEditBuilder};
15
16use crate::{db::RootDatabase, Diagnostic, FileId, FileSystemEdit, SourceChange, SourceFileEdit};
17
18#[derive(Debug, Copy, Clone)]
19pub enum Severity {
20 Error,
21 WeakWarning,
22}
23
24pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> {
25 let _p = profile("diagnostics");
26 let parse = db.parse(file_id);
27 let mut res = Vec::new();
28
29 res.extend(parse.errors().iter().map(|err| Diagnostic {
30 range: location_to_range(err.location()),
31 message: format!("Syntax Error: {}", err),
32 severity: Severity::Error,
33 fix: None,
34 }));
35
36 for node in parse.tree().syntax().descendants() {
37 check_unnecessary_braces_in_use_statement(&mut res, file_id, &node);
38 check_struct_shorthand_initialization(&mut res, file_id, &node);
39 }
40 let res = RefCell::new(res);
41 let mut sink = DiagnosticSink::new(|d| {
42 res.borrow_mut().push(Diagnostic {
43 message: d.message(),
44 range: d.highlight_range(),
45 severity: Severity::Error,
46 fix: None,
47 })
48 })
49 .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
50 let original_file = d.source().file_id.original_file(db);
51 let source_root = db.file_source_root(original_file);
52 let path = db
53 .file_relative_path(original_file)
54 .parent()
55 .unwrap_or_else(|| RelativePath::new(""))
56 .join(&d.candidate);
57 let create_file = FileSystemEdit::CreateFile { source_root, path };
58 let fix = SourceChange::file_system_edit("create module", create_file);
59 res.borrow_mut().push(Diagnostic {
60 range: d.highlight_range(),
61 message: d.message(),
62 severity: Severity::Error,
63 fix: Some(fix),
64 })
65 })
66 .on::<hir::diagnostics::MissingFields, _>(|d| {
67 let mut field_list = d.ast(db);
68 for f in d.missed_fields.iter() {
69 let field = make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
70 field_list = field_list.append_field(&field);
71 }
72
73 let mut builder = TextEditBuilder::default();
74 algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder);
75
76 let fix =
77 SourceChange::source_file_edit_from("fill struct fields", file_id, builder.finish());
78 res.borrow_mut().push(Diagnostic {
79 range: d.highlight_range(),
80 message: d.message(),
81 severity: Severity::Error,
82 fix: Some(fix),
83 })
84 })
85 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| {
86 let node = d.ast(db);
87 let replacement = format!("Ok({})", node.syntax());
88 let edit = TextEdit::replace(node.syntax().text_range(), replacement);
89 let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit);
90 res.borrow_mut().push(Diagnostic {
91 range: d.highlight_range(),
92 message: d.message(),
93 severity: Severity::Error,
94 fix: Some(fix),
95 })
96 });
97 let source_file = db.parse(file_id).tree();
98 let src =
99 hir::Source { file_id: file_id.into(), value: hir::ModuleSource::SourceFile(source_file) };
100 if let Some(m) = hir::Module::from_definition(db, src) {
101 m.diagnostics(db, &mut sink);
102 };
103 drop(sink);
104 res.into_inner()
105}
106fn location_to_range(location: Location) -> TextRange {
107 match location {
108 Location::Offset(offset) => TextRange::offset_len(offset, 1.into()),
109 Location::Range(range) => range,
110 }
111}
112
113fn check_unnecessary_braces_in_use_statement(
114 acc: &mut Vec<Diagnostic>,
115 file_id: FileId,
116 node: &SyntaxNode,
117) -> Option<()> {
118 let use_tree_list = ast::UseTreeList::cast(node.clone())?;
119 if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() {
120 let range = use_tree_list.syntax().text_range();
121 let edit =
122 text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree)
123 .unwrap_or_else(|| {
124 let to_replace = single_use_tree.syntax().text().to_string();
125 let mut edit_builder = TextEditBuilder::default();
126 edit_builder.delete(range);
127 edit_builder.insert(range.start(), to_replace);
128 edit_builder.finish()
129 });
130
131 acc.push(Diagnostic {
132 range,
133 message: "Unnecessary braces in use statement".to_string(),
134 severity: Severity::WeakWarning,
135 fix: Some(SourceChange::source_file_edit(
136 "Remove unnecessary braces",
137 SourceFileEdit { file_id, edit },
138 )),
139 });
140 }
141
142 Some(())
143}
144
145fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
146 single_use_tree: &ast::UseTree,
147) -> Option<TextEdit> {
148 let use_tree_list_node = single_use_tree.syntax().parent()?;
149 if single_use_tree.path()?.segment()?.syntax().first_child_or_token()?.kind() == T![self] {
150 let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start();
151 let end = use_tree_list_node.text_range().end();
152 let range = TextRange::from_to(start, end);
153 return Some(TextEdit::delete(range));
154 }
155 None
156}
157
158fn check_struct_shorthand_initialization(
159 acc: &mut Vec<Diagnostic>,
160 file_id: FileId,
161 node: &SyntaxNode,
162) -> Option<()> {
163 let record_lit = ast::RecordLit::cast(node.clone())?;
164 let record_field_list = record_lit.record_field_list()?;
165 for record_field in record_field_list.fields() {
166 if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) {
167 let field_name = name_ref.syntax().text().to_string();
168 let field_expr = expr.syntax().text().to_string();
169 if field_name == field_expr {
170 let mut edit_builder = TextEditBuilder::default();
171 edit_builder.delete(record_field.syntax().text_range());
172 edit_builder.insert(record_field.syntax().text_range().start(), field_name);
173 let edit = edit_builder.finish();
174
175 acc.push(Diagnostic {
176 range: record_field.syntax().text_range(),
177 message: "Shorthand struct initialization".to_string(),
178 severity: Severity::WeakWarning,
179 fix: Some(SourceChange::source_file_edit(
180 "use struct shorthand initialization",
181 SourceFileEdit { file_id, edit },
182 )),
183 });
184 }
185 }
186 }
187 Some(())
188}
189
190#[cfg(test)]
191mod tests {
192 use insta::assert_debug_snapshot;
193 use join_to_string::join;
194 use ra_syntax::SourceFile;
195 use test_utils::assert_eq_text;
196
197 use crate::mock_analysis::{analysis_and_position, single_file};
198
199 use super::*;
200
201 type DiagnosticChecker = fn(&mut Vec<Diagnostic>, FileId, &SyntaxNode) -> Option<()>;
202
203 fn check_not_applicable(code: &str, func: DiagnosticChecker) {
204 let parse = SourceFile::parse(code);
205 let mut diagnostics = Vec::new();
206 for node in parse.tree().syntax().descendants() {
207 func(&mut diagnostics, FileId(0), &node);
208 }
209 assert!(diagnostics.is_empty());
210 }
211
212 fn check_apply(before: &str, after: &str, func: DiagnosticChecker) {
213 let parse = SourceFile::parse(before);
214 let mut diagnostics = Vec::new();
215 for node in parse.tree().syntax().descendants() {
216 func(&mut diagnostics, FileId(0), &node);
217 }
218 let diagnostic =
219 diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before));
220 let mut fix = diagnostic.fix.unwrap();
221 let edit = fix.source_file_edits.pop().unwrap().edit;
222 let actual = edit.apply(&before);
223 assert_eq_text!(after, &actual);
224 }
225
226 /// Takes a multi-file input fixture with annotated cursor positions,
227 /// and checks that:
228 /// * a diagnostic is produced
229 /// * this diagnostic touches the input cursor position
230 /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
231 fn check_apply_diagnostic_fix_from_position(fixture: &str, after: &str) {
232 let (analysis, file_position) = analysis_and_position(fixture);
233 let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap();
234 let mut fix = diagnostic.fix.unwrap();
235 let edit = fix.source_file_edits.pop().unwrap().edit;
236 let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
237 let actual = edit.apply(&target_file_contents);
238
239 // Strip indent and empty lines from `after`, to match the behaviour of
240 // `parse_fixture` called from `analysis_and_position`.
241 let margin = fixture
242 .lines()
243 .filter(|it| it.trim_start().starts_with("//-"))
244 .map(|it| it.len() - it.trim_start().len())
245 .next()
246 .expect("empty fixture");
247 let after = join(after.lines().filter_map(|line| {
248 if line.len() > margin {
249 Some(&line[margin..])
250 } else {
251 None
252 }
253 }))
254 .separator("\n")
255 .suffix("\n")
256 .to_string();
257
258 assert_eq_text!(&after, &actual);
259 assert!(
260 diagnostic.range.start() <= file_position.offset
261 && diagnostic.range.end() >= file_position.offset,
262 "diagnostic range {} does not touch cursor position {}",
263 diagnostic.range,
264 file_position.offset
265 );
266 }
267
268 fn check_apply_diagnostic_fix(before: &str, after: &str) {
269 let (analysis, file_id) = single_file(before);
270 let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap();
271 let mut fix = diagnostic.fix.unwrap();
272 let edit = fix.source_file_edits.pop().unwrap().edit;
273 let actual = edit.apply(&before);
274 assert_eq_text!(after, &actual);
275 }
276
277 /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics
278 /// apply to the file containing the cursor.
279 fn check_no_diagnostic_for_target_file(fixture: &str) {
280 let (analysis, file_position) = analysis_and_position(fixture);
281 let diagnostics = analysis.diagnostics(file_position.file_id).unwrap();
282 assert_eq!(diagnostics.len(), 0);
283 }
284
285 fn check_no_diagnostic(content: &str) {
286 let (analysis, file_id) = single_file(content);
287 let diagnostics = analysis.diagnostics(file_id).unwrap();
288 assert_eq!(diagnostics.len(), 0);
289 }
290
291 #[test]
292 fn test_wrap_return_type() {
293 let before = r#"
294 //- /main.rs
295 use std::{string::String, result::Result::{self, Ok, Err}};
296
297 fn div(x: i32, y: i32) -> Result<i32, String> {
298 if y == 0 {
299 return Err("div by zero".into());
300 }
301 x / y<|>
302 }
303
304 //- /std/lib.rs
305 pub mod string {
306 pub struct String { }
307 }
308 pub mod result {
309 pub enum Result<T, E> { Ok(T), Err(E) }
310 }
311 "#;
312 let after = r#"
313 use std::{string::String, result::Result::{self, Ok, Err}};
314
315 fn div(x: i32, y: i32) -> Result<i32, String> {
316 if y == 0 {
317 return Err("div by zero".into());
318 }
319 Ok(x / y)
320 }
321 "#;
322 check_apply_diagnostic_fix_from_position(before, after);
323 }
324
325 #[test]
326 fn test_wrap_return_type_handles_generic_functions() {
327 let before = r#"
328 //- /main.rs
329 use std::result::Result::{self, Ok, Err};
330
331 fn div<T>(x: T) -> Result<T, i32> {
332 if x == 0 {
333 return Err(7);
334 }
335 <|>x
336 }
337
338 //- /std/lib.rs
339 pub mod result {
340 pub enum Result<T, E> { Ok(T), Err(E) }
341 }
342 "#;
343 let after = r#"
344 use std::result::Result::{self, Ok, Err};
345
346 fn div<T>(x: T) -> Result<T, i32> {
347 if x == 0 {
348 return Err(7);
349 }
350 Ok(x)
351 }
352 "#;
353 check_apply_diagnostic_fix_from_position(before, after);
354 }
355
356 #[test]
357 fn test_wrap_return_type_handles_type_aliases() {
358 let before = r#"
359 //- /main.rs
360 use std::{string::String, result::Result::{self, Ok, Err}};
361
362 type MyResult<T> = Result<T, String>;
363
364 fn div(x: i32, y: i32) -> MyResult<i32> {
365 if y == 0 {
366 return Err("div by zero".into());
367 }
368 x <|>/ y
369 }
370
371 //- /std/lib.rs
372 pub mod string {
373 pub struct String { }
374 }
375 pub mod result {
376 pub enum Result<T, E> { Ok(T), Err(E) }
377 }
378 "#;
379 let after = r#"
380 use std::{string::String, result::Result::{self, Ok, Err}};
381
382 type MyResult<T> = Result<T, String>;
383 fn div(x: i32, y: i32) -> MyResult<i32> {
384 if y == 0 {
385 return Err("div by zero".into());
386 }
387 Ok(x / y)
388 }
389 "#;
390 check_apply_diagnostic_fix_from_position(before, after);
391 }
392
393 #[test]
394 fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
395 let content = r#"
396 //- /main.rs
397 use std::{string::String, result::Result::{self, Ok, Err}};
398
399 fn foo() -> Result<String, i32> {
400 0<|>
401 }
402
403 //- /std/lib.rs
404 pub mod string {
405 pub struct String { }
406 }
407 pub mod result {
408 pub enum Result<T, E> { Ok(T), Err(E) }
409 }
410 "#;
411 check_no_diagnostic_for_target_file(content);
412 }
413
414 #[test]
415 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() {
416 let content = r#"
417 //- /main.rs
418 use std::{string::String, result::Result::{self, Ok, Err}};
419
420 enum SomeOtherEnum {
421 Ok(i32),
422 Err(String),
423 }
424
425 fn foo() -> SomeOtherEnum {
426 0<|>
427 }
428
429 //- /std/lib.rs
430 pub mod string {
431 pub struct String { }
432 }
433 pub mod result {
434 pub enum Result<T, E> { Ok(T), Err(E) }
435 }
436 "#;
437 check_no_diagnostic_for_target_file(content);
438 }
439
440 #[test]
441 fn test_fill_struct_fields_empty() {
442 let before = r"
443 struct TestStruct {
444 one: i32,
445 two: i64,
446 }
447
448 fn test_fn() {
449 let s = TestStruct{};
450 }
451 ";
452 let after = r"
453 struct TestStruct {
454 one: i32,
455 two: i64,
456 }
457
458 fn test_fn() {
459 let s = TestStruct{ one: (), two: ()};
460 }
461 ";
462 check_apply_diagnostic_fix(before, after);
463 }
464
465 #[test]
466 fn test_fill_struct_fields_partial() {
467 let before = r"
468 struct TestStruct {
469 one: i32,
470 two: i64,
471 }
472
473 fn test_fn() {
474 let s = TestStruct{ two: 2 };
475 }
476 ";
477 let after = r"
478 struct TestStruct {
479 one: i32,
480 two: i64,
481 }
482
483 fn test_fn() {
484 let s = TestStruct{ two: 2, one: () };
485 }
486 ";
487 check_apply_diagnostic_fix(before, after);
488 }
489
490 #[test]
491 fn test_fill_struct_fields_no_diagnostic() {
492 let content = r"
493 struct TestStruct {
494 one: i32,
495 two: i64,
496 }
497
498 fn test_fn() {
499 let one = 1;
500 let s = TestStruct{ one, two: 2 };
501 }
502 ";
503
504 check_no_diagnostic(content);
505 }
506
507 #[test]
508 fn test_fill_struct_fields_no_diagnostic_on_spread() {
509 let content = r"
510 struct TestStruct {
511 one: i32,
512 two: i64,
513 }
514
515 fn test_fn() {
516 let one = 1;
517 let s = TestStruct{ ..a };
518 }
519 ";
520
521 check_no_diagnostic(content);
522 }
523
524 #[test]
525 fn test_unresolved_module_diagnostic() {
526 let (analysis, file_id) = single_file("mod foo;");
527 let diagnostics = analysis.diagnostics(file_id).unwrap();
528 assert_debug_snapshot!(diagnostics, @r###"
529 [
530 Diagnostic {
531 message: "unresolved module",
532 range: [0; 8),
533 fix: Some(
534 SourceChange {
535 label: "create module",
536 source_file_edits: [],
537 file_system_edits: [
538 CreateFile {
539 source_root: SourceRootId(
540 0,
541 ),
542 path: "foo.rs",
543 },
544 ],
545 cursor_position: None,
546 },
547 ),
548 severity: Error,
549 },
550 ]
551 "###);
552 }
553
554 #[test]
555 fn test_check_unnecessary_braces_in_use_statement() {
556 check_not_applicable(
557 "
558 use a;
559 use a::{c, d::e};
560 ",
561 check_unnecessary_braces_in_use_statement,
562 );
563 check_apply("use {b};", "use b;", check_unnecessary_braces_in_use_statement);
564 check_apply("use a::{c};", "use a::c;", check_unnecessary_braces_in_use_statement);
565 check_apply("use a::{self};", "use a;", check_unnecessary_braces_in_use_statement);
566 check_apply(
567 "use a::{c, d::{e}};",
568 "use a::{c, d::e};",
569 check_unnecessary_braces_in_use_statement,
570 );
571 }
572
573 #[test]
574 fn test_check_struct_shorthand_initialization() {
575 check_not_applicable(
576 r#"
577 struct A {
578 a: &'static str
579 }
580
581 fn main() {
582 A {
583 a: "hello"
584 }
585 }
586 "#,
587 check_struct_shorthand_initialization,
588 );
589
590 check_apply(
591 r#"
592struct A {
593 a: &'static str
594}
595
596fn main() {
597 let a = "haha";
598 A {
599 a: a
600 }
601}
602 "#,
603 r#"
604struct A {
605 a: &'static str
606}
607
608fn main() {
609 let a = "haha";
610 A {
611 a
612 }
613}
614 "#,
615 check_struct_shorthand_initialization,
616 );
617
618 check_apply(
619 r#"
620struct A {
621 a: &'static str,
622 b: &'static str
623}
624
625fn main() {
626 let a = "haha";
627 let b = "bb";
628 A {
629 a: a,
630 b
631 }
632}
633 "#,
634 r#"
635struct A {
636 a: &'static str,
637 b: &'static str
638}
639
640fn main() {
641 let a = "haha";
642 let b = "bb";
643 A {
644 a,
645 b
646 }
647}
648 "#,
649 check_struct_shorthand_initialization,
650 );
651 }
652}
diff --git a/crates/ra_ide_api/src/display.rs b/crates/ra_ide_api/src/display.rs
deleted file mode 100644
index 30617412a..000000000
--- a/crates/ra_ide_api/src/display.rs
+++ /dev/null
@@ -1,84 +0,0 @@
1//! This module contains utilities for turning SyntaxNodes and HIR types
2//! into types that may be used to render in a UI.
3
4mod function_signature;
5mod navigation_target;
6mod structure;
7mod short_label;
8
9use ra_syntax::{
10 ast::{self, AstNode, AttrsOwner, NameOwner, TypeParamsOwner},
11 SyntaxKind::{ATTR, COMMENT},
12};
13
14pub use function_signature::FunctionSignature;
15pub use navigation_target::NavigationTarget;
16pub use structure::{file_structure, StructureNode};
17
18pub(crate) use navigation_target::{description_from_symbol, docs_from_symbol, ToNav};
19pub(crate) use short_label::ShortLabel;
20
21pub(crate) fn function_label(node: &ast::FnDef) -> String {
22 FunctionSignature::from(node).to_string()
23}
24
25pub(crate) fn const_label(node: &ast::ConstDef) -> String {
26 let label: String = node
27 .syntax()
28 .children_with_tokens()
29 .filter(|child| !(child.kind() == COMMENT || child.kind() == ATTR))
30 .map(|node| node.to_string())
31 .collect();
32
33 label.trim().to_owned()
34}
35
36pub(crate) fn type_label(node: &ast::TypeAliasDef) -> String {
37 let label: String = node
38 .syntax()
39 .children_with_tokens()
40 .filter(|child| !(child.kind() == COMMENT || child.kind() == ATTR))
41 .map(|node| node.to_string())
42 .collect();
43
44 label.trim().to_owned()
45}
46
47pub(crate) fn generic_parameters<N: TypeParamsOwner>(node: &N) -> Vec<String> {
48 let mut res = vec![];
49 if let Some(type_params) = node.type_param_list() {
50 res.extend(type_params.lifetime_params().map(|p| p.syntax().text().to_string()));
51 res.extend(type_params.type_params().map(|p| p.syntax().text().to_string()));
52 }
53 res
54}
55
56pub(crate) fn where_predicates<N: TypeParamsOwner>(node: &N) -> Vec<String> {
57 let mut res = vec![];
58 if let Some(clause) = node.where_clause() {
59 res.extend(clause.predicates().map(|p| p.syntax().text().to_string()));
60 }
61 res
62}
63
64pub(crate) fn macro_label(node: &ast::MacroCall) -> String {
65 let name = node.name().map(|name| name.syntax().text().to_string()).unwrap_or_default();
66 let vis = if node.has_atom_attr("macro_export") { "#[macro_export]\n" } else { "" };
67 format!("{}macro_rules! {}", vis, name)
68}
69
70pub(crate) fn rust_code_markup<CODE: AsRef<str>>(val: CODE) -> String {
71 rust_code_markup_with_doc::<_, &str>(val, None)
72}
73
74pub(crate) fn rust_code_markup_with_doc<CODE, DOC>(val: CODE, doc: Option<DOC>) -> String
75where
76 CODE: AsRef<str>,
77 DOC: AsRef<str>,
78{
79 if let Some(doc) = doc {
80 format!("```rust\n{}\n```\n\n{}", val.as_ref(), doc.as_ref())
81 } else {
82 format!("```rust\n{}\n```", val.as_ref())
83 }
84}
diff --git a/crates/ra_ide_api/src/display/function_signature.rs b/crates/ra_ide_api/src/display/function_signature.rs
deleted file mode 100644
index d96de4e4c..000000000
--- a/crates/ra_ide_api/src/display/function_signature.rs
+++ /dev/null
@@ -1,215 +0,0 @@
1//! FIXME: write short doc here
2
3use std::fmt::{self, Display};
4
5use hir::{Docs, Documentation, HasSource, HirDisplay};
6use join_to_string::join;
7use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
8use std::convert::From;
9
10use crate::{
11 db,
12 display::{generic_parameters, where_predicates},
13};
14
15#[derive(Debug)]
16pub enum CallableKind {
17 Function,
18 StructConstructor,
19 VariantConstructor,
20 Macro,
21}
22
23/// Contains information about a function signature
24#[derive(Debug)]
25pub struct FunctionSignature {
26 pub kind: CallableKind,
27 /// Optional visibility
28 pub visibility: Option<String>,
29 /// Name of the function
30 pub name: Option<String>,
31 /// Documentation for the function
32 pub doc: Option<Documentation>,
33 /// Generic parameters
34 pub generic_parameters: Vec<String>,
35 /// Parameters of the function
36 pub parameters: Vec<String>,
37 /// Optional return type
38 pub ret_type: Option<String>,
39 /// Where predicates
40 pub where_predicates: Vec<String>,
41}
42
43impl FunctionSignature {
44 pub(crate) fn with_doc_opt(mut self, doc: Option<Documentation>) -> Self {
45 self.doc = doc;
46 self
47 }
48
49 pub(crate) fn from_hir(db: &db::RootDatabase, function: hir::Function) -> Self {
50 let doc = function.docs(db);
51 let ast_node = function.source(db).value;
52 FunctionSignature::from(&ast_node).with_doc_opt(doc)
53 }
54
55 pub(crate) fn from_struct(db: &db::RootDatabase, st: hir::Struct) -> Option<Self> {
56 let node: ast::StructDef = st.source(db).value;
57 match node.kind() {
58 ast::StructKind::Record(_) => return None,
59 _ => (),
60 };
61
62 let params = st
63 .fields(db)
64 .into_iter()
65 .map(|field: hir::StructField| {
66 let ty = field.ty(db);
67 format!("{}", ty.display(db))
68 })
69 .collect();
70
71 Some(
72 FunctionSignature {
73 kind: CallableKind::StructConstructor,
74 visibility: node.visibility().map(|n| n.syntax().text().to_string()),
75 name: node.name().map(|n| n.text().to_string()),
76 ret_type: node.name().map(|n| n.text().to_string()),
77 parameters: params,
78 generic_parameters: generic_parameters(&node),
79 where_predicates: where_predicates(&node),
80 doc: None,
81 }
82 .with_doc_opt(st.docs(db)),
83 )
84 }
85
86 pub(crate) fn from_enum_variant(
87 db: &db::RootDatabase,
88 variant: hir::EnumVariant,
89 ) -> Option<Self> {
90 let node: ast::EnumVariant = variant.source(db).value;
91 match node.kind() {
92 ast::StructKind::Record(_) | ast::StructKind::Unit => return None,
93 _ => (),
94 };
95
96 let parent_name = match variant.parent_enum(db).name(db) {
97 Some(name) => name.to_string(),
98 None => "missing".into(),
99 };
100
101 let name = format!("{}::{}", parent_name, variant.name(db).unwrap());
102
103 let params = variant
104 .fields(db)
105 .into_iter()
106 .map(|field: hir::StructField| {
107 let name = field.name(db);
108 let ty = field.ty(db);
109 format!("{}: {}", name, ty.display(db))
110 })
111 .collect();
112
113 Some(
114 FunctionSignature {
115 kind: CallableKind::VariantConstructor,
116 visibility: None,
117 name: Some(name),
118 ret_type: None,
119 parameters: params,
120 generic_parameters: vec![],
121 where_predicates: vec![],
122 doc: None,
123 }
124 .with_doc_opt(variant.docs(db)),
125 )
126 }
127
128 pub(crate) fn from_macro(db: &db::RootDatabase, macro_def: hir::MacroDef) -> Option<Self> {
129 let node: ast::MacroCall = macro_def.source(db).value;
130
131 let params = vec![];
132
133 Some(
134 FunctionSignature {
135 kind: CallableKind::Macro,
136 visibility: None,
137 name: node.name().map(|n| n.text().to_string()),
138 ret_type: None,
139 parameters: params,
140 generic_parameters: vec![],
141 where_predicates: vec![],
142 doc: None,
143 }
144 .with_doc_opt(macro_def.docs(db)),
145 )
146 }
147}
148
149impl From<&'_ ast::FnDef> for FunctionSignature {
150 fn from(node: &ast::FnDef) -> FunctionSignature {
151 fn param_list(node: &ast::FnDef) -> Vec<String> {
152 let mut res = vec![];
153 if let Some(param_list) = node.param_list() {
154 if let Some(self_param) = param_list.self_param() {
155 res.push(self_param.syntax().text().to_string())
156 }
157
158 res.extend(param_list.params().map(|param| param.syntax().text().to_string()));
159 }
160 res
161 }
162
163 FunctionSignature {
164 kind: CallableKind::Function,
165 visibility: node.visibility().map(|n| n.syntax().text().to_string()),
166 name: node.name().map(|n| n.text().to_string()),
167 ret_type: node
168 .ret_type()
169 .and_then(|r| r.type_ref())
170 .map(|n| n.syntax().text().to_string()),
171 parameters: param_list(node),
172 generic_parameters: generic_parameters(node),
173 where_predicates: where_predicates(node),
174 // docs are processed separately
175 doc: None,
176 }
177 }
178}
179
180impl Display for FunctionSignature {
181 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
182 if let Some(t) = &self.visibility {
183 write!(f, "{} ", t)?;
184 }
185
186 if let Some(name) = &self.name {
187 match self.kind {
188 CallableKind::Function => write!(f, "fn {}", name)?,
189 CallableKind::StructConstructor => write!(f, "struct {}", name)?,
190 CallableKind::VariantConstructor => write!(f, "{}", name)?,
191 CallableKind::Macro => write!(f, "{}!", name)?,
192 }
193 }
194
195 if !self.generic_parameters.is_empty() {
196 join(self.generic_parameters.iter())
197 .separator(", ")
198 .surround_with("<", ">")
199 .to_fmt(f)?;
200 }
201
202 join(self.parameters.iter()).separator(", ").surround_with("(", ")").to_fmt(f)?;
203
204 if let Some(t) = &self.ret_type {
205 write!(f, " -> {}", t)?;
206 }
207
208 if !self.where_predicates.is_empty() {
209 write!(f, "\nwhere ")?;
210 join(self.where_predicates.iter()).separator(",\n ").to_fmt(f)?;
211 }
212
213 Ok(())
214 }
215}
diff --git a/crates/ra_ide_api/src/display/navigation_target.rs b/crates/ra_ide_api/src/display/navigation_target.rs
deleted file mode 100644
index 6ac60722b..000000000
--- a/crates/ra_ide_api/src/display/navigation_target.rs
+++ /dev/null
@@ -1,411 +0,0 @@
1//! FIXME: write short doc here
2
3use hir::{AssocItem, Either, FieldSource, HasSource, ModuleSource, Source};
4use ra_db::{FileId, SourceDatabase};
5use ra_syntax::{
6 ast::{self, DocCommentsOwner, NameOwner},
7 match_ast, AstNode, SmolStr,
8 SyntaxKind::{self, BIND_PAT},
9 TextRange,
10};
11
12use crate::{db::RootDatabase, expand::original_range, FileSymbol};
13
14use super::short_label::ShortLabel;
15
16/// `NavigationTarget` represents and element in the editor's UI which you can
17/// click on to navigate to a particular piece of code.
18///
19/// Typically, a `NavigationTarget` corresponds to some element in the source
20/// code, like a function or a struct, but this is not strictly required.
21#[derive(Debug, Clone)]
22pub struct NavigationTarget {
23 file_id: FileId,
24 name: SmolStr,
25 kind: SyntaxKind,
26 full_range: TextRange,
27 focus_range: Option<TextRange>,
28 container_name: Option<SmolStr>,
29 description: Option<String>,
30 docs: Option<String>,
31}
32
33pub(crate) trait ToNav {
34 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget;
35}
36
37impl NavigationTarget {
38 /// When `focus_range` is specified, returns it. otherwise
39 /// returns `full_range`
40 pub fn range(&self) -> TextRange {
41 self.focus_range.unwrap_or(self.full_range)
42 }
43
44 pub fn name(&self) -> &SmolStr {
45 &self.name
46 }
47
48 pub fn container_name(&self) -> Option<&SmolStr> {
49 self.container_name.as_ref()
50 }
51
52 pub fn kind(&self) -> SyntaxKind {
53 self.kind
54 }
55
56 pub fn file_id(&self) -> FileId {
57 self.file_id
58 }
59
60 pub fn full_range(&self) -> TextRange {
61 self.full_range
62 }
63
64 pub fn docs(&self) -> Option<&str> {
65 self.docs.as_ref().map(String::as_str)
66 }
67
68 pub fn description(&self) -> Option<&str> {
69 self.description.as_ref().map(String::as_str)
70 }
71
72 /// A "most interesting" range withing the `full_range`.
73 ///
74 /// Typically, `full_range` is the whole syntax node,
75 /// including doc comments, and `focus_range` is the range of the identifier.
76 pub fn focus_range(&self) -> Option<TextRange> {
77 self.focus_range
78 }
79
80 pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget {
81 let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default();
82 if let Some(src) = module.declaration_source(db) {
83 let frange = original_range(db, src.as_ref().map(|it| it.syntax()));
84 return NavigationTarget::from_syntax(
85 frange.file_id,
86 name,
87 None,
88 frange.range,
89 src.value.syntax().kind(),
90 src.value.doc_comment_text(),
91 src.value.short_label(),
92 );
93 }
94 module.to_nav(db)
95 }
96
97 pub(crate) fn from_def(
98 db: &RootDatabase,
99 module_def: hir::ModuleDef,
100 ) -> Option<NavigationTarget> {
101 let nav = match module_def {
102 hir::ModuleDef::Module(module) => module.to_nav(db),
103 hir::ModuleDef::Function(it) => it.to_nav(db),
104 hir::ModuleDef::Adt(it) => it.to_nav(db),
105 hir::ModuleDef::Const(it) => it.to_nav(db),
106 hir::ModuleDef::Static(it) => it.to_nav(db),
107 hir::ModuleDef::EnumVariant(it) => it.to_nav(db),
108 hir::ModuleDef::Trait(it) => it.to_nav(db),
109 hir::ModuleDef::TypeAlias(it) => it.to_nav(db),
110 hir::ModuleDef::BuiltinType(..) => {
111 return None;
112 }
113 };
114 Some(nav)
115 }
116
117 #[cfg(test)]
118 pub(crate) fn assert_match(&self, expected: &str) {
119 let actual = self.debug_render();
120 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
121 }
122
123 #[cfg(test)]
124 pub(crate) fn debug_render(&self) -> String {
125 let mut buf = format!(
126 "{} {:?} {:?} {:?}",
127 self.name(),
128 self.kind(),
129 self.file_id(),
130 self.full_range()
131 );
132 if let Some(focus_range) = self.focus_range() {
133 buf.push_str(&format!(" {:?}", focus_range))
134 }
135 if let Some(container_name) = self.container_name() {
136 buf.push_str(&format!(" {}", container_name))
137 }
138 buf
139 }
140
141 /// Allows `NavigationTarget` to be created from a `NameOwner`
142 pub(crate) fn from_named(
143 db: &RootDatabase,
144 node: Source<&dyn ast::NameOwner>,
145 docs: Option<String>,
146 description: Option<String>,
147 ) -> NavigationTarget {
148 //FIXME: use `_` instead of empty string
149 let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default();
150 let focus_range =
151 node.value.name().map(|it| original_range(db, node.with_value(it.syntax())).range);
152 let frange = original_range(db, node.map(|it| it.syntax()));
153
154 NavigationTarget::from_syntax(
155 frange.file_id,
156 name,
157 focus_range,
158 frange.range,
159 node.value.syntax().kind(),
160 docs,
161 description,
162 )
163 }
164
165 fn from_syntax(
166 file_id: FileId,
167 name: SmolStr,
168 focus_range: Option<TextRange>,
169 full_range: TextRange,
170 kind: SyntaxKind,
171 docs: Option<String>,
172 description: Option<String>,
173 ) -> NavigationTarget {
174 NavigationTarget {
175 file_id,
176 name,
177 kind,
178 full_range,
179 focus_range,
180 container_name: None,
181 description,
182 docs,
183 }
184 }
185}
186
187impl ToNav for FileSymbol {
188 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
189 NavigationTarget {
190 file_id: self.file_id,
191 name: self.name.clone(),
192 kind: self.ptr.kind(),
193 full_range: self.ptr.range(),
194 focus_range: self.name_range,
195 container_name: self.container_name.clone(),
196 description: description_from_symbol(db, self),
197 docs: docs_from_symbol(db, self),
198 }
199 }
200}
201
202pub(crate) trait ToNavFromAst {}
203impl ToNavFromAst for hir::Function {}
204impl ToNavFromAst for hir::Const {}
205impl ToNavFromAst for hir::Static {}
206impl ToNavFromAst for hir::Struct {}
207impl ToNavFromAst for hir::Enum {}
208impl ToNavFromAst for hir::EnumVariant {}
209impl ToNavFromAst for hir::Union {}
210impl ToNavFromAst for hir::TypeAlias {}
211impl ToNavFromAst for hir::Trait {}
212
213impl<D> ToNav for D
214where
215 D: HasSource + ToNavFromAst + Copy,
216 D::Ast: ast::DocCommentsOwner + ast::NameOwner + ShortLabel,
217{
218 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
219 let src = self.source(db);
220 NavigationTarget::from_named(
221 db,
222 src.as_ref().map(|it| it as &dyn ast::NameOwner),
223 src.value.doc_comment_text(),
224 src.value.short_label(),
225 )
226 }
227}
228
229impl ToNav for hir::Module {
230 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
231 let src = self.definition_source(db);
232 let name = self.name(db).map(|it| it.to_string().into()).unwrap_or_default();
233 match &src.value {
234 ModuleSource::SourceFile(node) => {
235 let frange = original_range(db, src.with_value(node.syntax()));
236
237 NavigationTarget::from_syntax(
238 frange.file_id,
239 name,
240 None,
241 frange.range,
242 node.syntax().kind(),
243 None,
244 None,
245 )
246 }
247 ModuleSource::Module(node) => {
248 let frange = original_range(db, src.with_value(node.syntax()));
249
250 NavigationTarget::from_syntax(
251 frange.file_id,
252 name,
253 None,
254 frange.range,
255 node.syntax().kind(),
256 node.doc_comment_text(),
257 node.short_label(),
258 )
259 }
260 }
261 }
262}
263
264impl ToNav for hir::ImplBlock {
265 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
266 let src = self.source(db);
267 let frange = original_range(db, src.as_ref().map(|it| it.syntax()));
268
269 NavigationTarget::from_syntax(
270 frange.file_id,
271 "impl".into(),
272 None,
273 frange.range,
274 src.value.syntax().kind(),
275 None,
276 None,
277 )
278 }
279}
280
281impl ToNav for hir::StructField {
282 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
283 let src = self.source(db);
284
285 match &src.value {
286 FieldSource::Named(it) => NavigationTarget::from_named(
287 db,
288 src.with_value(it),
289 it.doc_comment_text(),
290 it.short_label(),
291 ),
292 FieldSource::Pos(it) => {
293 let frange = original_range(db, src.with_value(it.syntax()));
294 NavigationTarget::from_syntax(
295 frange.file_id,
296 "".into(),
297 None,
298 frange.range,
299 it.syntax().kind(),
300 None,
301 None,
302 )
303 }
304 }
305 }
306}
307
308impl ToNav for hir::MacroDef {
309 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
310 let src = self.source(db);
311 log::debug!("nav target {:#?}", src.value.syntax());
312 NavigationTarget::from_named(
313 db,
314 src.as_ref().map(|it| it as &dyn ast::NameOwner),
315 src.value.doc_comment_text(),
316 None,
317 )
318 }
319}
320
321impl ToNav for hir::Adt {
322 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
323 match self {
324 hir::Adt::Struct(it) => it.to_nav(db),
325 hir::Adt::Union(it) => it.to_nav(db),
326 hir::Adt::Enum(it) => it.to_nav(db),
327 }
328 }
329}
330
331impl ToNav for hir::AssocItem {
332 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
333 match self {
334 AssocItem::Function(it) => it.to_nav(db),
335 AssocItem::Const(it) => it.to_nav(db),
336 AssocItem::TypeAlias(it) => it.to_nav(db),
337 }
338 }
339}
340
341impl ToNav for hir::Local {
342 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
343 let src = self.source(db);
344 let (full_range, focus_range) = match src.value {
345 Either::A(it) => {
346 (it.syntax().text_range(), it.name().map(|it| it.syntax().text_range()))
347 }
348 Either::B(it) => (it.syntax().text_range(), Some(it.self_kw_token().text_range())),
349 };
350 let name = match self.name(db) {
351 Some(it) => it.to_string().into(),
352 None => "".into(),
353 };
354 NavigationTarget {
355 file_id: src.file_id.original_file(db),
356 name,
357 kind: BIND_PAT,
358 full_range,
359 focus_range,
360 container_name: None,
361 description: None,
362 docs: None,
363 }
364 }
365}
366
367pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<String> {
368 let parse = db.parse(symbol.file_id);
369 let node = symbol.ptr.to_node(parse.tree().syntax());
370
371 match_ast! {
372 match node {
373 ast::FnDef(it) => { it.doc_comment_text() },
374 ast::StructDef(it) => { it.doc_comment_text() },
375 ast::EnumDef(it) => { it.doc_comment_text() },
376 ast::TraitDef(it) => { it.doc_comment_text() },
377 ast::Module(it) => { it.doc_comment_text() },
378 ast::TypeAliasDef(it) => { it.doc_comment_text() },
379 ast::ConstDef(it) => { it.doc_comment_text() },
380 ast::StaticDef(it) => { it.doc_comment_text() },
381 ast::RecordFieldDef(it) => { it.doc_comment_text() },
382 ast::EnumVariant(it) => { it.doc_comment_text() },
383 ast::MacroCall(it) => { it.doc_comment_text() },
384 _ => None,
385 }
386 }
387}
388
389/// Get a description of a symbol.
390///
391/// e.g. `struct Name`, `enum Name`, `fn Name`
392pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<String> {
393 let parse = db.parse(symbol.file_id);
394 let node = symbol.ptr.to_node(parse.tree().syntax());
395
396 match_ast! {
397 match node {
398 ast::FnDef(it) => { it.short_label() },
399 ast::StructDef(it) => { it.short_label() },
400 ast::EnumDef(it) => { it.short_label() },
401 ast::TraitDef(it) => { it.short_label() },
402 ast::Module(it) => { it.short_label() },
403 ast::TypeAliasDef(it) => { it.short_label() },
404 ast::ConstDef(it) => { it.short_label() },
405 ast::StaticDef(it) => { it.short_label() },
406 ast::RecordFieldDef(it) => { it.short_label() },
407 ast::EnumVariant(it) => { it.short_label() },
408 _ => None,
409 }
410 }
411}
diff --git a/crates/ra_ide_api/src/display/short_label.rs b/crates/ra_ide_api/src/display/short_label.rs
deleted file mode 100644
index 9ffc9b980..000000000
--- a/crates/ra_ide_api/src/display/short_label.rs
+++ /dev/null
@@ -1,97 +0,0 @@
1//! FIXME: write short doc here
2
3use format_buf::format;
4use ra_syntax::ast::{self, AstNode, NameOwner, TypeAscriptionOwner, VisibilityOwner};
5
6pub(crate) trait ShortLabel {
7 fn short_label(&self) -> Option<String>;
8}
9
10impl ShortLabel for ast::FnDef {
11 fn short_label(&self) -> Option<String> {
12 Some(crate::display::function_label(self))
13 }
14}
15
16impl ShortLabel for ast::StructDef {
17 fn short_label(&self) -> Option<String> {
18 short_label_from_node(self, "struct ")
19 }
20}
21
22impl ShortLabel for ast::UnionDef {
23 fn short_label(&self) -> Option<String> {
24 short_label_from_node(self, "union ")
25 }
26}
27
28impl ShortLabel for ast::EnumDef {
29 fn short_label(&self) -> Option<String> {
30 short_label_from_node(self, "enum ")
31 }
32}
33
34impl ShortLabel for ast::TraitDef {
35 fn short_label(&self) -> Option<String> {
36 short_label_from_node(self, "trait ")
37 }
38}
39
40impl ShortLabel for ast::Module {
41 fn short_label(&self) -> Option<String> {
42 short_label_from_node(self, "mod ")
43 }
44}
45
46impl ShortLabel for ast::TypeAliasDef {
47 fn short_label(&self) -> Option<String> {
48 short_label_from_node(self, "type ")
49 }
50}
51
52impl ShortLabel for ast::ConstDef {
53 fn short_label(&self) -> Option<String> {
54 short_label_from_ascribed_node(self, "const ")
55 }
56}
57
58impl ShortLabel for ast::StaticDef {
59 fn short_label(&self) -> Option<String> {
60 short_label_from_ascribed_node(self, "static ")
61 }
62}
63
64impl ShortLabel for ast::RecordFieldDef {
65 fn short_label(&self) -> Option<String> {
66 short_label_from_ascribed_node(self, "")
67 }
68}
69
70impl ShortLabel for ast::EnumVariant {
71 fn short_label(&self) -> Option<String> {
72 Some(self.name()?.text().to_string())
73 }
74}
75
76fn short_label_from_ascribed_node<T>(node: &T, prefix: &str) -> Option<String>
77where
78 T: NameOwner + VisibilityOwner + TypeAscriptionOwner,
79{
80 let mut buf = short_label_from_node(node, prefix)?;
81
82 if let Some(type_ref) = node.ascribed_type() {
83 format!(buf, ": {}", type_ref.syntax());
84 }
85
86 Some(buf)
87}
88
89fn short_label_from_node<T>(node: &T, label: &str) -> Option<String>
90where
91 T: NameOwner + VisibilityOwner,
92{
93 let mut buf = node.visibility().map(|v| format!("{} ", v.syntax())).unwrap_or_default();
94 buf.push_str(label);
95 buf.push_str(node.name()?.text().as_str());
96 Some(buf)
97}
diff --git a/crates/ra_ide_api/src/display/structure.rs b/crates/ra_ide_api/src/display/structure.rs
deleted file mode 100644
index a80d65ac7..000000000
--- a/crates/ra_ide_api/src/display/structure.rs
+++ /dev/null
@@ -1,401 +0,0 @@
1//! FIXME: write short doc here
2
3use crate::TextRange;
4
5use ra_syntax::{
6 ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner},
7 match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent,
8};
9
10#[derive(Debug, Clone)]
11pub struct StructureNode {
12 pub parent: Option<usize>,
13 pub label: String,
14 pub navigation_range: TextRange,
15 pub node_range: TextRange,
16 pub kind: SyntaxKind,
17 pub detail: Option<String>,
18 pub deprecated: bool,
19}
20
21pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> {
22 let mut res = Vec::new();
23 let mut stack = Vec::new();
24
25 for event in file.syntax().preorder() {
26 match event {
27 WalkEvent::Enter(node) => {
28 if let Some(mut symbol) = structure_node(&node) {
29 symbol.parent = stack.last().copied();
30 stack.push(res.len());
31 res.push(symbol);
32 }
33 }
34 WalkEvent::Leave(node) => {
35 if structure_node(&node).is_some() {
36 stack.pop().unwrap();
37 }
38 }
39 }
40 }
41 res
42}
43
44fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
45 fn decl<N: NameOwner + AttrsOwner>(node: N) -> Option<StructureNode> {
46 decl_with_detail(node, None)
47 }
48
49 fn decl_with_ascription<N: NameOwner + AttrsOwner + TypeAscriptionOwner>(
50 node: N,
51 ) -> Option<StructureNode> {
52 let ty = node.ascribed_type();
53 decl_with_type_ref(node, ty)
54 }
55
56 fn decl_with_type_ref<N: NameOwner + AttrsOwner>(
57 node: N,
58 type_ref: Option<ast::TypeRef>,
59 ) -> Option<StructureNode> {
60 let detail = type_ref.map(|type_ref| {
61 let mut detail = String::new();
62 collapse_ws(type_ref.syntax(), &mut detail);
63 detail
64 });
65 decl_with_detail(node, detail)
66 }
67
68 fn decl_with_detail<N: NameOwner + AttrsOwner>(
69 node: N,
70 detail: Option<String>,
71 ) -> Option<StructureNode> {
72 let name = node.name()?;
73
74 Some(StructureNode {
75 parent: None,
76 label: name.text().to_string(),
77 navigation_range: name.syntax().text_range(),
78 node_range: node.syntax().text_range(),
79 kind: node.syntax().kind(),
80 detail,
81 deprecated: node.attrs().filter_map(|x| x.simple_name()).any(|x| x == "deprecated"),
82 })
83 }
84
85 fn collapse_ws(node: &SyntaxNode, output: &mut String) {
86 let mut can_insert_ws = false;
87 node.text().for_each_chunk(|chunk| {
88 for line in chunk.lines() {
89 let line = line.trim();
90 if line.is_empty() {
91 if can_insert_ws {
92 output.push(' ');
93 can_insert_ws = false;
94 }
95 } else {
96 output.push_str(line);
97 can_insert_ws = true;
98 }
99 }
100 })
101 }
102
103 match_ast! {
104 match node {
105 ast::FnDef(it) => {
106 let mut detail = String::from("fn");
107 if let Some(type_param_list) = it.type_param_list() {
108 collapse_ws(type_param_list.syntax(), &mut detail);
109 }
110 if let Some(param_list) = it.param_list() {
111 collapse_ws(param_list.syntax(), &mut detail);
112 }
113 if let Some(ret_type) = it.ret_type() {
114 detail.push_str(" ");
115 collapse_ws(ret_type.syntax(), &mut detail);
116 }
117
118 decl_with_detail(it, Some(detail))
119 },
120 ast::StructDef(it) => { decl(it) },
121 ast::EnumDef(it) => { decl(it) },
122 ast::EnumVariant(it) => { decl(it) },
123 ast::TraitDef(it) => { decl(it) },
124 ast::Module(it) => { decl(it) },
125 ast::TypeAliasDef(it) => {
126 let ty = it.type_ref();
127 decl_with_type_ref(it, ty)
128 },
129 ast::RecordFieldDef(it) => { decl_with_ascription(it) },
130 ast::ConstDef(it) => { decl_with_ascription(it) },
131 ast::StaticDef(it) => { decl_with_ascription(it) },
132 ast::ImplBlock(it) => {
133 let target_type = it.target_type()?;
134 let target_trait = it.target_trait();
135 let label = match target_trait {
136 None => format!("impl {}", target_type.syntax().text()),
137 Some(t) => {
138 format!("impl {} for {}", t.syntax().text(), target_type.syntax().text(),)
139 }
140 };
141
142 let node = StructureNode {
143 parent: None,
144 label,
145 navigation_range: target_type.syntax().text_range(),
146 node_range: it.syntax().text_range(),
147 kind: it.syntax().kind(),
148 detail: None,
149 deprecated: false,
150 };
151 Some(node)
152 },
153 ast::MacroCall(it) => {
154 let first_token = it.syntax().first_token().unwrap();
155 if first_token.text().as_str() != "macro_rules" {
156 return None;
157 }
158 decl(it)
159 },
160 _ => None,
161 }
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use insta::assert_debug_snapshot;
169
170 #[test]
171 fn test_file_structure() {
172 let file = SourceFile::parse(
173 r#"
174struct Foo {
175 x: i32
176}
177
178mod m {
179 fn bar1() {}
180 fn bar2<T>(t: T) -> T {}
181 fn bar3<A,
182 B>(a: A,
183 b: B) -> Vec<
184 u32
185 > {}
186}
187
188enum E { X, Y(i32) }
189type T = ();
190static S: i32 = 92;
191const C: i32 = 92;
192
193impl E {}
194
195impl fmt::Debug for E {}
196
197macro_rules! mc {
198 () => {}
199}
200
201#[deprecated]
202fn obsolete() {}
203
204#[deprecated(note = "for awhile")]
205fn very_obsolete() {}
206"#,
207 )
208 .ok()
209 .unwrap();
210 let structure = file_structure(&file);
211 assert_debug_snapshot!(structure,
212 @r###"
213 [
214 StructureNode {
215 parent: None,
216 label: "Foo",
217 navigation_range: [8; 11),
218 node_range: [1; 26),
219 kind: STRUCT_DEF,
220 detail: None,
221 deprecated: false,
222 },
223 StructureNode {
224 parent: Some(
225 0,
226 ),
227 label: "x",
228 navigation_range: [18; 19),
229 node_range: [18; 24),
230 kind: RECORD_FIELD_DEF,
231 detail: Some(
232 "i32",
233 ),
234 deprecated: false,
235 },
236 StructureNode {
237 parent: None,
238 label: "m",
239 navigation_range: [32; 33),
240 node_range: [28; 158),
241 kind: MODULE,
242 detail: None,
243 deprecated: false,
244 },
245 StructureNode {
246 parent: Some(
247 2,
248 ),
249 label: "bar1",
250 navigation_range: [43; 47),
251 node_range: [40; 52),
252 kind: FN_DEF,
253 detail: Some(
254 "fn()",
255 ),
256 deprecated: false,
257 },
258 StructureNode {
259 parent: Some(
260 2,
261 ),
262 label: "bar2",
263 navigation_range: [60; 64),
264 node_range: [57; 81),
265 kind: FN_DEF,
266 detail: Some(
267 "fn<T>(t: T) -> T",
268 ),
269 deprecated: false,
270 },
271 StructureNode {
272 parent: Some(
273 2,
274 ),
275 label: "bar3",
276 navigation_range: [89; 93),
277 node_range: [86; 156),
278 kind: FN_DEF,
279 detail: Some(
280 "fn<A, B>(a: A, b: B) -> Vec< u32 >",
281 ),
282 deprecated: false,
283 },
284 StructureNode {
285 parent: None,
286 label: "E",
287 navigation_range: [165; 166),
288 node_range: [160; 180),
289 kind: ENUM_DEF,
290 detail: None,
291 deprecated: false,
292 },
293 StructureNode {
294 parent: Some(
295 6,
296 ),
297 label: "X",
298 navigation_range: [169; 170),
299 node_range: [169; 170),
300 kind: ENUM_VARIANT,
301 detail: None,
302 deprecated: false,
303 },
304 StructureNode {
305 parent: Some(
306 6,
307 ),
308 label: "Y",
309 navigation_range: [172; 173),
310 node_range: [172; 178),
311 kind: ENUM_VARIANT,
312 detail: None,
313 deprecated: false,
314 },
315 StructureNode {
316 parent: None,
317 label: "T",
318 navigation_range: [186; 187),
319 node_range: [181; 193),
320 kind: TYPE_ALIAS_DEF,
321 detail: Some(
322 "()",
323 ),
324 deprecated: false,
325 },
326 StructureNode {
327 parent: None,
328 label: "S",
329 navigation_range: [201; 202),
330 node_range: [194; 213),
331 kind: STATIC_DEF,
332 detail: Some(
333 "i32",
334 ),
335 deprecated: false,
336 },
337 StructureNode {
338 parent: None,
339 label: "C",
340 navigation_range: [220; 221),
341 node_range: [214; 232),
342 kind: CONST_DEF,
343 detail: Some(
344 "i32",
345 ),
346 deprecated: false,
347 },
348 StructureNode {
349 parent: None,
350 label: "impl E",
351 navigation_range: [239; 240),
352 node_range: [234; 243),
353 kind: IMPL_BLOCK,
354 detail: None,
355 deprecated: false,
356 },
357 StructureNode {
358 parent: None,
359 label: "impl fmt::Debug for E",
360 navigation_range: [265; 266),
361 node_range: [245; 269),
362 kind: IMPL_BLOCK,
363 detail: None,
364 deprecated: false,
365 },
366 StructureNode {
367 parent: None,
368 label: "mc",
369 navigation_range: [284; 286),
370 node_range: [271; 303),
371 kind: MACRO_CALL,
372 detail: None,
373 deprecated: false,
374 },
375 StructureNode {
376 parent: None,
377 label: "obsolete",
378 navigation_range: [322; 330),
379 node_range: [305; 335),
380 kind: FN_DEF,
381 detail: Some(
382 "fn()",
383 ),
384 deprecated: true,
385 },
386 StructureNode {
387 parent: None,
388 label: "very_obsolete",
389 navigation_range: [375; 388),
390 node_range: [337; 393),
391 kind: FN_DEF,
392 detail: Some(
393 "fn()",
394 ),
395 deprecated: true,
396 },
397 ]
398 "###
399 );
400 }
401}
diff --git a/crates/ra_ide_api/src/expand.rs b/crates/ra_ide_api/src/expand.rs
deleted file mode 100644
index 2f1abf509..000000000
--- a/crates/ra_ide_api/src/expand.rs
+++ /dev/null
@@ -1,63 +0,0 @@
1//! Utilities to work with files, produced by macros.
2use std::iter::successors;
3
4use hir::Source;
5use ra_db::FileId;
6use ra_syntax::{ast, AstNode, SyntaxNode, SyntaxToken};
7
8use crate::{db::RootDatabase, FileRange};
9
10pub(crate) fn original_range(db: &RootDatabase, node: Source<&SyntaxNode>) -> FileRange {
11 let expansion = match node.file_id.expansion_info(db) {
12 None => {
13 return FileRange {
14 file_id: node.file_id.original_file(db),
15 range: node.value.text_range(),
16 }
17 }
18 Some(it) => it,
19 };
20 // FIXME: the following completely wrong.
21 //
22 // *First*, we should try to map first and last tokens of node, and, if that
23 // fails, return the range of the overall macro expansions.
24 //
25 // *Second*, we should handle recurside macro expansions
26
27 let token = node
28 .value
29 .descendants_with_tokens()
30 .filter_map(|it| it.into_token())
31 .find_map(|it| expansion.map_token_up(node.with_value(&it)));
32
33 match token {
34 Some(it) => {
35 FileRange { file_id: it.file_id.original_file(db), range: it.value.text_range() }
36 }
37 None => {
38 FileRange { file_id: node.file_id.original_file(db), range: node.value.text_range() }
39 }
40 }
41}
42
43pub(crate) fn descend_into_macros(
44 db: &RootDatabase,
45 file_id: FileId,
46 token: SyntaxToken,
47) -> Source<SyntaxToken> {
48 let src = Source::new(file_id.into(), token);
49
50 successors(Some(src), |token| {
51 let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?;
52 let tt = macro_call.token_tree()?;
53 if !token.value.text_range().is_subrange(&tt.syntax().text_range()) {
54 return None;
55 }
56 let source_analyzer =
57 hir::SourceAnalyzer::new(db, token.with_value(token.value.parent()).as_ref(), None);
58 let exp = source_analyzer.expand(db, token.with_value(&macro_call))?;
59 exp.map_token_down(db, token.as_ref())
60 })
61 .last()
62 .unwrap()
63}
diff --git a/crates/ra_ide_api/src/expand_macro.rs b/crates/ra_ide_api/src/expand_macro.rs
deleted file mode 100644
index abc602244..000000000
--- a/crates/ra_ide_api/src/expand_macro.rs
+++ /dev/null
@@ -1,295 +0,0 @@
1//! This modules implements "expand macro" functionality in the IDE
2
3use crate::{db::RootDatabase, FilePosition};
4use hir::db::AstDatabase;
5use ra_db::SourceDatabase;
6use rustc_hash::FxHashMap;
7
8use ra_syntax::{
9 algo::{find_node_at_offset, replace_descendants},
10 ast::{self},
11 AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, T,
12};
13
14pub struct ExpandedMacro {
15 pub name: String,
16 pub expansion: String,
17}
18
19pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
20 let parse = db.parse(position.file_id);
21 let file = parse.tree();
22 let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset)?;
23 let mac = name_ref.syntax().ancestors().find_map(ast::MacroCall::cast)?;
24
25 let source = hir::Source::new(position.file_id.into(), mac.syntax());
26 let expanded = expand_macro_recur(db, source, source.with_value(&mac))?;
27
28 // FIXME:
29 // macro expansion may lose all white space information
30 // But we hope someday we can use ra_fmt for that
31 let expansion = insert_whitespaces(expanded);
32 Some(ExpandedMacro { name: name_ref.text().to_string(), expansion })
33}
34
35fn expand_macro_recur(
36 db: &RootDatabase,
37 source: hir::Source<&SyntaxNode>,
38 macro_call: hir::Source<&ast::MacroCall>,
39) -> Option<SyntaxNode> {
40 let analyzer = hir::SourceAnalyzer::new(db, source, None);
41 let expansion = analyzer.expand(db, macro_call)?;
42 let macro_file_id = expansion.file_id();
43 let mut expanded: SyntaxNode = db.parse_or_expand(macro_file_id)?;
44
45 let children = expanded.descendants().filter_map(ast::MacroCall::cast);
46 let mut replaces = FxHashMap::default();
47
48 for child in children.into_iter() {
49 let node = hir::Source::new(macro_file_id, &child);
50 if let Some(new_node) = expand_macro_recur(db, source, node) {
51 // Replace the whole node if it is root
52 // `replace_descendants` will not replace the parent node
53 // but `SyntaxNode::descendants include itself
54 if expanded == *child.syntax() {
55 expanded = new_node;
56 } else {
57 replaces.insert(child.syntax().clone().into(), new_node.into());
58 }
59 }
60 }
61
62 Some(replace_descendants(&expanded, &replaces))
63}
64
65// FIXME: It would also be cool to share logic here and in the mbe tests,
66// which are pretty unreadable at the moment.
67fn insert_whitespaces(syn: SyntaxNode) -> String {
68 use SyntaxKind::*;
69
70 let mut res = String::new();
71 let mut token_iter = syn
72 .preorder_with_tokens()
73 .filter_map(|event| {
74 if let WalkEvent::Enter(NodeOrToken::Token(token)) = event {
75 Some(token)
76 } else {
77 None
78 }
79 })
80 .peekable();
81
82 let mut indent = 0;
83 let mut last: Option<SyntaxKind> = None;
84
85 while let Some(token) = token_iter.next() {
86 let mut is_next = |f: fn(SyntaxKind) -> bool, default| -> bool {
87 token_iter.peek().map(|it| f(it.kind())).unwrap_or(default)
88 };
89 let is_last = |f: fn(SyntaxKind) -> bool, default| -> bool {
90 last.map(|it| f(it)).unwrap_or(default)
91 };
92
93 res += &match token.kind() {
94 k @ _ if is_text(k) && is_next(|it| !it.is_punct(), true) => {
95 token.text().to_string() + " "
96 }
97 L_CURLY if is_next(|it| it != R_CURLY, true) => {
98 indent += 1;
99 let leading_space = if is_last(|it| is_text(it), false) { " " } else { "" };
100 format!("{}{{\n{}", leading_space, " ".repeat(indent))
101 }
102 R_CURLY if is_last(|it| it != L_CURLY, true) => {
103 indent = indent.checked_sub(1).unwrap_or(0);
104 format!("\n{}}}", " ".repeat(indent))
105 }
106 R_CURLY => format!("}}\n{}", " ".repeat(indent)),
107 T![;] => format!(";\n{}", " ".repeat(indent)),
108 T![->] => " -> ".to_string(),
109 T![=] => " = ".to_string(),
110 T![=>] => " => ".to_string(),
111 _ => token.text().to_string(),
112 };
113
114 last = Some(token.kind());
115 }
116
117 return res;
118
119 fn is_text(k: SyntaxKind) -> bool {
120 k.is_keyword() || k.is_literal() || k == IDENT
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::mock_analysis::analysis_and_position;
128 use insta::assert_snapshot;
129
130 fn check_expand_macro(fixture: &str) -> ExpandedMacro {
131 let (analysis, pos) = analysis_and_position(fixture);
132 analysis.expand_macro(pos).unwrap().unwrap()
133 }
134
135 #[test]
136 fn macro_expand_recursive_expansion() {
137 let res = check_expand_macro(
138 r#"
139 //- /lib.rs
140 macro_rules! bar {
141 () => { fn b() {} }
142 }
143 macro_rules! foo {
144 () => { bar!(); }
145 }
146 macro_rules! baz {
147 () => { foo!(); }
148 }
149 f<|>oo!();
150 "#,
151 );
152
153 assert_eq!(res.name, "foo");
154 assert_snapshot!(res.expansion, @r###"
155fn b(){}
156"###);
157 }
158
159 #[test]
160 fn macro_expand_multiple_lines() {
161 let res = check_expand_macro(
162 r#"
163 //- /lib.rs
164 macro_rules! foo {
165 () => {
166 fn some_thing() -> u32 {
167 let a = 0;
168 a + 10
169 }
170 }
171 }
172 f<|>oo!();
173 "#,
174 );
175
176 assert_eq!(res.name, "foo");
177 assert_snapshot!(res.expansion, @r###"
178fn some_thing() -> u32 {
179 let a = 0;
180 a+10
181}
182"###);
183 }
184
185 #[test]
186 fn macro_expand_match_ast() {
187 let res = check_expand_macro(
188 r#"
189 //- /lib.rs
190 macro_rules! match_ast {
191 (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
192
193 (match ($node:expr) {
194 $( ast::$ast:ident($it:ident) => $res:block, )*
195 _ => $catch_all:expr $(,)?
196 }) => {{
197 $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )*
198 { $catch_all }
199 }};
200 }
201
202 fn main() {
203 mat<|>ch_ast! {
204 match container {
205 ast::TraitDef(it) => {},
206 ast::ImplBlock(it) => {},
207 _ => { continue },
208 }
209 }
210 }
211 "#,
212 );
213
214 assert_eq!(res.name, "match_ast");
215 assert_snapshot!(res.expansion, @r###"
216{
217 if let Some(it) = ast::TraitDef::cast(container.clone()){}
218 else if let Some(it) = ast::ImplBlock::cast(container.clone()){}
219 else {
220 {
221 continue
222 }
223 }
224}
225"###);
226 }
227
228 #[test]
229 fn macro_expand_match_ast_inside_let_statement() {
230 let res = check_expand_macro(
231 r#"
232 //- /lib.rs
233 macro_rules! match_ast {
234 (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
235 (match ($node:expr) {}) => {{}};
236 }
237
238 fn main() {
239 let p = f(|it| {
240 let res = mat<|>ch_ast! { match c {}};
241 Some(res)
242 })?;
243 }
244 "#,
245 );
246
247 assert_eq!(res.name, "match_ast");
248 assert_snapshot!(res.expansion, @r###"{}"###);
249 }
250
251 #[test]
252 fn macro_expand_inner_macro_fail_to_expand() {
253 let res = check_expand_macro(
254 r#"
255 //- /lib.rs
256 macro_rules! bar {
257 (BAD) => {};
258 }
259 macro_rules! foo {
260 () => {bar!()};
261 }
262
263 fn main() {
264 let res = fo<|>o!();
265 }
266 "#,
267 );
268
269 assert_eq!(res.name, "foo");
270 assert_snapshot!(res.expansion, @r###"bar!()"###);
271 }
272
273 #[test]
274 fn macro_expand_with_dollar_crate() {
275 let res = check_expand_macro(
276 r#"
277 //- /lib.rs
278 #[macro_export]
279 macro_rules! bar {
280 () => {0};
281 }
282 macro_rules! foo {
2