aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/Cargo.toml46
-rw-r--r--crates/ra_ide/src/assists.rs28
-rw-r--r--crates/ra_ide/src/call_info.rs592
-rw-r--r--crates/ra_ide/src/change.rs354
-rw-r--r--crates/ra_ide/src/completion.rs77
-rw-r--r--crates/ra_ide/src/completion/complete_dot.rs456
-rw-r--r--crates/ra_ide/src/completion/complete_fn_param.rs136
-rw-r--r--crates/ra_ide/src/completion/complete_keyword.rs781
-rw-r--r--crates/ra_ide/src/completion/complete_macro_in_item_position.rs143
-rw-r--r--crates/ra_ide/src/completion/complete_path.rs785
-rw-r--r--crates/ra_ide/src/completion/complete_pattern.rs89
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs282
-rw-r--r--crates/ra_ide/src/completion/complete_record_literal.rs159
-rw-r--r--crates/ra_ide/src/completion/complete_record_pattern.rs93
-rw-r--r--crates/ra_ide/src/completion/complete_scope.rs876
-rw-r--r--crates/ra_ide/src/completion/complete_snippet.rs120
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs274
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs322
-rw-r--r--crates/ra_ide/src/completion/presentation.rs676
-rw-r--r--crates/ra_ide/src/db.rs144
-rw-r--r--crates/ra_ide/src/diagnostics.rs652
-rw-r--r--crates/ra_ide/src/display.rs84
-rw-r--r--crates/ra_ide/src/display/function_signature.rs215
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs411
-rw-r--r--crates/ra_ide/src/display/short_label.rs97
-rw-r--r--crates/ra_ide/src/display/structure.rs401
-rw-r--r--crates/ra_ide/src/expand.rs63
-rw-r--r--crates/ra_ide/src/expand_macro.rs295
-rw-r--r--crates/ra_ide/src/extend_selection.rs452
-rw-r--r--crates/ra_ide/src/feature_flags.rs70
-rw-r--r--crates/ra_ide/src/folding_ranges.rs378
-rw-r--r--crates/ra_ide/src/goto_definition.rs696
-rw-r--r--crates/ra_ide/src/goto_type_definition.rs105
-rw-r--r--crates/ra_ide/src/hover.rs730
-rw-r--r--crates/ra_ide/src/impls.rs206
-rw-r--r--crates/ra_ide/src/inlay_hints.rs543
-rw-r--r--crates/ra_ide/src/join_lines.rs611
-rw-r--r--crates/ra_ide/src/lib.rs489
-rw-r--r--crates/ra_ide/src/line_index.rs283
-rw-r--r--crates/ra_ide/src/line_index_utils.rs331
-rw-r--r--crates/ra_ide/src/marks.rs13
-rw-r--r--crates/ra_ide/src/matching_brace.rs43
-rw-r--r--crates/ra_ide/src/mock_analysis.rs149
-rw-r--r--crates/ra_ide/src/parent_module.rs104
-rw-r--r--crates/ra_ide/src/references.rs389
-rw-r--r--crates/ra_ide/src/references/classify.rs186
-rw-r--r--crates/ra_ide/src/references/name_definition.rs83
-rw-r--r--crates/ra_ide/src/references/rename.rs328
-rw-r--r--crates/ra_ide/src/references/search_scope.rs145
-rw-r--r--crates/ra_ide/src/runnables.rs242
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html48
-rw-r--r--crates/ra_ide/src/snapshots/rainbow_highlighting.html33
-rw-r--r--crates/ra_ide/src/source_change.rs119
-rw-r--r--crates/ra_ide/src/status.rs136
-rw-r--r--crates/ra_ide/src/symbol_index.rs405
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs342
-rw-r--r--crates/ra_ide/src/syntax_tree.rs359
-rw-r--r--crates/ra_ide/src/test_utils.rs21
-rw-r--r--crates/ra_ide/src/typing.rs490
-rw-r--r--crates/ra_ide/src/wasm_shims.rs19
60 files changed, 17199 insertions, 0 deletions
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml
new file mode 100644
index 000000000..e6383dd35
--- /dev/null
+++ b/crates/ra_ide/Cargo.toml
@@ -0,0 +1,46 @@
1[package]
2edition = "2018"
3name = "ra_ide"
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 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/src/assists.rs b/crates/ra_ide/src/assists.rs
new file mode 100644
index 000000000..e00589733
--- /dev/null
+++ b/crates/ra_ide/src/assists.rs
@@ -0,0 +1,28 @@
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/src/call_info.rs b/crates/ra_ide/src/call_info.rs
new file mode 100644
index 000000000..d559dc4d0
--- /dev/null
+++ b/crates/ra_ide/src/call_info.rs
@@ -0,0 +1,592 @@
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/src/change.rs b/crates/ra_ide/src/change.rs
new file mode 100644
index 000000000..4a76d1dd8
--- /dev/null
+++ b/crates/ra_ide/src/change.rs
@@ -0,0 +1,354 @@
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/src/completion.rs b/crates/ra_ide/src/completion.rs
new file mode 100644
index 000000000..abe1f36ce
--- /dev/null
+++ b/crates/ra_ide/src/completion.rs
@@ -0,0 +1,77 @@
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/src/completion/complete_dot.rs b/crates/ra_ide/src/completion/complete_dot.rs
new file mode 100644
index 000000000..b6fe48627
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_dot.rs
@@ -0,0 +1,456 @@
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/src/completion/complete_fn_param.rs b/crates/ra_ide/src/completion/complete_fn_param.rs
new file mode 100644
index 000000000..502458706
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_fn_param.rs
@@ -0,0 +1,136 @@
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/src/completion/complete_keyword.rs b/crates/ra_ide/src/completion/complete_keyword.rs
new file mode 100644
index 000000000..eb7cd9ac2
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_keyword.rs
@@ -0,0 +1,781 @@
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/src/completion/complete_macro_in_item_position.rs b/crates/ra_ide/src/completion/complete_macro_in_item_position.rs
new file mode 100644
index 000000000..faadd1e3f
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_macro_in_item_position.rs
@@ -0,0 +1,143 @@
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/src/completion/complete_path.rs b/crates/ra_ide/src/completion/complete_path.rs
new file mode 100644
index 000000000..89e0009a1
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_path.rs
@@ -0,0 +1,785 @@
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/src/completion/complete_pattern.rs b/crates/ra_ide/src/completion/complete_pattern.rs
new file mode 100644
index 000000000..fd03b1c40
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_pattern.rs
@@ -0,0 +1,89 @@
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/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs
new file mode 100644
index 000000000..646a30c76
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_postfix.rs
@@ -0,0 +1,282 @@
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/src/completion/complete_record_literal.rs b/crates/ra_ide/src/completion/complete_record_literal.rs
new file mode 100644
index 000000000..577c394d2
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_record_literal.rs
@@ -0,0 +1,159 @@
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/src/completion/complete_record_pattern.rs b/crates/ra_ide/src/completion/complete_record_pattern.rs
new file mode 100644
index 000000000..a56c7e3a1
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_record_pattern.rs
@@ -0,0 +1,93 @@
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/src/completion/complete_scope.rs b/crates/ra_ide/src/completion/complete_scope.rs
new file mode 100644
index 000000000..d5739b58a
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_scope.rs
@@ -0,0 +1,876 @@
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/src/completion/complete_snippet.rs b/crates/ra_ide/src/completion/complete_snippet.rs
new file mode 100644
index 000000000..1f2988b36
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_snippet.rs
@@ -0,0 +1,120 @@
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/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
new file mode 100644
index 000000000..b8345c91d
--- /dev/null
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -0,0 +1,274 @@
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/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs
new file mode 100644
index 000000000..93f336370
--- /dev/null
+++ b/crates/ra_ide/src/completion/completion_item.rs
@@ -0,0 +1,322 @@
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/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs
new file mode 100644
index 000000000..5f056730a
--- /dev/null
+++ b/crates/ra_ide/src/completion/presentation.rs
@@ -0,0 +1,676 @@
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/src/db.rs b/crates/ra_ide/src/db.rs
new file mode 100644
index 000000000..f739ebecd
--- /dev/null
+++ b/crates/ra_ide/src/db.rs
@@ -0,0 +1,144 @@
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/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
new file mode 100644
index 000000000..cc1ccab4b
--- /dev/null
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -0,0 +1,652 @@
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/src/display.rs b/crates/ra_ide/src/display.rs
new file mode 100644
index 000000000..30617412a
--- /dev/null
+++ b/crates/ra_ide/src/display.rs
@@ -0,0 +1,84 @@
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/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs
new file mode 100644
index 000000000..d96de4e4c
--- /dev/null
+++ b/crates/ra_ide/src/display/function_signature.rs
@@ -0,0 +1,215 @@
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/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs
new file mode 100644
index 000000000..6ac60722b
--- /dev/null
+++ b/crates/ra_ide/src/display/navigation_target.rs
@@ -0,0 +1,411 @@
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/src/display/short_label.rs b/crates/ra_ide/src/display/short_label.rs
new file mode 100644
index 000000000..9ffc9b980
--- /dev/null
+++ b/crates/ra_ide/src/display/short_label.rs
@@ -0,0 +1,97 @@
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/src/display/structure.rs b/crates/ra_ide/src/display/structure.rs
new file mode 100644
index 000000000..a80d65ac7
--- /dev/null
+++ b/crates/ra_ide/src/display/structure.rs
@@ -0,0 +1,401 @@
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/src/expand.rs b/crates/ra_ide/src/expand.rs
new file mode 100644
index 000000000..2f1abf509
--- /dev/null
+++ b/crates/ra_ide/src/expand.rs
@@ -0,0 +1,63 @@
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/src/expand_macro.rs b/crates/ra_ide/src/expand_macro.rs
new file mode 100644
index 000000000..abc602244
--- /dev/null
+++ b/crates/ra_ide/src/expand_macro.rs
@@ -0,0 +1,295 @@
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 {
283 () => {$crate::bar!()};
284 }
285
286 fn main() {
287 let res = fo<|>o!();
288 }
289 "#,
290 );
291
292 assert_eq!(res.name, "foo");
293 assert_snapshot!(res.expansion, @r###"0"###);
294 }
295}
diff --git a/crates/ra_ide/src/extend_selection.rs b/crates/ra_ide/src/extend_selection.rs
new file mode 100644
index 000000000..4b7bfc0b1
--- /dev/null
+++ b/crates/ra_ide/src/extend_selection.rs
@@ -0,0 +1,452 @@
1//! FIXME: write short doc here
2
3use ra_db::SourceDatabase;
4use ra_syntax::{
5 algo::find_covering_element,
6 ast::{self, AstNode, AstToken},
7 Direction, NodeOrToken,
8 SyntaxKind::{self, *},
9 SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T,
10};
11
12use crate::{db::RootDatabase, FileRange};
13
14// FIXME: restore macro support
15pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
16 let parse = db.parse(frange.file_id);
17 try_extend_selection(parse.tree().syntax(), frange.range).unwrap_or(frange.range)
18}
19
20fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> {
21 let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING];
22 let list_kinds = [
23 RECORD_FIELD_PAT_LIST,
24 MATCH_ARM_LIST,
25 RECORD_FIELD_DEF_LIST,
26 TUPLE_FIELD_DEF_LIST,
27 RECORD_FIELD_LIST,
28 ENUM_VARIANT_LIST,
29 USE_TREE_LIST,
30 TYPE_PARAM_LIST,
31 TYPE_ARG_LIST,
32 TYPE_BOUND_LIST,
33 PARAM_LIST,
34 ARG_LIST,
35 ARRAY_EXPR,
36 TUPLE_EXPR,
37 WHERE_CLAUSE,
38 ];
39
40 if range.is_empty() {
41 let offset = range.start();
42 let mut leaves = root.token_at_offset(offset);
43 if leaves.clone().all(|it| it.kind() == WHITESPACE) {
44 return Some(extend_ws(root, leaves.next()?, offset));
45 }
46 let leaf_range = match leaves {
47 TokenAtOffset::None => return None,
48 TokenAtOffset::Single(l) => {
49 if string_kinds.contains(&l.kind()) {
50 extend_single_word_in_comment_or_string(&l, offset)
51 .unwrap_or_else(|| l.text_range())
52 } else {
53 l.text_range()
54 }
55 }
56 TokenAtOffset::Between(l, r) => pick_best(l, r).text_range(),
57 };
58 return Some(leaf_range);
59 };
60 let node = match find_covering_element(root, range) {
61 NodeOrToken::Token(token) => {
62 if token.text_range() != range {
63 return Some(token.text_range());
64 }
65 if let Some(comment) = ast::Comment::cast(token.clone()) {
66 if let Some(range) = extend_comments(comment) {
67 return Some(range);
68 }
69 }
70 token.parent()
71 }
72 NodeOrToken::Node(node) => node,
73 };
74 if node.text_range() != range {
75 return Some(node.text_range());
76 }
77
78 // Using shallowest node with same range allows us to traverse siblings.
79 let node = node.ancestors().take_while(|n| n.text_range() == node.text_range()).last().unwrap();
80
81 if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) {
82 if let Some(range) = extend_list_item(&node) {
83 return Some(range);
84 }
85 }
86
87 node.parent().map(|it| it.text_range())
88}
89
90fn extend_single_word_in_comment_or_string(
91 leaf: &SyntaxToken,
92 offset: TextUnit,
93) -> Option<TextRange> {
94 let text: &str = leaf.text();
95 let cursor_position: u32 = (offset - leaf.text_range().start()).into();
96
97 let (before, after) = text.split_at(cursor_position as usize);
98
99 fn non_word_char(c: char) -> bool {
100 !(c.is_alphanumeric() || c == '_')
101 }
102
103 let start_idx = before.rfind(non_word_char)? as u32;
104 let end_idx = after.find(non_word_char).unwrap_or_else(|| after.len()) as u32;
105
106 let from: TextUnit = (start_idx + 1).into();
107 let to: TextUnit = (cursor_position + end_idx).into();
108
109 let range = TextRange::from_to(from, to);
110 if range.is_empty() {
111 None
112 } else {
113 Some(range + leaf.text_range().start())
114 }
115}
116
117fn extend_ws(root: &SyntaxNode, ws: SyntaxToken, offset: TextUnit) -> TextRange {
118 let ws_text = ws.text();
119 let suffix = TextRange::from_to(offset, ws.text_range().end()) - ws.text_range().start();
120 let prefix = TextRange::from_to(ws.text_range().start(), offset) - ws.text_range().start();
121 let ws_suffix = &ws_text.as_str()[suffix];
122 let ws_prefix = &ws_text.as_str()[prefix];
123 if ws_text.contains('\n') && !ws_suffix.contains('\n') {
124 if let Some(node) = ws.next_sibling_or_token() {
125 let start = match ws_prefix.rfind('\n') {
126 Some(idx) => ws.text_range().start() + TextUnit::from((idx + 1) as u32),
127 None => node.text_range().start(),
128 };
129 let end = if root.text().char_at(node.text_range().end()) == Some('\n') {
130 node.text_range().end() + TextUnit::of_char('\n')
131 } else {
132 node.text_range().end()
133 };
134 return TextRange::from_to(start, end);
135 }
136 }
137 ws.text_range()
138}
139
140fn pick_best<'a>(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken {
141 return if priority(&r) > priority(&l) { r } else { l };
142 fn priority(n: &SyntaxToken) -> usize {
143 match n.kind() {
144 WHITESPACE => 0,
145 IDENT | T![self] | T![super] | T![crate] | LIFETIME => 2,
146 _ => 1,
147 }
148 }
149}
150
151/// Extend list item selection to include nearby delimiter and whitespace.
152fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
153 fn is_single_line_ws(node: &SyntaxToken) -> bool {
154 node.kind() == WHITESPACE && !node.text().contains('\n')
155 }
156
157 fn nearby_delimiter(
158 delimiter_kind: SyntaxKind,
159 node: &SyntaxNode,
160 dir: Direction,
161 ) -> Option<SyntaxToken> {
162 node.siblings_with_tokens(dir)
163 .skip(1)
164 .skip_while(|node| match node {
165 NodeOrToken::Node(_) => false,
166 NodeOrToken::Token(it) => is_single_line_ws(it),
167 })
168 .next()
169 .and_then(|it| it.into_token())
170 .filter(|node| node.kind() == delimiter_kind)
171 }
172
173 let delimiter = match node.kind() {
174 TYPE_BOUND => T![+],
175 _ => T![,],
176 };
177 if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Prev) {
178 return Some(TextRange::from_to(
179 delimiter_node.text_range().start(),
180 node.text_range().end(),
181 ));
182 }
183 if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Next) {
184 // Include any following whitespace when delimiter is after list item.
185 let final_node = delimiter_node
186 .next_sibling_or_token()
187 .and_then(|it| it.into_token())
188 .filter(|node| is_single_line_ws(node))
189 .unwrap_or(delimiter_node);
190
191 return Some(TextRange::from_to(node.text_range().start(), final_node.text_range().end()));
192 }
193
194 None
195}
196
197fn extend_comments(comment: ast::Comment) -> Option<TextRange> {
198 let prev = adj_comments(&comment, Direction::Prev);
199 let next = adj_comments(&comment, Direction::Next);
200 if prev != next {
201 Some(TextRange::from_to(
202 prev.syntax().text_range().start(),
203 next.syntax().text_range().end(),
204 ))
205 } else {
206 None
207 }
208}
209
210fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment {
211 let mut res = comment.clone();
212 for element in comment.syntax().siblings_with_tokens(dir) {
213 let token = match element.as_token() {
214 None => break,
215 Some(token) => token,
216 };
217 if let Some(c) = ast::Comment::cast(token.clone()) {
218 res = c
219 } else if token.kind() != WHITESPACE || token.text().contains("\n\n") {
220 break;
221 }
222 }
223 res
224}
225
226#[cfg(test)]
227mod tests {
228 use ra_syntax::{AstNode, SourceFile};
229 use test_utils::extract_offset;
230
231 use super::*;
232
233 fn do_check(before: &str, afters: &[&str]) {
234 let (cursor, before) = extract_offset(before);
235 let parse = SourceFile::parse(&before);
236 let mut range = TextRange::offset_len(cursor, 0.into());
237 for &after in afters {
238 range = try_extend_selection(parse.tree().syntax(), range).unwrap();
239 let actual = &before[range];
240 assert_eq!(after, actual);
241 }
242 }
243
244 #[test]
245 fn test_extend_selection_arith() {
246 do_check(r#"fn foo() { <|>1 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]);
247 }
248
249 #[test]
250 fn test_extend_selection_list() {
251 do_check(r#"fn foo(<|>x: i32) {}"#, &["x", "x: i32"]);
252 do_check(r#"fn foo(<|>x: i32, y: i32) {}"#, &["x", "x: i32", "x: i32, "]);
253 do_check(r#"fn foo(<|>x: i32,y: i32) {}"#, &["x", "x: i32", "x: i32,"]);
254 do_check(r#"fn foo(x: i32, <|>y: i32) {}"#, &["y", "y: i32", ", y: i32"]);
255 do_check(r#"fn foo(x: i32, <|>y: i32, ) {}"#, &["y", "y: i32", ", y: i32"]);
256 do_check(r#"fn foo(x: i32,<|>y: i32) {}"#, &["y", "y: i32", ",y: i32"]);
257
258 do_check(r#"const FOO: [usize; 2] = [ 22<|> , 33];"#, &["22", "22 , "]);
259 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|>];"#, &["33", ", 33"]);
260 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|> ,];"#, &["33", ", 33"]);
261
262 do_check(r#"fn main() { (1, 2<|>) }"#, &["2", ", 2", "(1, 2)"]);
263
264 do_check(
265 r#"
266const FOO: [usize; 2] = [
267 22,
268 <|>33,
269]"#,
270 &["33", "33,"],
271 );
272
273 do_check(
274 r#"
275const FOO: [usize; 2] = [
276 22
277 , 33<|>,
278]"#,
279 &["33", ", 33"],
280 );
281 }
282
283 #[test]
284 fn test_extend_selection_start_of_the_line() {
285 do_check(
286 r#"
287impl S {
288<|> fn foo() {
289
290 }
291}"#,
292 &[" fn foo() {\n\n }\n"],
293 );
294 }
295
296 #[test]
297 fn test_extend_selection_doc_comments() {
298 do_check(
299 r#"
300struct A;
301
302/// bla
303/// bla
304struct B {
305 <|>
306}
307 "#,
308 &["\n \n", "{\n \n}", "/// bla\n/// bla\nstruct B {\n \n}"],
309 )
310 }
311
312 #[test]
313 fn test_extend_selection_comments() {
314 do_check(
315 r#"
316fn bar(){}
317
318// fn foo() {
319// 1 + <|>1
320// }
321
322// fn foo(){}
323 "#,
324 &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"],
325 );
326
327 do_check(
328 r#"
329// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
330// pub enum Direction {
331// <|> Next,
332// Prev
333// }
334"#,
335 &[
336 "// Next,",
337 "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }",
338 ],
339 );
340
341 do_check(
342 r#"
343/*
344foo
345_bar1<|>*/
346"#,
347 &["_bar1", "/*\nfoo\n_bar1*/"],
348 );
349
350 do_check(r#"//!<|>foo_2 bar"#, &["foo_2", "//!foo_2 bar"]);
351
352 do_check(r#"/<|>/foo bar"#, &["//foo bar"]);
353 }
354
355 #[test]
356 fn test_extend_selection_prefer_idents() {
357 do_check(
358 r#"
359fn main() { foo<|>+bar;}
360"#,
361 &["foo", "foo+bar"],
362 );
363 do_check(
364 r#"
365fn main() { foo+<|>bar;}
366"#,
367 &["bar", "foo+bar"],
368 );
369 }
370
371 #[test]
372 fn test_extend_selection_prefer_lifetimes() {
373 do_check(r#"fn foo<<|>'a>() {}"#, &["'a", "<'a>"]);
374 do_check(r#"fn foo<'a<|>>() {}"#, &["'a", "<'a>"]);
375 }
376
377 #[test]
378 fn test_extend_selection_select_first_word() {
379 do_check(r#"// foo bar b<|>az quxx"#, &["baz", "// foo bar baz quxx"]);
380 do_check(
381 r#"
382impl S {
383fn foo() {
384// hel<|>lo world
385}
386}
387"#,
388 &["hello", "// hello world"],
389 );
390 }
391
392 #[test]
393 fn test_extend_selection_string() {
394 do_check(
395 r#"
396fn bar(){}
397
398" fn f<|>oo() {"
399"#,
400 &["foo", "\" fn foo() {\""],
401 );
402 }
403
404 #[test]
405 fn test_extend_trait_bounds_list_in_where_clause() {
406 do_check(
407 r#"
408fn foo<R>()
409 where
410 R: req::Request + 'static,
411 R::Params: DeserializeOwned<|> + panic::UnwindSafe + 'static,
412 R::Result: Serialize + 'static,
413"#,
414 &[
415 "DeserializeOwned",
416 "DeserializeOwned + ",
417 "DeserializeOwned + panic::UnwindSafe + 'static",
418 "R::Params: DeserializeOwned + panic::UnwindSafe + 'static",
419 "R::Params: DeserializeOwned + panic::UnwindSafe + 'static,",
420 ],
421 );
422 do_check(r#"fn foo<T>() where T: <|>Copy"#, &["Copy"]);
423 do_check(r#"fn foo<T>() where T: <|>Copy + Display"#, &["Copy", "Copy + "]);
424 do_check(r#"fn foo<T>() where T: <|>Copy +Display"#, &["Copy", "Copy +"]);
425 do_check(r#"fn foo<T>() where T: <|>Copy+Display"#, &["Copy", "Copy+"]);
426 do_check(r#"fn foo<T>() where T: Copy + <|>Display"#, &["Display", "+ Display"]);
427 do_check(r#"fn foo<T>() where T: Copy + <|>Display + Sync"#, &["Display", "+ Display"]);
428 do_check(r#"fn foo<T>() where T: Copy +<|>Display"#, &["Display", "+Display"]);
429 }
430
431 #[test]
432 fn test_extend_trait_bounds_list_inline() {
433 do_check(r#"fn foo<T: <|>Copy>() {}"#, &["Copy"]);
434 do_check(r#"fn foo<T: <|>Copy + Display>() {}"#, &["Copy", "Copy + "]);
435 do_check(r#"fn foo<T: <|>Copy +Display>() {}"#, &["Copy", "Copy +"]);
436 do_check(r#"fn foo<T: <|>Copy+Display>() {}"#, &["Copy", "Copy+"]);
437 do_check(r#"fn foo<T: Copy + <|>Display>() {}"#, &["Display", "+ Display"]);
438 do_check(r#"fn foo<T: Copy + <|>Display + Sync>() {}"#, &["Display", "+ Display"]);
439 do_check(r#"fn foo<T: Copy +<|>Display>() {}"#, &["Display", "+Display"]);
440 do_check(
441 r#"fn foo<T: Copy<|> + Display, U: Copy>() {}"#,
442 &[
443 "Copy",
444 "Copy + ",
445 "Copy + Display",
446 "T: Copy + Display",
447 "T: Copy + Display, ",
448 "<T: Copy + Display, U: Copy>",
449 ],
450 );
451 }
452}
diff --git a/crates/ra_ide/src/feature_flags.rs b/crates/ra_ide/src/feature_flags.rs
new file mode 100644
index 000000000..de4ae513d
--- /dev/null
+++ b/crates/ra_ide/src/feature_flags.rs
@@ -0,0 +1,70 @@
1//! FIXME: write short doc here
2
3use rustc_hash::FxHashMap;
4
5/// Feature flags hold fine-grained toggles for all *user-visible* features of
6/// rust-analyzer.
7///
8/// The exists such that users are able to disable any annoying feature (and,
9/// with many users and many features, some features are bound to be annoying
10/// for some users)
11///
12/// Note that we purposefully use run-time checked strings, and not something
13/// checked at compile time, to keep things simple and flexible.
14///
15/// Also note that, at the moment, `FeatureFlags` also store features for
16/// `ra_lsp_server`. This should be benign layering violation.
17#[derive(Debug)]
18pub struct FeatureFlags {
19 flags: FxHashMap<String, bool>,
20}
21
22impl FeatureFlags {
23 fn new(flags: &[(&str, bool)]) -> FeatureFlags {
24 let flags = flags
25 .iter()
26 .map(|&(name, value)| {
27 check_flag_name(name);
28 (name.to_string(), value)
29 })
30 .collect();
31 FeatureFlags { flags }
32 }
33
34 pub fn set(&mut self, flag: &str, value: bool) -> Result<(), ()> {
35 match self.flags.get_mut(flag) {
36 None => Err(()),
37 Some(slot) => {
38 *slot = value;
39 Ok(())
40 }
41 }
42 }
43
44 pub fn get(&self, flag: &str) -> bool {
45 match self.flags.get(flag) {
46 None => panic!("unknown flag: {:?}", flag),
47 Some(value) => *value,
48 }
49 }
50}
51
52impl Default for FeatureFlags {
53 fn default() -> FeatureFlags {
54 FeatureFlags::new(&[
55 ("lsp.diagnostics", true),
56 ("completion.insertion.add-call-parenthesis", true),
57 ("completion.enable-postfix", true),
58 ("notifications.workspace-loaded", true),
59 ])
60 }
61}
62
63fn check_flag_name(flag: &str) {
64 for c in flag.bytes() {
65 match c {
66 b'a'..=b'z' | b'-' | b'.' => (),
67 _ => panic!("flag name does not match conventions: {:?}", flag),
68 }
69 }
70}
diff --git a/crates/ra_ide/src/folding_ranges.rs b/crates/ra_ide/src/folding_ranges.rs
new file mode 100644
index 000000000..4eeb76d14
--- /dev/null
+++ b/crates/ra_ide/src/folding_ranges.rs
@@ -0,0 +1,378 @@
1//! FIXME: write short doc here
2
3use rustc_hash::FxHashSet;
4
5use ra_syntax::{
6 ast::{self, AstNode, AstToken, VisibilityOwner},
7 Direction, NodeOrToken, SourceFile,
8 SyntaxKind::{self, *},
9 SyntaxNode, TextRange,
10};
11
12#[derive(Debug, PartialEq, Eq)]
13pub enum FoldKind {
14 Comment,
15 Imports,
16 Mods,
17 Block,
18}
19
20#[derive(Debug)]
21pub struct Fold {
22 pub range: TextRange,
23 pub kind: FoldKind,
24}
25
26pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> {
27 let mut res = vec![];
28 let mut visited_comments = FxHashSet::default();
29 let mut visited_imports = FxHashSet::default();
30 let mut visited_mods = FxHashSet::default();
31
32 for element in file.syntax().descendants_with_tokens() {
33 // Fold items that span multiple lines
34 if let Some(kind) = fold_kind(element.kind()) {
35 let is_multiline = match &element {
36 NodeOrToken::Node(node) => node.text().contains_char('\n'),
37 NodeOrToken::Token(token) => token.text().contains('\n'),
38 };
39 if is_multiline {
40 res.push(Fold { range: element.text_range(), kind });
41 continue;
42 }
43 }
44
45 match element {
46 NodeOrToken::Token(token) => {
47 // Fold groups of comments
48 if let Some(comment) = ast::Comment::cast(token) {
49 if !visited_comments.contains(&comment) {
50 if let Some(range) =
51 contiguous_range_for_comment(comment, &mut visited_comments)
52 {
53 res.push(Fold { range, kind: FoldKind::Comment })
54 }
55 }
56 }
57 }
58 NodeOrToken::Node(node) => {
59 // Fold groups of imports
60 if node.kind() == USE_ITEM && !visited_imports.contains(&node) {
61 if let Some(range) = contiguous_range_for_group(&node, &mut visited_imports) {
62 res.push(Fold { range, kind: FoldKind::Imports })
63 }
64 }
65
66 // Fold groups of mods
67 if node.kind() == MODULE && !has_visibility(&node) && !visited_mods.contains(&node)
68 {
69 if let Some(range) =
70 contiguous_range_for_group_unless(&node, has_visibility, &mut visited_mods)
71 {
72 res.push(Fold { range, kind: FoldKind::Mods })
73 }
74 }
75 }
76 }
77 }
78
79 res
80}
81
82fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> {
83 match kind {
84 COMMENT => Some(FoldKind::Comment),
85 USE_ITEM => Some(FoldKind::Imports),
86 RECORD_FIELD_DEF_LIST
87 | RECORD_FIELD_PAT_LIST
88 | ITEM_LIST
89 | EXTERN_ITEM_LIST
90 | USE_TREE_LIST
91 | BLOCK
92 | MATCH_ARM_LIST
93 | ENUM_VARIANT_LIST
94 | TOKEN_TREE => Some(FoldKind::Block),
95 _ => None,
96 }
97}
98
99fn has_visibility(node: &SyntaxNode) -> bool {
100 ast::Module::cast(node.clone()).and_then(|m| m.visibility()).is_some()
101}
102
103fn contiguous_range_for_group(
104 first: &SyntaxNode,
105 visited: &mut FxHashSet<SyntaxNode>,
106) -> Option<TextRange> {
107 contiguous_range_for_group_unless(first, |_| false, visited)
108}
109
110fn contiguous_range_for_group_unless(
111 first: &SyntaxNode,
112 unless: impl Fn(&SyntaxNode) -> bool,
113 visited: &mut FxHashSet<SyntaxNode>,
114) -> Option<TextRange> {
115 visited.insert(first.clone());
116
117 let mut last = first.clone();
118 for element in first.siblings_with_tokens(Direction::Next) {
119 let node = match element {
120 NodeOrToken::Token(token) => {
121 if let Some(ws) = ast::Whitespace::cast(token) {
122 if !ws.spans_multiple_lines() {
123 // Ignore whitespace without blank lines
124 continue;
125 }
126 }
127 // There is a blank line or another token, which means that the
128 // group ends here
129 break;
130 }
131 NodeOrToken::Node(node) => node,
132 };
133
134 // Stop if we find a node that doesn't belong to the group
135 if node.kind() != first.kind() || unless(&node) {
136 break;
137 }
138
139 visited.insert(node.clone());
140 last = node;
141 }
142
143 if first != &last {
144 Some(TextRange::from_to(first.text_range().start(), last.text_range().end()))
145 } else {
146 // The group consists of only one element, therefore it cannot be folded
147 None
148 }
149}
150
151fn contiguous_range_for_comment(
152 first: ast::Comment,
153 visited: &mut FxHashSet<ast::Comment>,
154) -> Option<TextRange> {
155 visited.insert(first.clone());
156
157 // Only fold comments of the same flavor
158 let group_kind = first.kind();
159 if !group_kind.shape.is_line() {
160 return None;
161 }
162
163 let mut last = first.clone();
164 for element in first.syntax().siblings_with_tokens(Direction::Next) {
165 match element {
166 NodeOrToken::Token(token) => {
167 if let Some(ws) = ast::Whitespace::cast(token.clone()) {
168 if !ws.spans_multiple_lines() {
169 // Ignore whitespace without blank lines
170 continue;
171 }
172 }
173 if let Some(c) = ast::Comment::cast(token) {
174 if c.kind() == group_kind {
175 visited.insert(c.clone());
176 last = c;
177 continue;
178 }
179 }
180 // The comment group ends because either:
181 // * An element of a different kind was reached
182 // * A comment of a different flavor was reached
183 break;
184 }
185 NodeOrToken::Node(_) => break,
186 };
187 }
188
189 if first != last {
190 Some(TextRange::from_to(
191 first.syntax().text_range().start(),
192 last.syntax().text_range().end(),
193 ))
194 } else {
195 // The group consists of only one element, therefore it cannot be folded
196 None
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use test_utils::extract_ranges;
204
205 fn do_check(text: &str, fold_kinds: &[FoldKind]) {
206 let (ranges, text) = extract_ranges(text, "fold");
207 let parse = SourceFile::parse(&text);
208 let folds = folding_ranges(&parse.tree());
209
210 assert_eq!(
211 folds.len(),
212 ranges.len(),
213 "The amount of folds is different than the expected amount"
214 );
215 assert_eq!(
216 folds.len(),
217 fold_kinds.len(),
218 "The amount of fold kinds is different than the expected amount"
219 );
220 for ((fold, range), fold_kind) in
221 folds.iter().zip(ranges.into_iter()).zip(fold_kinds.iter())
222 {
223 assert_eq!(fold.range.start(), range.start());
224 assert_eq!(fold.range.end(), range.end());
225 assert_eq!(&fold.kind, fold_kind);
226 }
227 }
228
229 #[test]
230 fn test_fold_comments() {
231 let text = r#"
232<fold>// Hello
233// this is a multiline
234// comment
235//</fold>
236
237// But this is not
238
239fn main() <fold>{
240 <fold>// We should
241 // also
242 // fold
243 // this one.</fold>
244 <fold>//! But this one is different
245 //! because it has another flavor</fold>
246 <fold>/* As does this
247 multiline comment */</fold>
248}</fold>"#;
249
250 let fold_kinds = &[
251 FoldKind::Comment,
252 FoldKind::Block,
253 FoldKind::Comment,
254 FoldKind::Comment,
255 FoldKind::Comment,
256 ];
257 do_check(text, fold_kinds);
258 }
259
260 #[test]
261 fn test_fold_imports() {
262 let text = r#"
263<fold>use std::<fold>{
264 str,
265 vec,
266 io as iop
267}</fold>;</fold>
268
269fn main() <fold>{
270}</fold>"#;
271
272 let folds = &[FoldKind::Imports, FoldKind::Block, FoldKind::Block];
273 do_check(text, folds);
274 }
275
276 #[test]
277 fn test_fold_mods() {
278 let text = r#"
279
280pub mod foo;
281<fold>mod after_pub;
282mod after_pub_next;</fold>
283
284<fold>mod before_pub;
285mod before_pub_next;</fold>
286pub mod bar;
287
288mod not_folding_single;
289pub mod foobar;
290pub not_folding_single_next;
291
292<fold>#[cfg(test)]
293mod with_attribute;
294mod with_attribute_next;</fold>
295
296fn main() <fold>{
297}</fold>"#;
298
299 let folds = &[FoldKind::Mods, FoldKind::Mods, FoldKind::Mods, FoldKind::Block];
300 do_check(text, folds);
301 }
302
303 #[test]
304 fn test_fold_import_groups() {
305 let text = r#"
306<fold>use std::str;
307use std::vec;
308use std::io as iop;</fold>
309
310<fold>use std::mem;
311use std::f64;</fold>
312
313use std::collections::HashMap;
314// Some random comment
315use std::collections::VecDeque;
316
317fn main() <fold>{
318}</fold>"#;
319
320 let folds = &[FoldKind::Imports, FoldKind::Imports, FoldKind::Block];
321 do_check(text, folds);
322 }
323
324 #[test]
325 fn test_fold_import_and_groups() {
326 let text = r#"
327<fold>use std::str;
328use std::vec;
329use std::io as iop;</fold>
330
331<fold>use std::mem;
332use std::f64;</fold>
333
334<fold>use std::collections::<fold>{
335 HashMap,
336 VecDeque,
337}</fold>;</fold>
338// Some random comment
339
340fn main() <fold>{
341}</fold>"#;
342
343 let folds = &[
344 FoldKind::Imports,
345 FoldKind::Imports,
346 FoldKind::Imports,
347 FoldKind::Block,
348 FoldKind::Block,
349 ];
350 do_check(text, folds);
351 }
352
353 #[test]
354 fn test_folds_macros() {
355 let text = r#"
356macro_rules! foo <fold>{
357 ($($tt:tt)*) => { $($tt)* }
358}</fold>
359"#;
360
361 let folds = &[FoldKind::Block];
362 do_check(text, folds);
363 }
364
365 #[test]
366 fn test_fold_match_arms() {
367 let text = r#"
368fn main() <fold>{
369 match 0 <fold>{
370 0 => 0,
371 _ => 1,
372 }</fold>
373}</fold>"#;
374
375 let folds = &[FoldKind::Block, FoldKind::Block];
376 do_check(text, folds);
377 }
378}
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs
new file mode 100644
index 000000000..c10a6c844
--- /dev/null
+++ b/crates/ra_ide/src/goto_definition.rs
@@ -0,0 +1,696 @@
1//! FIXME: write short doc here
2
3use hir::{db::AstDatabase, Source};
4use ra_syntax::{
5 ast::{self, DocCommentsOwner},
6 match_ast, AstNode, SyntaxNode,
7};
8
9use crate::{
10 db::RootDatabase,
11 display::{ShortLabel, ToNav},
12 expand::descend_into_macros,
13 references::{classify_name_ref, NameKind::*},
14 FilePosition, NavigationTarget, RangeInfo,
15};
16
17pub(crate) fn goto_definition(
18 db: &RootDatabase,
19 position: FilePosition,
20) -> Option<RangeInfo<Vec<NavigationTarget>>> {
21 let file = db.parse_or_expand(position.file_id.into())?;
22 let token = file.token_at_offset(position.offset).filter(|it| !it.kind().is_trivia()).next()?;
23 let token = descend_into_macros(db, position.file_id, token);
24
25 let res = match_ast! {
26 match (token.value.parent()) {
27 ast::NameRef(name_ref) => {
28 let navs = reference_definition(db, token.with_value(&name_ref)).to_vec();
29 RangeInfo::new(name_ref.syntax().text_range(), navs.to_vec())
30 },
31 ast::Name(name) => {
32 let navs = name_definition(db, token.with_value(&name))?;
33 RangeInfo::new(name.syntax().text_range(), navs)
34
35 },
36 _ => return None,
37 }
38 };
39
40 Some(res)
41}
42
43#[derive(Debug)]
44pub(crate) enum ReferenceResult {
45 Exact(NavigationTarget),
46 Approximate(Vec<NavigationTarget>),
47}
48
49impl ReferenceResult {
50 fn to_vec(self) -> Vec<NavigationTarget> {
51 use self::ReferenceResult::*;
52 match self {
53 Exact(target) => vec![target],
54 Approximate(vec) => vec,
55 }
56 }
57}
58
59pub(crate) fn reference_definition(
60 db: &RootDatabase,
61 name_ref: Source<&ast::NameRef>,
62) -> ReferenceResult {
63 use self::ReferenceResult::*;
64
65 let name_kind = classify_name_ref(db, name_ref).map(|d| d.kind);
66 match name_kind {
67 Some(Macro(mac)) => return Exact(mac.to_nav(db)),
68 Some(Field(field)) => return Exact(field.to_nav(db)),
69 Some(AssocItem(assoc)) => return Exact(assoc.to_nav(db)),
70 Some(Def(def)) => match NavigationTarget::from_def(db, def) {
71 Some(nav) => return Exact(nav),
72 None => return Approximate(vec![]),
73 },
74 Some(SelfType(imp)) => {
75 // FIXME: ideally, this should point to the type in the impl, and
76 // not at the whole impl. And goto **type** definition should bring
77 // us to the actual type
78 return Exact(imp.to_nav(db));
79 }
80 Some(Local(local)) => return Exact(local.to_nav(db)),
81 Some(GenericParam(_)) => {
82 // FIXME: go to the generic param def
83 }
84 None => {}
85 };
86
87 // Fallback index based approach:
88 let navs = crate::symbol_index::index_resolve(db, name_ref.value)
89 .into_iter()
90 .map(|s| s.to_nav(db))
91 .collect();
92 Approximate(navs)
93}
94
95pub(crate) fn name_definition(
96 db: &RootDatabase,
97 name: Source<&ast::Name>,
98) -> Option<Vec<NavigationTarget>> {
99 let parent = name.value.syntax().parent()?;
100
101 if let Some(module) = ast::Module::cast(parent.clone()) {
102 if module.has_semi() {
103 let src = name.with_value(module);
104 if let Some(child_module) = hir::Module::from_declaration(db, src) {
105 let nav = child_module.to_nav(db);
106 return Some(vec![nav]);
107 }
108 }
109 }
110
111 if let Some(nav) = named_target(db, name.with_value(&parent)) {
112 return Some(vec![nav]);
113 }
114
115 None
116}
117
118fn named_target(db: &RootDatabase, node: Source<&SyntaxNode>) -> Option<NavigationTarget> {
119 match_ast! {
120 match (node.value) {
121 ast::StructDef(it) => {
122 Some(NavigationTarget::from_named(
123 db,
124 node.with_value(&it),
125 it.doc_comment_text(),
126 it.short_label(),
127 ))
128 },
129 ast::EnumDef(it) => {
130 Some(NavigationTarget::from_named(
131 db,
132 node.with_value(&it),
133 it.doc_comment_text(),
134 it.short_label(),
135 ))
136 },
137 ast::EnumVariant(it) => {
138 Some(NavigationTarget::from_named(
139 db,
140 node.with_value(&it),
141 it.doc_comment_text(),
142 it.short_label(),
143 ))
144 },
145 ast::FnDef(it) => {
146 Some(NavigationTarget::from_named(
147 db,
148 node.with_value(&it),
149 it.doc_comment_text(),
150 it.short_label(),
151 ))
152 },
153 ast::TypeAliasDef(it) => {
154 Some(NavigationTarget::from_named(
155 db,
156 node.with_value(&it),
157 it.doc_comment_text(),
158 it.short_label(),
159 ))
160 },
161 ast::ConstDef(it) => {
162 Some(NavigationTarget::from_named(
163 db,
164 node.with_value(&it),
165 it.doc_comment_text(),
166 it.short_label(),
167 ))
168 },
169 ast::StaticDef(it) => {
170 Some(NavigationTarget::from_named(
171 db,
172 node.with_value(&it),
173 it.doc_comment_text(),
174 it.short_label(),
175 ))
176 },
177 ast::TraitDef(it) => {
178 Some(NavigationTarget::from_named(
179 db,
180 node.with_value(&it),
181 it.doc_comment_text(),
182 it.short_label(),
183 ))
184 },
185 ast::RecordFieldDef(it) => {
186 Some(NavigationTarget::from_named(
187 db,
188 node.with_value(&it),
189 it.doc_comment_text(),
190 it.short_label(),
191 ))
192 },
193 ast::Module(it) => {
194 Some(NavigationTarget::from_named(
195 db,
196 node.with_value(&it),
197 it.doc_comment_text(),
198 it.short_label(),
199 ))
200 },
201 ast::MacroCall(it) => {
202 Some(NavigationTarget::from_named(
203 db,
204 node.with_value(&it),
205 it.doc_comment_text(),
206 None,
207 ))
208 },
209 _ => None,
210 }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use test_utils::covers;
217
218 use crate::mock_analysis::analysis_and_position;
219
220 fn check_goto(fixture: &str, expected: &str) {
221 let (analysis, pos) = analysis_and_position(fixture);
222
223 let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info;
224 assert_eq!(navs.len(), 1);
225 let nav = navs.pop().unwrap();
226 nav.assert_match(expected);
227 }
228
229 #[test]
230 fn goto_definition_works_in_items() {
231 check_goto(
232 "
233 //- /lib.rs
234 struct Foo;
235 enum E { X(Foo<|>) }
236 ",
237 "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)",
238 );
239 }
240
241 #[test]
242 fn goto_definition_resolves_correct_name() {
243 check_goto(
244 "
245 //- /lib.rs
246 use a::Foo;
247 mod a;
248 mod b;
249 enum E { X(Foo<|>) }
250 //- /a.rs
251 struct Foo;
252 //- /b.rs
253 struct Foo;
254 ",
255 "Foo STRUCT_DEF FileId(2) [0; 11) [7; 10)",
256 );
257 }
258
259 #[test]
260 fn goto_definition_works_for_module_declaration() {
261 check_goto(
262 "
263 //- /lib.rs
264 mod <|>foo;
265 //- /foo.rs
266 // empty
267 ",
268 "foo SOURCE_FILE FileId(2) [0; 10)",
269 );
270
271 check_goto(
272 "
273 //- /lib.rs
274 mod <|>foo;
275 //- /foo/mod.rs
276 // empty
277 ",
278 "foo SOURCE_FILE FileId(2) [0; 10)",
279 );
280 }
281
282 #[test]
283 fn goto_definition_works_for_macros() {
284 covers!(goto_definition_works_for_macros);
285 check_goto(
286 "
287 //- /lib.rs
288 macro_rules! foo {
289 () => {
290 {}
291 };
292 }
293
294 fn bar() {
295 <|>foo!();
296 }
297 ",
298 "foo MACRO_CALL FileId(1) [0; 50) [13; 16)",
299 );
300 }
301
302 #[test]
303 fn goto_definition_works_for_macros_from_other_crates() {
304 covers!(goto_definition_works_for_macros);
305 check_goto(
306 "
307 //- /lib.rs
308 use foo::foo;
309 fn bar() {
310 <|>foo!();
311 }
312
313 //- /foo/lib.rs
314 #[macro_export]
315 macro_rules! foo {
316 () => {
317 {}
318 };
319 }
320 ",
321 "foo MACRO_CALL FileId(2) [0; 66) [29; 32)",
322 );
323 }
324
325 #[test]
326 fn goto_definition_works_for_macros_in_use_tree() {
327 check_goto(
328 "
329 //- /lib.rs
330 use foo::foo<|>;
331
332 //- /foo/lib.rs
333 #[macro_export]
334 macro_rules! foo {
335 () => {
336 {}
337 };
338 }
339 ",
340 "foo MACRO_CALL FileId(2) [0; 66) [29; 32)",
341 );
342 }
343
344 #[test]
345 fn goto_definition_works_for_macro_defined_fn_with_arg() {
346 check_goto(
347 "
348 //- /lib.rs
349 macro_rules! define_fn {
350 ($name:ident) => (fn $name() {})
351 }
352
353 define_fn!(
354 foo
355 )
356
357 fn bar() {
358 <|>foo();
359 }
360 ",
361 "foo FN_DEF FileId(1) [80; 83) [80; 83)",
362 );
363 }
364
365 #[test]
366 fn goto_definition_works_for_macro_defined_fn_no_arg() {
367 check_goto(
368 "
369 //- /lib.rs
370 macro_rules! define_fn {
371 () => (fn foo() {})
372 }
373
374 define_fn!();
375
376 fn bar() {
377 <|>foo();
378 }
379 ",
380 "foo FN_DEF FileId(1) [39; 42) [39; 42)",
381 );
382 }
383
384 #[test]
385 fn goto_definition_works_for_methods() {
386 covers!(goto_definition_works_for_methods);
387 check_goto(
388 "
389 //- /lib.rs
390 struct Foo;
391 impl Foo {
392 fn frobnicate(&self) { }
393 }
394
395 fn bar(foo: &Foo) {
396 foo.frobnicate<|>();
397 }
398 ",
399 "frobnicate FN_DEF FileId(1) [27; 52) [30; 40)",
400 );
401 }
402
403 #[test]
404 fn goto_definition_works_for_fields() {
405 covers!(goto_definition_works_for_fields);
406 check_goto(
407 "
408 //- /lib.rs
409 struct Foo {
410 spam: u32,
411 }
412
413 fn bar(foo: &Foo) {
414 foo.spam<|>;
415 }
416 ",
417 "spam RECORD_FIELD_DEF FileId(1) [17; 26) [17; 21)",
418 );
419 }
420
421 #[test]
422 fn goto_definition_works_for_record_fields() {
423 covers!(goto_definition_works_for_record_fields);
424 check_goto(
425 "
426 //- /lib.rs
427 struct Foo {
428 spam: u32,
429 }
430
431 fn bar() -> Foo {
432 Foo {
433 spam<|>: 0,
434 }
435 }
436 ",
437 "spam RECORD_FIELD_DEF FileId(1) [17; 26) [17; 21)",
438 );
439 }
440
441 #[test]
442 fn goto_definition_works_for_ufcs_inherent_methods() {
443 check_goto(
444 "
445 //- /lib.rs
446 struct Foo;
447 impl Foo {
448 fn frobnicate() { }
449 }
450
451 fn bar(foo: &Foo) {
452 Foo::frobnicate<|>();
453 }
454 ",
455 "frobnicate FN_DEF FileId(1) [27; 47) [30; 40)",
456 );
457 }
458
459 #[test]
460 fn goto_definition_works_for_ufcs_trait_methods_through_traits() {
461 check_goto(
462 "
463 //- /lib.rs
464 trait Foo {
465 fn frobnicate();
466 }
467
468 fn bar() {
469 Foo::frobnicate<|>();
470 }
471 ",
472 "frobnicate FN_DEF FileId(1) [16; 32) [19; 29)",
473 );
474 }
475
476 #[test]
477 fn goto_definition_works_for_ufcs_trait_methods_through_self() {
478 check_goto(
479 "
480 //- /lib.rs
481 struct Foo;
482 trait Trait {
483 fn frobnicate();
484 }
485 impl Trait for Foo {}
486
487 fn bar() {
488 Foo::frobnicate<|>();
489 }
490 ",
491 "frobnicate FN_DEF FileId(1) [30; 46) [33; 43)",
492 );
493 }
494
495 #[test]
496 fn goto_definition_on_self() {
497 check_goto(
498 "
499 //- /lib.rs
500 struct Foo;
501 impl Foo {
502 pub fn new() -> Self {
503 Self<|> {}
504 }
505 }
506 ",
507 "impl IMPL_BLOCK FileId(1) [12; 73)",
508 );
509
510 check_goto(
511 "
512 //- /lib.rs
513 struct Foo;
514 impl Foo {
515 pub fn new() -> Self<|> {
516 Self {}
517 }
518 }
519 ",
520 "impl IMPL_BLOCK FileId(1) [12; 73)",
521 );
522
523 check_goto(
524 "
525 //- /lib.rs
526 enum Foo { A }
527 impl Foo {
528 pub fn new() -> Self<|> {
529 Foo::A
530 }
531 }
532 ",
533 "impl IMPL_BLOCK FileId(1) [15; 75)",
534 );
535
536 check_goto(
537 "
538 //- /lib.rs
539 enum Foo { A }
540 impl Foo {
541 pub fn thing(a: &Self<|>) {
542 }
543 }
544 ",
545 "impl IMPL_BLOCK FileId(1) [15; 62)",
546 );
547 }
548
549 #[test]
550 fn goto_definition_on_self_in_trait_impl() {
551 check_goto(
552 "
553 //- /lib.rs
554 struct Foo;
555 trait Make {
556 fn new() -> Self;
557 }
558 impl Make for Foo {
559 fn new() -> Self {
560 Self<|> {}
561 }
562 }
563 ",
564 "impl IMPL_BLOCK FileId(1) [49; 115)",
565 );
566
567 check_goto(
568 "
569 //- /lib.rs
570 struct Foo;
571 trait Make {
572 fn new() -> Self;
573 }
574 impl Make for Foo {
575 fn new() -> Self<|> {
576 Self {}
577 }
578 }
579 ",
580 "impl IMPL_BLOCK FileId(1) [49; 115)",
581 );
582 }
583
584 #[test]
585 fn goto_definition_works_when_used_on_definition_name_itself() {
586 check_goto(
587 "
588 //- /lib.rs
589 struct Foo<|> { value: u32 }
590 ",
591 "Foo STRUCT_DEF FileId(1) [0; 25) [7; 10)",
592 );
593
594 check_goto(
595 r#"
596 //- /lib.rs
597 struct Foo {
598 field<|>: string,
599 }
600 "#,
601 "field RECORD_FIELD_DEF FileId(1) [17; 30) [17; 22)",
602 );
603
604 check_goto(
605 "
606 //- /lib.rs
607 fn foo_test<|>() {
608 }
609 ",
610 "foo_test FN_DEF FileId(1) [0; 17) [3; 11)",
611 );
612
613 check_goto(
614 "
615 //- /lib.rs
616 enum Foo<|> {
617 Variant,
618 }
619 ",
620 "Foo ENUM_DEF FileId(1) [0; 25) [5; 8)",
621 );
622
623 check_goto(
624 "
625 //- /lib.rs
626 enum Foo {
627 Variant1,
628 Variant2<|>,
629 Variant3,
630 }
631 ",
632 "Variant2 ENUM_VARIANT FileId(1) [29; 37) [29; 37)",
633 );
634
635 check_goto(
636 r#"
637 //- /lib.rs
638 static inner<|>: &str = "";
639 "#,
640 "inner STATIC_DEF FileId(1) [0; 24) [7; 12)",
641 );
642
643 check_goto(
644 r#"
645 //- /lib.rs
646 const inner<|>: &str = "";
647 "#,
648 "inner CONST_DEF FileId(1) [0; 23) [6; 11)",
649 );
650
651 check_goto(
652 r#"
653 //- /lib.rs
654 type Thing<|> = Option<()>;
655 "#,
656 "Thing TYPE_ALIAS_DEF FileId(1) [0; 24) [5; 10)",
657 );
658
659 check_goto(
660 r#"
661 //- /lib.rs
662 trait Foo<|> {
663 }
664 "#,
665 "Foo TRAIT_DEF FileId(1) [0; 13) [6; 9)",
666 );
667
668 check_goto(
669 r#"
670 //- /lib.rs
671 mod bar<|> {
672 }
673 "#,
674 "bar MODULE FileId(1) [0; 11) [4; 7)",
675 );
676 }
677
678 #[test]
679 fn goto_from_macro() {
680 check_goto(
681 "
682 //- /lib.rs
683 macro_rules! id {
684 ($($tt:tt)*) => { $($tt)* }
685 }
686 fn foo() {}
687 id! {
688 fn bar() {
689 fo<|>o();
690 }
691 }
692 ",
693 "foo FN_DEF FileId(1) [52; 63) [55; 58)",
694 );
695 }
696}
diff --git a/crates/ra_ide/src/goto_type_definition.rs b/crates/ra_ide/src/goto_type_definition.rs
new file mode 100644
index 000000000..992a08809
--- /dev/null
+++ b/crates/ra_ide/src/goto_type_definition.rs
@@ -0,0 +1,105 @@
1//! FIXME: write short doc here
2
3use hir::db::AstDatabase;
4use ra_syntax::{ast, AstNode};
5
6use crate::{
7 db::RootDatabase, display::ToNav, expand::descend_into_macros, FilePosition, NavigationTarget,
8 RangeInfo,
9};
10
11pub(crate) fn goto_type_definition(
12 db: &RootDatabase,
13 position: FilePosition,
14) -> Option<RangeInfo<Vec<NavigationTarget>>> {
15 let file = db.parse_or_expand(position.file_id.into())?;
16 let token = file.token_at_offset(position.offset).filter(|it| !it.kind().is_trivia()).next()?;
17 let token = descend_into_macros(db, position.file_id, token);
18
19 let node = token.value.ancestors().find_map(|token| {
20 token
21 .ancestors()
22 .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())
23 })?;
24
25 let analyzer = hir::SourceAnalyzer::new(db, token.with_value(&node), None);
26
27 let ty: hir::Type = if let Some(ty) =
28 ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e))
29 {
30 ty
31 } else if let Some(ty) = ast::Pat::cast(node.clone()).and_then(|p| analyzer.type_of_pat(db, &p))
32 {
33 ty
34 } else {
35 return None;
36 };
37
38 let adt_def = ty.autoderef(db).find_map(|ty| ty.as_adt())?;
39
40 let nav = adt_def.to_nav(db);
41 Some(RangeInfo::new(node.text_range(), vec![nav]))
42}
43
44#[cfg(test)]
45mod tests {
46 use crate::mock_analysis::analysis_and_position;
47
48 fn check_goto(fixture: &str, expected: &str) {
49 let (analysis, pos) = analysis_and_position(fixture);
50
51 let mut navs = analysis.goto_type_definition(pos).unwrap().unwrap().info;
52 assert_eq!(navs.len(), 1);
53 let nav = navs.pop().unwrap();
54 nav.assert_match(expected);
55 }
56
57 #[test]
58 fn goto_type_definition_works_simple() {
59 check_goto(
60 "
61 //- /lib.rs
62 struct Foo;
63 fn foo() {
64 let f: Foo;
65 f<|>
66 }
67 ",
68 "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)",
69 );
70 }
71
72 #[test]
73 fn goto_type_definition_works_simple_ref() {
74 check_goto(
75 "
76 //- /lib.rs
77 struct Foo;
78 fn foo() {
79 let f: &Foo;
80 f<|>
81 }
82 ",
83 "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)",
84 );
85 }
86
87 #[test]
88 fn goto_type_definition_works_through_macro() {
89 check_goto(
90 "
91 //- /lib.rs
92 macro_rules! id {
93 ($($tt:tt)*) => { $($tt)* }
94 }
95 struct Foo {}
96 id! {
97 fn bar() {
98 let f<|> = Foo {};
99 }
100 }
101 ",
102 "Foo STRUCT_DEF FileId(1) [52; 65) [59; 62)",
103 );
104 }
105}
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
new file mode 100644
index 000000000..260a7b869
--- /dev/null
+++ b/crates/ra_ide/src/hover.rs
@@ -0,0 +1,730 @@
1//! FIXME: write short doc here
2
3use hir::{db::AstDatabase, Adt, HasSource, HirDisplay};
4use ra_db::SourceDatabase;
5use ra_syntax::{
6 algo::find_covering_element,
7 ast::{self, DocCommentsOwner},
8 match_ast, AstNode,
9};
10
11use crate::{
12 db::RootDatabase,
13 display::{
14 description_from_symbol, docs_from_symbol, macro_label, rust_code_markup,
15 rust_code_markup_with_doc, ShortLabel,
16 },
17 expand::descend_into_macros,
18 references::{classify_name, classify_name_ref, NameKind, NameKind::*},
19 FilePosition, FileRange, RangeInfo,
20};
21
22/// Contains the results when hovering over an item
23#[derive(Debug, Clone)]
24pub struct HoverResult {
25 results: Vec<String>,
26 exact: bool,
27}
28
29impl Default for HoverResult {
30 fn default() -> Self {
31 HoverResult::new()
32 }
33}
34
35impl HoverResult {
36 pub fn new() -> HoverResult {
37 HoverResult {
38 results: Vec::new(),
39 // We assume exact by default
40 exact: true,
41 }
42 }
43
44 pub fn extend(&mut self, item: Option<String>) {
45 self.results.extend(item);
46 }
47
48 pub fn is_exact(&self) -> bool {
49 self.exact
50 }
51
52 pub fn is_empty(&self) -> bool {
53 self.results.is_empty()
54 }
55
56 pub fn len(&self) -> usize {
57 self.results.len()
58 }
59
60 pub fn first(&self) -> Option<&str> {
61 self.results.first().map(String::as_str)
62 }
63
64 pub fn results(&self) -> &[String] {
65 &self.results
66 }
67
68 /// Returns the results converted into markup
69 /// for displaying in a UI
70 pub fn to_markup(&self) -> String {
71 let mut markup = if !self.exact {
72 let mut msg = String::from("Failed to exactly resolve the symbol. This is probably because rust_analyzer does not yet support traits.");
73 if !self.results.is_empty() {
74 msg.push_str(" \nThese items were found instead:");
75 }
76 msg.push_str("\n\n---\n");
77 msg
78 } else {
79 String::new()
80 };
81
82 markup.push_str(&self.results.join("\n\n---\n"));
83
84 markup
85 }
86}
87
88fn hover_text(docs: Option<String>, desc: Option<String>) -> Option<String> {
89 match (desc, docs) {
90 (Some(desc), docs) => Some(rust_code_markup_with_doc(desc, docs)),
91 (None, Some(docs)) => Some(docs),
92 _ => None,
93 }
94}
95
96fn hover_text_from_name_kind(
97 db: &RootDatabase,
98 name_kind: NameKind,
99 no_fallback: &mut bool,
100) -> Option<String> {
101 return match name_kind {
102 Macro(it) => {
103 let src = it.source(db);
104 hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value)))
105 }
106 Field(it) => {
107 let src = it.source(db);
108 match src.value {
109 hir::FieldSource::Named(it) => hover_text(it.doc_comment_text(), it.short_label()),
110 _ => None,
111 }
112 }
113 AssocItem(it) => match it {
114 hir::AssocItem::Function(it) => from_def_source(db, it),
115 hir::AssocItem::Const(it) => from_def_source(db, it),
116 hir::AssocItem::TypeAlias(it) => from_def_source(db, it),
117 },
118 Def(it) => match it {
119 hir::ModuleDef::Module(it) => match it.definition_source(db).value {
120 hir::ModuleSource::Module(it) => {
121 hover_text(it.doc_comment_text(), it.short_label())
122 }
123 _ => None,
124 },
125 hir::ModuleDef::Function(it) => from_def_source(db, it),
126 hir::ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it),
127 hir::ModuleDef::Adt(Adt::Union(it)) => from_def_source(db, it),
128 hir::ModuleDef::Adt(Adt::Enum(it)) => from_def_source(db, it),
129 hir::ModuleDef::EnumVariant(it) => from_def_source(db, it),
130 hir::ModuleDef::Const(it) => from_def_source(db, it),
131 hir::ModuleDef::Static(it) => from_def_source(db, it),
132 hir::ModuleDef::Trait(it) => from_def_source(db, it),
133 hir::ModuleDef::TypeAlias(it) => from_def_source(db, it),
134 hir::ModuleDef::BuiltinType(it) => Some(it.to_string()),
135 },
136 Local(_) => {
137 // Hover for these shows type names
138 *no_fallback = true;
139 None
140 }
141 GenericParam(_) | SelfType(_) => {
142 // FIXME: Hover for generic param
143 None
144 }
145 };
146
147 fn from_def_source<A, D>(db: &RootDatabase, def: D) -> Option<String>
148 where
149 D: HasSource<Ast = A>,
150 A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel,
151 {
152 let src = def.source(db);
153 hover_text(src.value.doc_comment_text(), src.value.short_label())
154 }
155}
156
157pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> {
158 let file = db.parse_or_expand(position.file_id.into())?;
159 let token = file.token_at_offset(position.offset).filter(|it| !it.kind().is_trivia()).next()?;
160 let token = descend_into_macros(db, position.file_id, token);
161
162 let mut res = HoverResult::new();
163
164 let mut range = match_ast! {
165 match (token.value.parent()) {
166 ast::NameRef(name_ref) => {
167 let mut no_fallback = false;
168 if let Some(name_kind) =
169 classify_name_ref(db, token.with_value(&name_ref)).map(|d| d.kind)
170 {
171 res.extend(hover_text_from_name_kind(db, name_kind, &mut no_fallback))
172 }
173
174 if res.is_empty() && !no_fallback {
175 // Fallback index based approach:
176 let symbols = crate::symbol_index::index_resolve(db, &name_ref);
177 for sym in symbols {
178 let docs = docs_from_symbol(db, &sym);
179 let desc = description_from_symbol(db, &sym);
180 res.extend(hover_text(docs, desc));
181 }
182 }
183
184 if !res.is_empty() {
185 Some(name_ref.syntax().text_range())
186 } else {
187 None
188 }
189 },
190 ast::Name(name) => {
191 if let Some(name_kind) = classify_name(db, token.with_value(&name)).map(|d| d.kind) {
192 res.extend(hover_text_from_name_kind(db, name_kind, &mut true));
193 }
194
195 if !res.is_empty() {
196 Some(name.syntax().text_range())
197 } else {
198 None
199 }
200 },
201 _ => None,
202 }
203 };
204
205 if range.is_none() {
206 let node = token.value.ancestors().find(|n| {
207 ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some()
208 })?;
209 let frange = FileRange { file_id: position.file_id, range: node.text_range() };
210 res.extend(type_of(db, frange).map(rust_code_markup));
211 range = Some(node.text_range());
212 };
213
214 let range = range?;
215 if res.is_empty() {
216 return None;
217 }
218 Some(RangeInfo::new(range, res))
219}
220
221pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> {
222 let parse = db.parse(frange.file_id);
223 let leaf_node = find_covering_element(parse.tree().syntax(), frange.range);
224 // if we picked identifier, expand to pattern/expression
225 let node = leaf_node
226 .ancestors()
227 .take_while(|it| it.text_range() == leaf_node.text_range())
228 .find(|it| ast::Expr::cast(it.clone()).is_some() || ast::Pat::cast(it.clone()).is_some())?;
229 let analyzer =
230 hir::SourceAnalyzer::new(db, hir::Source::new(frange.file_id.into(), &node), None);
231 let ty = if let Some(ty) = ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e))
232 {
233 ty
234 } else if let Some(ty) = ast::Pat::cast(node).and_then(|p| analyzer.type_of_pat(db, &p)) {
235 ty
236 } else {
237 return None;
238 };
239 Some(ty.display(db).to_string())
240}
241
242#[cfg(test)]
243mod tests {
244 use crate::mock_analysis::{
245 analysis_and_position, single_file_with_position, single_file_with_range,
246 };
247 use ra_syntax::TextRange;
248
249 fn trim_markup(s: &str) -> &str {
250 s.trim_start_matches("```rust\n").trim_end_matches("\n```")
251 }
252
253 fn trim_markup_opt(s: Option<&str>) -> Option<&str> {
254 s.map(trim_markup)
255 }
256
257 fn check_hover_result(fixture: &str, expected: &[&str]) {
258 let (analysis, position) = analysis_and_position(fixture);
259 let hover = analysis.hover(position).unwrap().unwrap();
260 let mut results = Vec::from(hover.info.results());
261 results.sort();
262
263 for (markup, expected) in
264 results.iter().zip(expected.iter().chain(std::iter::repeat(&"<missing>")))
265 {
266 assert_eq!(trim_markup(&markup), *expected);
267 }
268
269 assert_eq!(hover.info.len(), expected.len());
270 }
271
272 #[test]
273 fn hover_shows_type_of_an_expression() {
274 let (analysis, position) = single_file_with_position(
275 "
276 pub fn foo() -> u32 { 1 }
277
278 fn main() {
279 let foo_test = foo()<|>;
280 }
281 ",
282 );
283 let hover = analysis.hover(position).unwrap().unwrap();
284 assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into()));
285 assert_eq!(trim_markup_opt(hover.info.first()), Some("u32"));
286 }
287
288 #[test]
289 fn hover_shows_fn_signature() {
290 // Single file with result
291 check_hover_result(
292 r#"
293 //- /main.rs
294 pub fn foo() -> u32 { 1 }
295
296 fn main() {
297 let foo_test = fo<|>o();
298 }
299 "#,
300 &["pub fn foo() -> u32"],
301 );
302
303 // Multiple results
304 check_hover_result(
305 r#"
306 //- /a.rs
307 pub fn foo() -> u32 { 1 }
308
309 //- /b.rs
310 pub fn foo() -> &str { "" }
311
312 //- /c.rs
313 pub fn foo(a: u32, b: u32) {}
314
315 //- /main.rs
316 mod a;
317 mod b;
318 mod c;
319
320 fn main() {
321 let foo_test = fo<|>o();
322 }
323 "#,
324 &["pub fn foo() -> &str", "pub fn foo() -> u32", "pub fn foo(a: u32, b: u32)"],
325 );
326 }
327
328 #[test]
329 fn hover_shows_fn_signature_with_type_params() {
330 check_hover_result(
331 r#"
332 //- /main.rs
333 pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str { }
334
335 fn main() {
336 let foo_test = fo<|>o();
337 }
338 "#,
339 &["pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str"],
340 );
341 }
342
343 #[test]
344 fn hover_shows_fn_signature_on_fn_name() {
345 check_hover_result(
346 r#"
347 //- /main.rs
348 pub fn foo<|>(a: u32, b: u32) -> u32 {}
349
350 fn main() {
351 }
352 "#,
353 &["pub fn foo(a: u32, b: u32) -> u32"],
354 );
355 }
356
357 #[test]
358 fn hover_shows_struct_field_info() {
359 // Hovering over the field when instantiating
360 check_hover_result(
361 r#"
362 //- /main.rs
363 struct Foo {
364 field_a: u32,
365 }
366
367 fn main() {
368 let foo = Foo {
369 field_a<|>: 0,
370 };
371 }
372 "#,
373 &["field_a: u32"],
374 );
375
376 // Hovering over the field in the definition
377 check_hover_result(
378 r#"
379 //- /main.rs
380 struct Foo {
381 field_a<|>: u32,
382 }
383
384 fn main() {
385 let foo = Foo {
386 field_a: 0,
387 };
388 }
389 "#,
390 &["field_a: u32"],
391 );
392 }
393
394 #[test]
395 fn hover_const_static() {
396 check_hover_result(
397 r#"
398 //- /main.rs
399 const foo<|>: u32 = 0;
400 "#,
401 &["const foo: u32"],
402 );
403
404 check_hover_result(
405 r#"
406 //- /main.rs
407 static foo<|>: u32 = 0;
408 "#,
409 &["static foo: u32"],
410 );
411 }
412
413 #[test]
414 fn hover_some() {
415 let (analysis, position) = single_file_with_position(
416 "
417 enum Option<T> { Some(T) }
418 use Option::Some;
419
420 fn main() {
421 So<|>me(12);
422 }
423 ",
424 );
425 let hover = analysis.hover(position).unwrap().unwrap();
426 assert_eq!(trim_markup_opt(hover.info.first()), Some("Some"));
427
428 let (analysis, position) = single_file_with_position(
429 "
430 enum Option<T> { Some(T) }
431 use Option::Some;
432
433 fn main() {
434 let b<|>ar = Some(12);
435 }
436 ",
437 );
438 let hover = analysis.hover(position).unwrap().unwrap();
439 assert_eq!(trim_markup_opt(hover.info.first()), Some("Option<i32>"));
440 }
441
442 #[test]
443 fn hover_enum_variant() {
444 check_hover_result(
445 r#"
446 //- /main.rs
447 enum Option<T> {
448 /// The None variant
449 Non<|>e
450 }
451 "#,
452 &["
453None
454```
455
456The None variant
457 "
458 .trim()],
459 );
460
461 check_hover_result(
462 r#"
463 //- /main.rs
464 enum Option<T> {
465 /// The Some variant
466 Some(T)
467 }
468 fn main() {
469 let s = Option::Som<|>e(12);
470 }
471 "#,
472 &["
473Some
474```
475
476The Some variant
477 "
478 .trim()],
479 );
480 }
481
482 #[test]
483 fn hover_for_local_variable() {
484 let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }");
485 let hover = analysis.hover(position).unwrap().unwrap();
486 assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
487 }
488
489 #[test]
490 fn hover_for_local_variable_pat() {
491 let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}");
492 let hover = analysis.hover(position).unwrap().unwrap();
493 assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
494 }
495
496 #[test]
497 fn hover_local_var_edge() {
498 let (analysis, position) = single_file_with_position(
499 "
500fn func(foo: i32) { if true { <|>foo; }; }
501",
502 );
503 let hover = analysis.hover(position).unwrap().unwrap();
504 assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
505 }
506
507 #[test]
508 fn test_type_of_for_function() {
509 let (analysis, range) = single_file_with_range(
510 "
511 pub fn foo() -> u32 { 1 };
512
513 fn main() {
514 let foo_test = <|>foo()<|>;
515 }
516 ",
517 );
518
519 let type_name = analysis.type_of(range).unwrap().unwrap();
520 assert_eq!("u32", &type_name);
521 }
522
523 #[test]
524 fn test_type_of_for_expr() {
525 let (analysis, range) = single_file_with_range(
526 "
527 fn main() {
528 let foo: usize = 1;
529 let bar = <|>1 + foo<|>;
530 }
531 ",
532 );
533
534 let type_name = analysis.type_of(range).unwrap().unwrap();
535 assert_eq!("usize", &type_name);
536 }
537
538 #[test]
539 fn test_hover_infer_associated_method_result() {
540 let (analysis, position) = single_file_with_position(
541 "
542 struct Thing { x: u32 }
543
544 impl Thing {
545 fn new() -> Thing {
546 Thing { x: 0 }
547 }
548 }
549
550 fn main() {
551 let foo_<|>test = Thing::new();
552 }
553 ",
554 );
555 let hover = analysis.hover(position).unwrap().unwrap();
556 assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing"));
557 }
558
559 #[test]
560 fn test_hover_infer_associated_method_exact() {
561 let (analysis, position) = single_file_with_position(
562 "
563 struct Thing { x: u32 }
564
565 impl Thing {
566 fn new() -> Thing {
567 Thing { x: 0 }
568 }
569 }
570
571 fn main() {
572 let foo_test = Thing::new<|>();
573 }
574 ",
575 );
576 let hover = analysis.hover(position).unwrap().unwrap();
577 assert_eq!(trim_markup_opt(hover.info.first()), Some("fn new() -> Thing"));
578 assert_eq!(hover.info.is_exact(), true);
579 }
580
581 #[test]
582 fn test_hover_infer_associated_const_in_pattern() {
583 let (analysis, position) = single_file_with_position(
584 "
585 struct X;
586 impl X {
587 const C: u32 = 1;
588 }
589
590 fn main() {
591 match 1 {
592 X::C<|> => {},
593 2 => {},
594 _ => {}
595 };
596 }
597 ",
598 );
599 let hover = analysis.hover(position).unwrap().unwrap();
600 assert_eq!(trim_markup_opt(hover.info.first()), Some("const C: u32"));
601 assert_eq!(hover.info.is_exact(), true);
602 }
603
604 #[test]
605 fn test_hover_self() {
606 let (analysis, position) = single_file_with_position(
607 "
608 struct Thing { x: u32 }
609 impl Thing {
610 fn new() -> Self {
611 Self<|> { x: 0 }
612 }
613 }
614 ",
615 );
616 let hover = analysis.hover(position).unwrap().unwrap();
617 assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing"));
618 assert_eq!(hover.info.is_exact(), true);
619
620 /* FIXME: revive these tests
621 let (analysis, position) = single_file_with_position(
622 "
623 struct Thing { x: u32 }
624 impl Thing {
625 fn new() -> Self<|> {
626 Self { x: 0 }
627 }
628 }
629 ",
630 );
631
632 let hover = analysis.hover(position).unwrap().unwrap();
633 assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing"));
634 assert_eq!(hover.info.is_exact(), true);
635
636 let (analysis, position) = single_file_with_position(
637 "
638 enum Thing { A }
639 impl Thing {
640 pub fn new() -> Self<|> {
641 Thing::A
642 }
643 }
644 ",
645 );
646 let hover = analysis.hover(position).unwrap().unwrap();
647 assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing"));
648 assert_eq!(hover.info.is_exact(), true);
649
650 let (analysis, position) = single_file_with_position(
651 "
652 enum Thing { A }
653 impl Thing {
654 pub fn thing(a: Self<|>) {
655 }
656 }
657 ",
658 );
659 let hover = analysis.hover(position).unwrap().unwrap();
660 assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing"));
661 assert_eq!(hover.info.is_exact(), true);
662 */
663 }
664
665 #[test]
666 fn test_hover_shadowing_pat() {
667 let (analysis, position) = single_file_with_position(
668 "
669 fn x() {}
670
671 fn y() {
672 let x = 0i32;
673 x<|>;
674 }
675 ",
676 );
677 let hover = analysis.hover(position).unwrap().unwrap();
678 assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
679 assert_eq!(hover.info.is_exact(), true);
680 }
681
682 #[test]
683 fn test_hover_macro_invocation() {
684 let (analysis, position) = single_file_with_position(
685 "
686 macro_rules! foo {
687 () => {}
688 }
689
690 fn f() {
691 fo<|>o!();
692 }
693 ",
694 );
695 let hover = analysis.hover(position).unwrap().unwrap();
696 assert_eq!(trim_markup_opt(hover.info.first()), Some("macro_rules! foo"));
697 assert_eq!(hover.info.is_exact(), true);
698 }
699
700 #[test]
701 fn test_hover_tuple_field() {
702 let (analysis, position) = single_file_with_position(
703 "
704 struct TS(String, i32<|>);
705 ",
706 );
707 let hover = analysis.hover(position).unwrap().unwrap();
708 assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
709 assert_eq!(hover.info.is_exact(), true);
710 }
711
712 #[test]
713 fn test_hover_through_macro() {
714 check_hover_result(
715 "
716 //- /lib.rs
717 macro_rules! id {
718 ($($tt:tt)*) => { $($tt)* }
719 }
720 fn foo() {}
721 id! {
722 fn bar() {
723 fo<|>o();
724 }
725 }
726 ",
727 &["fn foo()"],
728 );
729 }
730}
diff --git a/crates/ra_ide/src/impls.rs b/crates/ra_ide/src/impls.rs
new file mode 100644
index 000000000..aa480e399
--- /dev/null
+++ b/crates/ra_ide/src/impls.rs
@@ -0,0 +1,206 @@
1//! FIXME: write short doc here
2
3use hir::{FromSource, ImplBlock};
4use ra_db::SourceDatabase;
5use ra_syntax::{algo::find_node_at_offset, ast, AstNode};
6
7use crate::{db::RootDatabase, display::ToNav, FilePosition, NavigationTarget, RangeInfo};
8
9pub(crate) fn goto_implementation(
10 db: &RootDatabase,
11 position: FilePosition,
12) -> Option<RangeInfo<Vec<NavigationTarget>>> {
13 let parse = db.parse(position.file_id);
14 let syntax = parse.tree().syntax().clone();
15
16 let src = hir::ModuleSource::from_position(db, position);
17 let module = hir::Module::from_definition(
18 db,
19 hir::Source { file_id: position.file_id.into(), value: src },
20 )?;
21
22 if let Some(nominal_def) = find_node_at_offset::<ast::NominalDef>(&syntax, position.offset) {
23 return Some(RangeInfo::new(
24 nominal_def.syntax().text_range(),
25 impls_for_def(db, position, &nominal_def, module)?,
26 ));
27 } else if let Some(trait_def) = find_node_at_offset::<ast::TraitDef>(&syntax, position.offset) {
28 return Some(RangeInfo::new(
29 trait_def.syntax().text_range(),
30 impls_for_trait(db, position, &trait_def, module)?,
31 ));
32 }
33
34 None
35}
36
37fn impls_for_def(
38 db: &RootDatabase,
39 position: FilePosition,
40 node: &ast::NominalDef,
41 module: hir::Module,
42) -> Option<Vec<NavigationTarget>> {
43 let ty = match node {
44 ast::NominalDef::StructDef(def) => {
45 let src = hir::Source { file_id: position.file_id.into(), value: def.clone() };
46 hir::Struct::from_source(db, src)?.ty(db)
47 }
48 ast::NominalDef::EnumDef(def) => {
49 let src = hir::Source { file_id: position.file_id.into(), value: def.clone() };
50 hir::Enum::from_source(db, src)?.ty(db)
51 }
52 ast::NominalDef::UnionDef(def) => {
53 let src = hir::Source { file_id: position.file_id.into(), value: def.clone() };
54 hir::Union::from_source(db, src)?.ty(db)
55 }
56 };
57
58 let krate = module.krate();
59 let impls = ImplBlock::all_in_crate(db, krate);
60
61 Some(
62 impls
63 .into_iter()
64 .filter(|impl_block| ty.is_equal_for_find_impls(&impl_block.target_ty(db)))
65 .map(|imp| imp.to_nav(db))
66 .collect(),
67 )
68}
69
70fn impls_for_trait(
71 db: &RootDatabase,
72 position: FilePosition,
73 node: &ast::TraitDef,
74 module: hir::Module,
75) -> Option<Vec<NavigationTarget>> {
76 let src = hir::Source { file_id: position.file_id.into(), value: node.clone() };
77 let tr = hir::Trait::from_source(db, src)?;
78
79 let krate = module.krate();
80 let impls = ImplBlock::for_trait(db, krate, tr);
81
82 Some(impls.into_iter().map(|imp| imp.to_nav(db)).collect())
83}
84
85#[cfg(test)]
86mod tests {
87 use crate::mock_analysis::analysis_and_position;
88
89 fn check_goto(fixture: &str, expected: &[&str]) {
90 let (analysis, pos) = analysis_and_position(fixture);
91
92 let mut navs = analysis.goto_implementation(pos).unwrap().unwrap().info;
93 assert_eq!(navs.len(), expected.len());
94 navs.sort_by_key(|nav| (nav.file_id(), nav.full_range().start()));
95 navs.into_iter().enumerate().for_each(|(i, nav)| nav.assert_match(expected[i]));
96 }
97
98 #[test]
99 fn goto_implementation_works() {
100 check_goto(
101 "
102 //- /lib.rs
103 struct Foo<|>;
104 impl Foo {}
105 ",
106 &["impl IMPL_BLOCK FileId(1) [12; 23)"],
107 );
108 }
109
110 #[test]
111 fn goto_implementation_works_multiple_blocks() {
112 check_goto(
113 "
114 //- /lib.rs
115 struct Foo<|>;
116 impl Foo {}
117 impl Foo {}
118 ",
119 &["impl IMPL_BLOCK FileId(1) [12; 23)", "impl IMPL_BLOCK FileId(1) [24; 35)"],
120 );
121 }
122
123 #[test]
124 fn goto_implementation_works_multiple_mods() {
125 check_goto(
126 "
127 //- /lib.rs
128 struct Foo<|>;
129 mod a {
130 impl super::Foo {}
131 }
132 mod b {
133 impl super::Foo {}
134 }
135 ",
136 &["impl IMPL_BLOCK FileId(1) [24; 42)", "impl IMPL_BLOCK FileId(1) [57; 75)"],
137 );
138 }
139
140 #[test]
141 fn goto_implementation_works_multiple_files() {
142 check_goto(
143 "
144 //- /lib.rs
145 struct Foo<|>;
146 mod a;
147 mod b;
148 //- /a.rs
149 impl crate::Foo {}
150 //- /b.rs
151 impl crate::Foo {}
152 ",
153 &["impl IMPL_BLOCK FileId(2) [0; 18)", "impl IMPL_BLOCK FileId(3) [0; 18)"],
154 );
155 }
156
157 #[test]
158 fn goto_implementation_for_trait() {
159 check_goto(
160 "
161 //- /lib.rs
162 trait T<|> {}
163 struct Foo;
164 impl T for Foo {}
165 ",
166 &["impl IMPL_BLOCK FileId(1) [23; 40)"],
167 );
168 }
169
170 #[test]
171 fn goto_implementation_for_trait_multiple_files() {
172 check_goto(
173 "
174 //- /lib.rs
175 trait T<|> {};
176 struct Foo;
177 mod a;
178 mod b;
179 //- /a.rs
180 impl crate::T for crate::Foo {}
181 //- /b.rs
182 impl crate::T for crate::Foo {}
183 ",
184 &["impl IMPL_BLOCK FileId(2) [0; 31)", "impl IMPL_BLOCK FileId(3) [0; 31)"],
185 );
186 }
187
188 #[test]
189 fn goto_implementation_all_impls() {
190 check_goto(
191 "
192 //- /lib.rs
193 trait T {}
194 struct Foo<|>;
195 impl Foo {}
196 impl T for Foo {}
197 impl T for &Foo {}
198 ",
199 &[
200 "impl IMPL_BLOCK FileId(1) [23; 34)",
201 "impl IMPL_BLOCK FileId(1) [35; 52)",
202 "impl IMPL_BLOCK FileId(1) [53; 71)",
203 ],
204 );
205 }
206}
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs
new file mode 100644
index 000000000..45149bf0c
--- /dev/null
+++ b/crates/ra_ide/src/inlay_hints.rs
@@ -0,0 +1,543 @@
1//! FIXME: write short doc here
2
3use crate::{db::RootDatabase, FileId};
4use hir::{HirDisplay, SourceAnalyzer};
5use ra_syntax::{
6 ast::{self, AstNode, TypeAscriptionOwner},
7 match_ast, SmolStr, SourceFile, SyntaxKind, SyntaxNode, TextRange,
8};
9
10#[derive(Debug, PartialEq, Eq)]
11pub enum InlayKind {
12 TypeHint,
13}
14
15#[derive(Debug)]
16pub struct InlayHint {
17 pub range: TextRange,
18 pub kind: InlayKind,
19 pub label: SmolStr,
20}
21
22pub(crate) fn inlay_hints(
23 db: &RootDatabase,
24 file_id: FileId,
25 file: &SourceFile,
26 max_inlay_hint_length: Option<usize>,
27) -> Vec<InlayHint> {
28 file.syntax()
29 .descendants()
30 .map(|node| get_inlay_hints(db, file_id, &node, max_inlay_hint_length).unwrap_or_default())
31 .flatten()
32 .collect()
33}
34
35fn get_inlay_hints(
36 db: &RootDatabase,
37 file_id: FileId,
38 node: &SyntaxNode,
39 max_inlay_hint_length: Option<usize>,
40) -> Option<Vec<InlayHint>> {
41 let analyzer = SourceAnalyzer::new(db, hir::Source::new(file_id.into(), node), None);
42 match_ast! {
43 match node {
44 ast::LetStmt(it) => {
45 if it.ascribed_type().is_some() {
46 return None;
47 }
48 let pat = it.pat()?;
49 Some(get_pat_type_hints(db, &analyzer, pat, false, max_inlay_hint_length))
50 },
51 ast::LambdaExpr(it) => {
52 it.param_list().map(|param_list| {
53 param_list
54 .params()
55 .filter(|closure_param| closure_param.ascribed_type().is_none())
56 .filter_map(|closure_param| closure_param.pat())
57 .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, false, max_inlay_hint_length))
58 .flatten()
59 .collect()
60 })
61 },
62 ast::ForExpr(it) => {
63 let pat = it.pat()?;
64 Some(get_pat_type_hints(db, &analyzer, pat, false, max_inlay_hint_length))
65 },
66 ast::IfExpr(it) => {
67 let pat = it.condition()?.pat()?;
68 Some(get_pat_type_hints(db, &analyzer, pat, true, max_inlay_hint_length))
69 },
70 ast::WhileExpr(it) => {
71 let pat = it.condition()?.pat()?;
72 Some(get_pat_type_hints(db, &analyzer, pat, true, max_inlay_hint_length))
73 },
74 ast::MatchArmList(it) => {
75 Some(
76 it
77 .arms()
78 .map(|match_arm| match_arm.pats())
79 .flatten()
80 .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, true, max_inlay_hint_length))
81 .flatten()
82 .collect(),
83 )
84 },
85 _ => None,
86 }
87 }
88}
89
90fn get_pat_type_hints(
91 db: &RootDatabase,
92 analyzer: &SourceAnalyzer,
93 root_pat: ast::Pat,
94 skip_root_pat_hint: bool,
95 max_inlay_hint_length: Option<usize>,
96) -> Vec<InlayHint> {
97 let original_pat = &root_pat.clone();
98
99 get_leaf_pats(root_pat)
100 .into_iter()
101 .filter(|pat| !skip_root_pat_hint || pat != original_pat)
102 .filter_map(|pat| {
103 let ty = analyzer.type_of_pat(db, &pat)?;
104 if ty.is_unknown() {
105 return None;
106 }
107 Some((pat.syntax().text_range(), ty))
108 })
109 .map(|(range, pat_type)| InlayHint {
110 range,
111 kind: InlayKind::TypeHint,
112 label: pat_type.display_truncated(db, max_inlay_hint_length).to_string().into(),
113 })
114 .collect()
115}
116
117fn get_leaf_pats(root_pat: ast::Pat) -> Vec<ast::Pat> {
118 let mut pats_to_process = std::collections::VecDeque::<ast::Pat>::new();
119 pats_to_process.push_back(root_pat);
120
121 let mut leaf_pats = Vec::new();
122
123 while let Some(maybe_leaf_pat) = pats_to_process.pop_front() {
124 match &maybe_leaf_pat {
125 ast::Pat::BindPat(bind_pat) => {
126 if let Some(pat) = bind_pat.pat() {
127 pats_to_process.push_back(pat);
128 } else {
129 leaf_pats.push(maybe_leaf_pat);
130 }
131 }
132 ast::Pat::TuplePat(tuple_pat) => {
133 for arg_pat in tuple_pat.args() {
134 pats_to_process.push_back(arg_pat);
135 }
136 }
137 ast::Pat::RecordPat(record_pat) => {
138 if let Some(pat_list) = record_pat.record_field_pat_list() {
139 pats_to_process.extend(
140 pat_list
141 .record_field_pats()
142 .filter_map(|record_field_pat| {
143 record_field_pat
144 .pat()
145 .filter(|pat| pat.syntax().kind() != SyntaxKind::BIND_PAT)
146 })
147 .chain(pat_list.bind_pats().map(|bind_pat| {
148 bind_pat.pat().unwrap_or_else(|| ast::Pat::from(bind_pat))
149 })),
150 );
151 }
152 }
153 ast::Pat::TupleStructPat(tuple_struct_pat) => {
154 for arg_pat in tuple_struct_pat.args() {
155 pats_to_process.push_back(arg_pat);
156 }
157 }
158 _ => (),
159 }
160 }
161 leaf_pats
162}
163
164#[cfg(test)]
165mod tests {
166 use crate::mock_analysis::single_file;
167 use insta::assert_debug_snapshot;
168
169 #[test]
170 fn let_statement() {
171 let (analysis, file_id) = single_file(
172 r#"
173#[derive(PartialEq)]
174enum CustomOption<T> {
175 None,
176 Some(T),
177}
178
179#[derive(PartialEq)]
180struct Test {
181 a: CustomOption<u32>,
182 b: u8,
183}
184
185fn main() {
186 struct InnerStruct {}
187
188 let test = 54;
189 let test: i32 = 33;
190 let mut test = 33;
191 let _ = 22;
192 let test = "test";
193 let test = InnerStruct {};
194
195 let test = vec![222];
196 let test: Vec<_> = (0..3).collect();
197 let test = (0..3).collect::<Vec<i128>>();
198 let test = (0..3).collect::<Vec<_>>();
199
200 let mut test = Vec::new();
201 test.push(333);
202
203 let test = (42, 'a');
204 let (a, (b, c, (d, e), f)) = (2, (3, 4, (6.6, 7.7), 5));
205}"#,
206 );
207
208 assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
209 [
210 InlayHint {
211 range: [193; 197),
212 kind: TypeHint,
213 label: "i32",
214 },
215 InlayHint {
216 range: [236; 244),
217 kind: TypeHint,
218 label: "i32",
219 },
220 InlayHint {
221 range: [275; 279),
222 kind: TypeHint,
223 label: "&str",
224 },
225 InlayHint {
226 range: [539; 543),
227 kind: TypeHint,
228 label: "(i32, char)",
229 },
230 InlayHint {
231 range: [566; 567),
232 kind: TypeHint,
233 label: "i32",
234 },
235 InlayHint {
236 range: [570; 571),
237 kind: TypeHint,
238 label: "i32",
239 },
240 InlayHint {
241 range: [573; 574),
242 kind: TypeHint,
243 label: "i32",
244 },
245 InlayHint {
246 range: [584; 585),
247 kind: TypeHint,
248 label: "i32",
249 },
250 InlayHint {
251 range: [577; 578),
252 kind: TypeHint,
253 label: "f64",
254 },
255 InlayHint {
256 range: [580; 581),
257 kind: TypeHint,
258 label: "f64",
259 },
260 ]
261 "###
262 );
263 }
264
265 #[test]
266 fn closure_parameter() {
267 let (analysis, file_id) = single_file(
268 r#"
269fn main() {
270 let mut start = 0;
271 (0..2).for_each(|increment| {
272 start += increment;
273 })
274}"#,
275 );
276
277 assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
278 [
279 InlayHint {
280 range: [21; 30),
281 kind: TypeHint,
282 label: "i32",
283 },
284 InlayHint {
285 range: [57; 66),
286 kind: TypeHint,
287 label: "i32",
288 },
289 ]
290 "###
291 );
292 }
293
294 #[test]
295 fn for_expression() {
296 let (analysis, file_id) = single_file(
297 r#"
298fn main() {
299 let mut start = 0;
300 for increment in 0..2 {
301 start += increment;
302 }
303}"#,
304 );
305
306 assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
307 [
308 InlayHint {
309 range: [21; 30),
310 kind: TypeHint,
311 label: "i32",
312 },
313 InlayHint {
314 range: [44; 53),
315 kind: TypeHint,
316 label: "i32",
317 },
318 ]
319 "###
320 );
321 }
322
323 #[test]
324 fn if_expr() {
325 let (analysis, file_id) = single_file(
326 r#"
327#[derive(PartialEq)]
328enum CustomOption<T> {
329 None,
330 Some(T),
331}
332
333#[derive(PartialEq)]
334struct Test {
335 a: CustomOption<u32>,
336 b: u8,
337}
338
339fn main() {
340 let test = CustomOption::Some(Test { a: CustomOption::Some(3), b: 1 });
341 if let CustomOption::None = &test {};
342 if let test = &test {};
343 if let CustomOption::Some(test) = &test {};
344 if let CustomOption::Some(Test { a, b }) = &test {};
345 if let CustomOption::Some(Test { a: x, b: y }) = &test {};
346 if let CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) = &test {};
347 if let CustomOption::Some(Test { a: CustomOption::None, b: y }) = &test {};
348 if let CustomOption::Some(Test { b: y, .. }) = &test {};
349
350 if test == CustomOption::None {}
351}"#,
352 );
353
354 assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
355 [
356 InlayHint {
357 range: [166; 170),
358 kind: TypeHint,
359 label: "CustomOption<Test>",
360 },
361 InlayHint {
362 range: [334; 338),
363 kind: TypeHint,
364 label: "&Test",
365 },
366 InlayHint {
367 range: [389; 390),
368 kind: TypeHint,
369 label: "&CustomOption<u32>",
370 },
371 InlayHint {
372 range: [392; 393),
373 kind: TypeHint,
374 label: "&u8",
375 },
376 InlayHint {
377 range: [531; 532),
378 kind: TypeHint,
379 label: "&u32",
380 },
381 ]
382 "###
383 );
384 }
385
386 #[test]
387 fn while_expr() {
388 let (analysis, file_id) = single_file(
389 r#"
390#[derive(PartialEq)]
391enum CustomOption<T> {
392 None,
393 Some(T),
394}
395
396#[derive(PartialEq)]
397struct Test {
398 a: CustomOption<u32>,
399 b: u8,
400}
401
402fn main() {
403 let test = CustomOption::Some(Test { a: CustomOption::Some(3), b: 1 });
404 while let CustomOption::None = &test {};
405 while let test = &test {};
406 while let CustomOption::Some(test) = &test {};
407 while let CustomOption::Some(Test { a, b }) = &test {};
408 while let CustomOption::Some(Test { a: x, b: y }) = &test {};
409 while let CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) = &test {};
410 while let CustomOption::Some(Test { a: CustomOption::None, b: y }) = &test {};
411 while let CustomOption::Some(Test { b: y, .. }) = &test {};
412
413 while test == CustomOption::None {}
414}"#,
415 );
416
417 assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
418 [
419 InlayHint {
420 range: [166; 170),
421 kind: TypeHint,
422 label: "CustomOption<Test>",
423 },
424 InlayHint {
425 range: [343; 347),
426 kind: TypeHint,
427 label: "&Test",
428 },
429 InlayHint {
430 range: [401; 402),
431 kind: TypeHint,
432 label: "&CustomOption<u32>",
433 },
434 InlayHint {
435 range: [404; 405),
436 kind: TypeHint,
437 label: "&u8",
438 },
439 InlayHint {
440 range: [549; 550),
441 kind: TypeHint,
442 label: "&u32",
443 },
444 ]
445 "###
446 );
447 }
448
449 #[test]
450 fn match_arm_list() {
451 let (analysis, file_id) = single_file(
452 r#"
453#[derive(PartialEq)]
454enum CustomOption<T> {
455 None,
456 Some(T),
457}
458
459#[derive(PartialEq)]
460struct Test {
461 a: CustomOption<u32>,
462 b: u8,
463}
464
465fn main() {
466 match CustomOption::Some(Test { a: CustomOption::Some(3), b: 1 }) {
467 CustomOption::None => (),
468 test => (),
469 CustomOption::Some(test) => (),
470 CustomOption::Some(Test { a, b }) => (),
471 CustomOption::Some(Test { a: x, b: y }) => (),
472 CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) => (),
473 CustomOption::Some(Test { a: CustomOption::None, b: y }) => (),
474 CustomOption::Some(Test { b: y, .. }) => (),
475 _ => {}
476 }
477}"#,
478 );
479
480 assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
481 [
482 InlayHint {
483 range: [311; 315),
484 kind: TypeHint,
485 label: "Test",
486 },
487 InlayHint {
488 range: [358; 359),
489 kind: TypeHint,
490 label: "CustomOption<u32>",
491 },
492 InlayHint {
493 range: [361; 362),
494 kind: TypeHint,
495 label: "u8",
496 },
497 InlayHint {
498 range: [484; 485),
499 kind: TypeHint,
500 label: "u32",
501 },
502 ]
503 "###
504 );
505 }
506
507 #[test]
508 fn hint_truncation() {
509 let (analysis, file_id) = single_file(
510 r#"
511struct Smol<T>(T);
512
513struct VeryLongOuterName<T>(T);
514
515fn main() {
516 let a = Smol(0u32);
517 let b = VeryLongOuterName(0usize);
518 let c = Smol(Smol(0u32))
519}"#,
520 );
521
522 assert_debug_snapshot!(analysis.inlay_hints(file_id, Some(8)).unwrap(), @r###"
523 [
524 InlayHint {
525 range: [74; 75),
526 kind: TypeHint,
527 label: "Smol<u32>",
528 },
529 InlayHint {
530 range: [98; 99),
531 kind: TypeHint,
532 label: "VeryLongOuterName<…>",
533 },
534 InlayHint {
535 range: [137; 138),
536 kind: TypeHint,
537 label: "Smol<Smol<…>>",
538 },
539 ]
540 "###
541 );
542 }
543}
diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs
new file mode 100644
index 000000000..7deeb3494
--- /dev/null
+++ b/crates/ra_ide/src/join_lines.rs
@@ -0,0 +1,611 @@
1//! FIXME: write short doc here
2
3use itertools::Itertools;
4use ra_fmt::{compute_ws, extract_trivial_expression};
5use ra_syntax::{
6 algo::{find_covering_element, non_trivia_sibling},
7 ast::{self, AstNode, AstToken},
8 Direction, NodeOrToken, SourceFile,
9 SyntaxKind::{self, WHITESPACE},
10 SyntaxNode, SyntaxToken, TextRange, TextUnit, T,
11};
12use ra_text_edit::{TextEdit, TextEditBuilder};
13
14pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit {
15 let range = if range.is_empty() {
16 let syntax = file.syntax();
17 let text = syntax.text().slice(range.start()..);
18 let pos = match text.find_char('\n') {
19 None => return TextEditBuilder::default().finish(),
20 Some(pos) => pos,
21 };
22 TextRange::offset_len(range.start() + pos, TextUnit::of_char('\n'))
23 } else {
24 range
25 };
26
27 let node = match find_covering_element(file.syntax(), range) {
28 NodeOrToken::Node(node) => node,
29 NodeOrToken::Token(token) => token.parent(),
30 };
31 let mut edit = TextEditBuilder::default();
32 for token in node.descendants_with_tokens().filter_map(|it| it.into_token()) {
33 let range = match range.intersection(&token.text_range()) {
34 Some(range) => range,
35 None => continue,
36 } - token.text_range().start();
37 let text = token.text();
38 for (pos, _) in text[range].bytes().enumerate().filter(|&(_, b)| b == b'\n') {
39 let pos: TextUnit = (pos as u32).into();
40 let off = token.text_range().start() + range.start() + pos;
41 if !edit.invalidates_offset(off) {
42 remove_newline(&mut edit, &token, off);
43 }
44 }
45 }
46
47 edit.finish()
48}
49
50fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextUnit) {
51 if token.kind() != WHITESPACE || token.text().bytes().filter(|&b| b == b'\n').count() != 1 {
52 // The node is either the first or the last in the file
53 let suff = &token.text()[TextRange::from_to(
54 offset - token.text_range().start() + TextUnit::of_char('\n'),
55 TextUnit::of_str(token.text()),
56 )];
57 let spaces = suff.bytes().take_while(|&b| b == b' ').count();
58
59 edit.replace(TextRange::offset_len(offset, ((spaces + 1) as u32).into()), " ".to_string());
60 return;
61 }
62
63 // Special case that turns something like:
64 //
65 // ```
66 // my_function({<|>
67 // <some-expr>
68 // })
69 // ```
70 //
71 // into `my_function(<some-expr>)`
72 if join_single_expr_block(edit, token).is_some() {
73 return;
74 }
75 // ditto for
76 //
77 // ```
78 // use foo::{<|>
79 // bar
80 // };
81 // ```
82 if join_single_use_tree(edit, token).is_some() {
83 return;
84 }
85
86 // The node is between two other nodes
87 let prev = token.prev_sibling_or_token().unwrap();
88 let next = token.next_sibling_or_token().unwrap();
89 if is_trailing_comma(prev.kind(), next.kind()) {
90 // Removes: trailing comma, newline (incl. surrounding whitespace)
91 edit.delete(TextRange::from_to(prev.text_range().start(), token.text_range().end()));
92 } else if prev.kind() == T![,] && next.kind() == T!['}'] {
93 // Removes: comma, newline (incl. surrounding whitespace)
94 let space = if let Some(left) = prev.prev_sibling_or_token() {
95 compute_ws(left.kind(), next.kind())
96 } else {
97 " "
98 };
99 edit.replace(
100 TextRange::from_to(prev.text_range().start(), token.text_range().end()),
101 space.to_string(),
102 );
103 } else if let (Some(_), Some(next)) = (
104 prev.as_token().cloned().and_then(ast::Comment::cast),
105 next.as_token().cloned().and_then(ast::Comment::cast),
106 ) {
107 // Removes: newline (incl. surrounding whitespace), start of the next comment
108 edit.delete(TextRange::from_to(
109 token.text_range().start(),
110 next.syntax().text_range().start() + TextUnit::of_str(next.prefix()),
111 ));
112 } else {
113 // Remove newline but add a computed amount of whitespace characters
114 edit.replace(token.text_range(), compute_ws(prev.kind(), next.kind()).to_string());
115 }
116}
117
118fn has_comma_after(node: &SyntaxNode) -> bool {
119 match non_trivia_sibling(node.clone().into(), Direction::Next) {
120 Some(n) => n.kind() == T![,],
121 _ => false,
122 }
123}
124
125fn join_single_expr_block(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> {
126 let block = ast::Block::cast(token.parent())?;
127 let block_expr = ast::BlockExpr::cast(block.syntax().parent()?)?;
128 let expr = extract_trivial_expression(&block_expr)?;
129
130 let block_range = block_expr.syntax().text_range();
131 let mut buf = expr.syntax().text().to_string();
132
133 // Match block needs to have a comma after the block
134 if let Some(match_arm) = block_expr.syntax().parent().and_then(ast::MatchArm::cast) {
135 if !has_comma_after(match_arm.syntax()) {
136 buf.push(',');
137 }
138 }
139
140 edit.replace(block_range, buf);
141
142 Some(())
143}
144
145fn join_single_use_tree(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> {
146 let use_tree_list = ast::UseTreeList::cast(token.parent())?;
147 let (tree,) = use_tree_list.use_trees().collect_tuple()?;
148 edit.replace(use_tree_list.syntax().text_range(), tree.syntax().text().to_string());
149 Some(())
150}
151
152fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool {
153 match (left, right) {
154 (T![,], T![')']) | (T![,], T![']']) => true,
155 _ => false,
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use crate::test_utils::{assert_eq_text, check_action, extract_range};
162
163 use super::*;
164
165 fn check_join_lines(before: &str, after: &str) {
166 check_action(before, after, |file, offset| {
167 let range = TextRange::offset_len(offset, 0.into());
168 let res = join_lines(file, range);
169 Some(res)
170 })
171 }
172
173 #[test]
174 fn test_join_lines_comma() {
175 check_join_lines(
176 r"
177fn foo() {
178 <|>foo(1,
179 )
180}
181",
182 r"
183fn foo() {
184 <|>foo(1)
185}
186",
187 );
188 }
189
190 #[test]
191 fn test_join_lines_lambda_block() {
192 check_join_lines(
193 r"
194pub fn reparse(&self, edit: &AtomTextEdit) -> File {
195 <|>self.incremental_reparse(edit).unwrap_or_else(|| {
196 self.full_reparse(edit)
197 })
198}
199",
200 r"
201pub fn reparse(&self, edit: &AtomTextEdit) -> File {
202 <|>self.incremental_reparse(edit).unwrap_or_else(|| self.full_reparse(edit))
203}
204",
205 );
206 }
207
208 #[test]
209 fn test_join_lines_block() {
210 check_join_lines(
211 r"
212fn foo() {
213 foo(<|>{
214 92
215 })
216}",
217 r"
218fn foo() {
219 foo(<|>92)
220}",
221 );
222 }
223
224 #[test]
225 fn join_lines_adds_comma_for_block_in_match_arm() {
226 check_join_lines(
227 r"
228fn foo(e: Result<U, V>) {
229 match e {
230 Ok(u) => <|>{
231 u.foo()
232 }
233 Err(v) => v,
234 }
235}",
236 r"
237fn foo(e: Result<U, V>) {
238 match e {
239 Ok(u) => <|>u.foo(),
240 Err(v) => v,
241 }
242}",
243 );
244 }
245
246 #[test]
247 fn join_lines_multiline_in_block() {
248 check_join_lines(
249 r"
250fn foo() {
251 match ty {
252 <|> Some(ty) => {
253 match ty {
254 _ => false,
255 }
256 }
257 _ => true,
258 }
259}
260",
261 r"
262fn foo() {
263 match ty {
264 <|> Some(ty) => match ty {
265 _ => false,
266 },
267 _ => true,
268 }
269}
270",
271 );
272 }
273
274 #[test]
275 fn join_lines_keeps_comma_for_block_in_match_arm() {
276 // We already have a comma
277 check_join_lines(
278 r"
279fn foo(e: Result<U, V>) {
280 match e {
281 Ok(u) => <|>{
282 u.foo()
283 },
284 Err(v) => v,
285 }
286}",
287 r"
288fn foo(e: Result<U, V>) {
289 match e {
290 Ok(u) => <|>u.foo(),
291 Err(v) => v,
292 }
293}",
294 );
295
296 // comma with whitespace between brace and ,
297 check_join_lines(
298 r"
299fn foo(e: Result<U, V>) {
300 match e {
301 Ok(u) => <|>{
302 u.foo()
303 } ,
304 Err(v) => v,
305 }
306}",
307 r"
308fn foo(e: Result<U, V>) {
309 match e {
310 Ok(u) => <|>u.foo() ,
311 Err(v) => v,
312 }
313}",
314 );
315
316 // comma with newline between brace and ,
317 check_join_lines(
318 r"
319fn foo(e: Result<U, V>) {
320 match e {
321 Ok(u) => <|>{
322 u.foo()
323 }
324 ,
325 Err(v) => v,
326 }
327}",
328 r"
329fn foo(e: Result<U, V>) {
330 match e {
331 Ok(u) => <|>u.foo()
332 ,
333 Err(v) => v,
334 }
335}",
336 );
337 }
338
339 #[test]
340 fn join_lines_keeps_comma_with_single_arg_tuple() {
341 // A single arg tuple
342 check_join_lines(
343 r"
344fn foo() {
345 let x = (<|>{
346 4
347 },);
348}",
349 r"
350fn foo() {
351 let x = (<|>4,);
352}",
353 );
354
355 // single arg tuple with whitespace between brace and comma
356 check_join_lines(
357 r"
358fn foo() {
359 let x = (<|>{
360 4
361 } ,);
362}",
363 r"
364fn foo() {
365 let x = (<|>4 ,);
366}",
367 );
368
369 // single arg tuple with newline between brace and comma
370 check_join_lines(
371 r"
372fn foo() {
373 let x = (<|>{
374 4
375 }
376 ,);
377}",
378 r"
379fn foo() {
380 let x = (<|>4
381 ,);
382}",
383 );
384 }
385
386 #[test]
387 fn test_join_lines_use_items_left() {
388 // No space after the '{'
389 check_join_lines(
390 r"
391<|>use ra_syntax::{
392 TextUnit, TextRange,
393};",
394 r"
395<|>use ra_syntax::{TextUnit, TextRange,
396};",
397 );
398 }
399
400 #[test]
401 fn test_join_lines_use_items_right() {
402 // No space after the '}'
403 check_join_lines(
404 r"
405use ra_syntax::{
406<|> TextUnit, TextRange
407};",
408 r"
409use ra_syntax::{
410<|> TextUnit, TextRange};",
411 );
412 }
413
414 #[test]
415 fn test_join_lines_use_items_right_comma() {
416 // No space after the '}'
417 check_join_lines(
418 r"
419use ra_syntax::{
420<|> TextUnit, TextRange,
421};",
422 r"
423use ra_syntax::{
424<|> TextUnit, TextRange};",
425 );
426 }
427
428 #[test]
429 fn test_join_lines_use_tree() {
430 check_join_lines(
431 r"
432use ra_syntax::{
433 algo::<|>{
434 find_token_at_offset,
435 },
436 ast,
437};",
438 r"
439use ra_syntax::{
440 algo::<|>find_token_at_offset,
441 ast,
442};",
443 );
444 }
445
446 #[test]
447 fn test_join_lines_normal_comments() {
448 check_join_lines(
449 r"
450fn foo() {
451 // Hello<|>
452 // world!
453}
454",
455 r"
456fn foo() {
457 // Hello<|> world!
458}
459",
460 );
461 }
462
463 #[test]
464 fn test_join_lines_doc_comments() {
465 check_join_lines(
466 r"
467fn foo() {
468 /// Hello<|>
469 /// world!
470}
471",
472 r"
473fn foo() {
474 /// Hello<|> world!
475}
476",
477 );
478 }
479
480 #[test]
481 fn test_join_lines_mod_comments() {
482 check_join_lines(
483 r"
484fn foo() {
485 //! Hello<|>
486 //! world!
487}
488",
489 r"
490fn foo() {
491 //! Hello<|> world!
492}
493",
494 );
495 }
496
497 #[test]
498 fn test_join_lines_multiline_comments_1() {
499 check_join_lines(
500 r"
501fn foo() {
502 // Hello<|>
503 /* world! */
504}
505",
506 r"
507fn foo() {
508 // Hello<|> world! */
509}
510",
511 );
512 }
513
514 #[test]
515 fn test_join_lines_multiline_comments_2() {
516 check_join_lines(
517 r"
518fn foo() {
519 // The<|>
520 /* quick
521 brown
522 fox! */
523}
524",
525 r"
526fn foo() {
527 // The<|> quick
528 brown
529 fox! */
530}
531",
532 );
533 }
534
535 fn check_join_lines_sel(before: &str, after: &str) {
536 let (sel, before) = extract_range(before);
537 let parse = SourceFile::parse(&before);
538 let result = join_lines(&parse.tree(), sel);
539 let actual = result.apply(&before);
540 assert_eq_text!(after, &actual);
541 }
542
543 #[test]
544 fn test_join_lines_selection_fn_args() {
545 check_join_lines_sel(
546 r"
547fn foo() {
548 <|>foo(1,
549 2,
550 3,
551 <|>)
552}
553 ",
554 r"
555fn foo() {
556 foo(1, 2, 3)
557}
558 ",
559 );
560 }
561
562 #[test]
563 fn test_join_lines_selection_struct() {
564 check_join_lines_sel(
565 r"
566struct Foo <|>{
567 f: u32,
568}<|>
569 ",
570 r"
571struct Foo { f: u32 }
572 ",
573 );
574 }
575
576 #[test]
577 fn test_join_lines_selection_dot_chain() {
578 check_join_lines_sel(
579 r"
580fn foo() {
581 join(<|>type_params.type_params()
582 .filter_map(|it| it.name())
583 .map(|it| it.text())<|>)
584}",
585 r"
586fn foo() {
587 join(type_params.type_params().filter_map(|it| it.name()).map(|it| it.text()))
588}",
589 );
590 }
591
592 #[test]
593 fn test_join_lines_selection_lambda_block_body() {
594 check_join_lines_sel(
595 r"
596pub fn handle_find_matching_brace() {
597 params.offsets
598 .map(|offset| <|>{
599 world.analysis().matching_brace(&file, offset).unwrap_or(offset)
600 }<|>)
601 .collect();
602}",
603 r"
604pub fn handle_find_matching_brace() {
605 params.offsets
606 .map(|offset| world.analysis().matching_brace(&file, offset).unwrap_or(offset))
607 .collect();
608}",
609 );
610 }
611}
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
new file mode 100644
index 000000000..d1bff4a76
--- /dev/null
+++ b/crates/ra_ide/src/lib.rs
@@ -0,0 +1,489 @@
1//! ra_ide crate provides "ide-centric" APIs for the rust-analyzer. That is,
2//! it generally operates with files and text ranges, and returns results as
3//! Strings, suitable for displaying to the human.
4//!
5//! What powers this API are the `RootDatabase` struct, which defines a `salsa`
6//! database, and the `ra_hir` crate, where majority of the analysis happens.
7//! However, IDE specific bits of the analysis (most notably completion) happen
8//! in this crate.
9
10// For proving that RootDatabase is RefUnwindSafe.
11#![recursion_limit = "128"]
12
13mod db;
14pub mod mock_analysis;
15mod symbol_index;
16mod change;
17mod source_change;
18mod feature_flags;
19
20mod status;
21mod completion;
22mod runnables;
23mod goto_definition;
24mod goto_type_definition;
25mod extend_selection;
26mod hover;
27mod call_info;
28mod syntax_highlighting;
29mod parent_module;
30mod references;
31mod impls;
32mod assists;
33mod diagnostics;
34mod syntax_tree;
35mod folding_ranges;
36mod line_index;
37mod line_index_utils;
38mod join_lines;
39mod typing;
40mod matching_brace;
41mod display;
42mod inlay_hints;
43mod wasm_shims;
44mod expand;
45mod expand_macro;
46
47#[cfg(test)]
48mod marks;
49#[cfg(test)]
50mod test_utils;
51
52use std::sync::Arc;
53
54use ra_cfg::CfgOptions;
55use ra_db::{
56 salsa::{self, ParallelDatabase},
57 CheckCanceled, Env, FileLoader, SourceDatabase,
58};
59use ra_syntax::{SourceFile, TextRange, TextUnit};
60
61use crate::{db::LineIndexDatabase, display::ToNav, symbol_index::FileSymbol};
62
63pub use crate::{
64 assists::{Assist, AssistId},
65 change::{AnalysisChange, LibraryData},
66 completion::{CompletionItem, CompletionItemKind, InsertTextFormat},
67 diagnostics::Severity,
68 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode},
69 expand_macro::ExpandedMacro,
70 feature_flags::FeatureFlags,
71 folding_ranges::{Fold, FoldKind},
72 hover::HoverResult,
73 inlay_hints::{InlayHint, InlayKind},
74 line_index::{LineCol, LineIndex},
75 line_index_utils::translate_offset_with_edit,
76 references::{ReferenceSearchResult, SearchScope},
77 runnables::{Runnable, RunnableKind},
78 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
79 syntax_highlighting::HighlightedRange,
80};
81
82pub use hir::Documentation;
83pub use ra_db::{
84 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId,
85};
86
87pub type Cancelable<T> = Result<T, Canceled>;
88
89#[derive(Debug)]
90pub struct Diagnostic {
91 pub message: String,
92 pub range: TextRange,
93 pub fix: Option<SourceChange>,
94 pub severity: Severity,
95}
96
97#[derive(Debug)]
98pub struct Query {
99 query: String,
100 lowercased: String,
101 only_types: bool,
102 libs: bool,
103 exact: bool,
104 limit: usize,
105}
106
107impl Query {
108 pub fn new(query: String) -> Query {
109 let lowercased = query.to_lowercase();
110 Query {
111 query,
112 lowercased,
113 only_types: false,
114 libs: false,
115 exact: false,
116 limit: usize::max_value(),
117 }
118 }
119
120 pub fn only_types(&mut self) {
121 self.only_types = true;
122 }
123
124 pub fn libs(&mut self) {
125 self.libs = true;
126 }
127
128 pub fn exact(&mut self) {
129 self.exact = true;
130 }
131
132 pub fn limit(&mut self, limit: usize) {
133 self.limit = limit
134 }
135}
136
137/// Info associated with a text range.
138#[derive(Debug)]
139pub struct RangeInfo<T> {
140 pub range: TextRange,
141 pub info: T,
142}
143
144impl<T> RangeInfo<T> {
145 pub fn new(range: TextRange, info: T) -> RangeInfo<T> {
146 RangeInfo { range, info }
147 }
148}
149
150/// Contains information about a call site. Specifically the
151/// `FunctionSignature`and current parameter.
152#[derive(Debug)]
153pub struct CallInfo {
154 pub signature: FunctionSignature,
155 pub active_parameter: Option<usize>,
156}
157
158/// `AnalysisHost` stores the current state of the world.
159#[derive(Debug)]
160pub struct AnalysisHost {
161 db: db::RootDatabase,
162}
163
164impl Default for AnalysisHost {
165 fn default() -> AnalysisHost {
166 AnalysisHost::new(None, FeatureFlags::default())
167 }
168}
169
170impl AnalysisHost {
171 pub fn new(lru_capcity: Option<usize>, feature_flags: FeatureFlags) -> AnalysisHost {
172 AnalysisHost { db: db::RootDatabase::new(lru_capcity, feature_flags) }
173 }
174 /// Returns a snapshot of the current state, which you can query for
175 /// semantic information.
176 pub fn analysis(&self) -> Analysis {
177 Analysis { db: self.db.snapshot() }
178 }
179
180 pub fn feature_flags(&self) -> &FeatureFlags {
181 &self.db.feature_flags
182 }
183
184 /// Applies changes to the current state of the world. If there are
185 /// outstanding snapshots, they will be canceled.
186 pub fn apply_change(&mut self, change: AnalysisChange) {
187 self.db.apply_change(change)
188 }
189
190 pub fn maybe_collect_garbage(&mut self) {
191 self.db.maybe_collect_garbage();
192 }
193
194 pub fn collect_garbage(&mut self) {
195 self.db.collect_garbage();
196 }
197 /// NB: this clears the database
198 pub fn per_query_memory_usage(&mut self) -> Vec<(String, ra_prof::Bytes)> {
199 self.db.per_query_memory_usage()
200 }
201 pub fn raw_database(
202 &self,
203 ) -> &(impl hir::db::HirDatabase + salsa::Database + ra_db::SourceDatabaseExt) {
204 &self.db
205 }
206 pub fn raw_database_mut(
207 &mut self,
208 ) -> &mut (impl hir::db::HirDatabase + salsa::Database + ra_db::SourceDatabaseExt) {
209 &mut self.db
210 }
211}
212
213/// Analysis is a snapshot of a world state at a moment in time. It is the main
214/// entry point for asking semantic information about the world. When the world
215/// state is advanced using `AnalysisHost::apply_change` method, all existing
216/// `Analysis` are canceled (most method return `Err(Canceled)`).
217#[derive(Debug)]
218pub struct Analysis {
219 db: salsa::Snapshot<db::RootDatabase>,
220}
221
222// As a general design guideline, `Analysis` API are intended to be independent
223// from the language server protocol. That is, when exposing some functionality
224// we should think in terms of "what API makes most sense" and not in terms of
225// "what types LSP uses". Although currently LSP is the only consumer of the
226// API, the API should in theory be usable as a library, or via a different
227// protocol.
228impl Analysis {
229 // Creates an analysis instance for a single file, without any extenal
230 // dependencies, stdlib support or ability to apply changes. See
231 // `AnalysisHost` for creating a fully-featured analysis.
232 pub fn from_single_file(text: String) -> (Analysis, FileId) {
233 let mut host = AnalysisHost::default();
234 let source_root = SourceRootId(0);
235 let mut change = AnalysisChange::new();
236 change.add_root(source_root, true);
237 let mut crate_graph = CrateGraph::default();
238 let file_id = FileId(0);
239 // FIXME: cfg options
240 // Default to enable test for single file.
241 let mut cfg_options = CfgOptions::default();
242 cfg_options.insert_atom("test".into());
243 crate_graph.add_crate_root(file_id, Edition::Edition2018, cfg_options, Env::default());
244 change.add_file(source_root, file_id, "main.rs".into(), Arc::new(text));
245 change.set_crate_graph(crate_graph);
246 host.apply_change(change);
247 (host.analysis(), file_id)
248 }
249
250 /// Features for Analysis.
251 pub fn feature_flags(&self) -> &FeatureFlags {
252 &self.db.feature_flags
253 }
254
255 /// Debug info about the current state of the analysis.
256 pub fn status(&self) -> Cancelable<String> {
257 self.with_db(|db| status::status(&*db))
258 }
259
260 /// Gets the text of the source file.
261 pub fn file_text(&self, file_id: FileId) -> Cancelable<Arc<String>> {
262 self.with_db(|db| db.file_text(file_id))
263 }
264
265 /// Gets the syntax tree of the file.
266 pub fn parse(&self, file_id: FileId) -> Cancelable<SourceFile> {
267 self.with_db(|db| db.parse(file_id).tree())
268 }
269
270 /// Gets the file's `LineIndex`: data structure to convert between absolute
271 /// offsets and line/column representation.
272 pub fn file_line_index(&self, file_id: FileId) -> Cancelable<Arc<LineIndex>> {
273 self.with_db(|db| db.line_index(file_id))
274 }
275
276 /// Selects the next syntactic nodes encompassing the range.
277 pub fn extend_selection(&self, frange: FileRange) -> Cancelable<TextRange> {
278 self.with_db(|db| extend_selection::extend_selection(db, frange))
279 }
280
281 /// Returns position of the matching brace (all types of braces are
282 /// supported).
283 pub fn matching_brace(&self, position: FilePosition) -> Cancelable<Option<TextUnit>> {
284 self.with_db(|db| {
285 let parse = db.parse(position.file_id);
286 let file = parse.tree();
287 matching_brace::matching_brace(&file, position.offset)
288 })
289 }
290
291 /// Returns a syntax tree represented as `String`, for debug purposes.
292 // FIXME: use a better name here.
293 pub fn syntax_tree(
294 &self,
295 file_id: FileId,
296 text_range: Option<TextRange>,
297 ) -> Cancelable<String> {
298 self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range))
299 }
300
301 pub fn expand_macro(&self, position: FilePosition) -> Cancelable<Option<ExpandedMacro>> {
302 self.with_db(|db| expand_macro::expand_macro(db, position))
303 }
304
305 /// Returns an edit to remove all newlines in the range, cleaning up minor
306 /// stuff like trailing commas.
307 pub fn join_lines(&self, frange: FileRange) -> Cancelable<SourceChange> {
308 self.with_db(|db| {
309 let parse = db.parse(frange.file_id);
310 let file_edit = SourceFileEdit {
311 file_id: frange.file_id,
312 edit: join_lines::join_lines(&parse.tree(), frange.range),
313 };
314 SourceChange::source_file_edit("join lines", file_edit)
315 })
316 }
317
318 /// Returns an edit which should be applied when opening a new line, fixing
319 /// up minor stuff like continuing the comment.
320 pub fn on_enter(&self, position: FilePosition) -> Cancelable<Option<SourceChange>> {
321 self.with_db(|db| typing::on_enter(&db, position))
322 }
323
324 /// Returns an edit which should be applied after a character was typed.
325 ///
326 /// This is useful for some on-the-fly fixups, like adding `;` to `let =`
327 /// automatically.
328 pub fn on_char_typed(
329 &self,
330 position: FilePosition,
331 char_typed: char,
332 ) -> Cancelable<Option<SourceChange>> {
333 // Fast path to not even parse the file.
334 if !typing::TRIGGER_CHARS.contains(char_typed) {
335 return Ok(None);
336 }
337 self.with_db(|db| typing::on_char_typed(&db, position, char_typed))
338 }
339
340 /// Returns a tree representation of symbols in the file. Useful to draw a
341 /// file outline.
342 pub fn file_structure(&self, file_id: FileId) -> Cancelable<Vec<StructureNode>> {
343 self.with_db(|db| file_structure(&db.parse(file_id).tree()))
344 }
345
346 /// Returns a list of the places in the file where type hints can be displayed.
347 pub fn inlay_hints(
348 &self,
349 file_id: FileId,
350 max_inlay_hint_length: Option<usize>,
351 ) -> Cancelable<Vec<InlayHint>> {
352 self.with_db(|db| {
353 inlay_hints::inlay_hints(db, file_id, &db.parse(file_id).tree(), max_inlay_hint_length)
354 })
355 }
356
357 /// Returns the set of folding ranges.
358 pub fn folding_ranges(&self, file_id: FileId) -> Cancelable<Vec<Fold>> {
359 self.with_db(|db| folding_ranges::folding_ranges(&db.parse(file_id).tree()))
360 }
361
362 /// Fuzzy searches for a symbol.
363 pub fn symbol_search(&self, query: Query) -> Cancelable<Vec<NavigationTarget>> {
364 self.with_db(|db| {
365 symbol_index::world_symbols(db, query)
366 .into_iter()
367 .map(|s| s.to_nav(db))
368 .collect::<Vec<_>>()
369 })
370 }
371
372 /// Returns the definitions from the symbol at `position`.
373 pub fn goto_definition(
374 &self,
375 position: FilePosition,
376 ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
377 self.with_db(|db| goto_definition::goto_definition(db, position))
378 }
379
380 /// Returns the impls from the symbol at `position`.
381 pub fn goto_implementation(
382 &self,
383 position: FilePosition,
384 ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
385 self.with_db(|db| impls::goto_implementation(db, position))
386 }
387
388 /// Returns the type definitions for the symbol at `position`.
389 pub fn goto_type_definition(
390 &self,
391 position: FilePosition,
392 ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
393 self.with_db(|db| goto_type_definition::goto_type_definition(db, position))
394 }
395
396 /// Finds all usages of the reference at point.
397 pub fn find_all_refs(
398 &self,
399 position: FilePosition,
400 search_scope: Option<SearchScope>,
401 ) -> Cancelable<Option<ReferenceSearchResult>> {
402 self.with_db(|db| references::find_all_refs(db, position, search_scope).map(|it| it.info))
403 }
404
405 /// Returns a short text describing element at position.
406 pub fn hover(&self, position: FilePosition) -> Cancelable<Option<RangeInfo<HoverResult>>> {
407 self.with_db(|db| hover::hover(db, position))
408 }
409
410 /// Computes parameter information for the given call expression.
411 pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> {
412 self.with_db(|db| call_info::call_info(db, position))
413 }
414
415 /// Returns a `mod name;` declaration which created the current module.
416 pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> {
417 self.with_db(|db| parent_module::parent_module(db, position))
418 }
419
420 /// Returns crates this file belongs too.
421 pub fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> {
422 self.with_db(|db| parent_module::crate_for(db, file_id))
423 }
424
425 /// Returns the root file of the given crate.
426 pub fn crate_root(&self, crate_id: CrateId) -> Cancelable<FileId> {
427 self.with_db(|db| db.crate_graph().crate_root(crate_id))
428 }
429
430 /// Returns the set of possible targets to run for the current file.
431 pub fn runnables(&self, file_id: FileId) -> Cancelable<Vec<Runnable>> {
432 self.with_db(|db| runnables::runnables(db, file_id))
433 }
434
435 /// Computes syntax highlighting for the given file.
436 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> {
437 self.with_db(|db| syntax_highlighting::highlight(db, file_id))
438 }
439
440 /// Computes syntax highlighting for the given file.
441 pub fn highlight_as_html(&self, file_id: FileId, rainbow: bool) -> Cancelable<String> {
442 self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id, rainbow))
443 }
444
445 /// Computes completions at the given position.
446 pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> {
447 self.with_db(|db| completion::completions(db, position).map(Into::into))
448 }
449
450 /// Computes assists (aka code actions aka intentions) for the given
451 /// position.
452 pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> {
453 self.with_db(|db| assists::assists(db, frange))
454 }
455
456 /// Computes the set of diagnostics for the given file.
457 pub fn diagnostics(&self, file_id: FileId) -> Cancelable<Vec<Diagnostic>> {
458 self.with_db(|db| diagnostics::diagnostics(db, file_id))
459 }
460
461 /// Computes the type of the expression at the given position.
462 pub fn type_of(&self, frange: FileRange) -> Cancelable<Option<String>> {
463 self.with_db(|db| hover::type_of(db, frange))
464 }
465
466 /// Returns the edit required to rename reference at the position to the new
467 /// name.
468 pub fn rename(
469 &self,
470 position: FilePosition,
471 new_name: &str,
472 ) -> Cancelable<Option<RangeInfo<SourceChange>>> {
473 self.with_db(|db| references::rename(db, position, new_name))
474 }
475
476 /// Performs an operation on that may be Canceled.
477 fn with_db<F: FnOnce(&db::RootDatabase) -> T + std::panic::UnwindSafe, T>(
478 &self,
479 f: F,
480 ) -> Cancelable<T> {
481 self.db.catch_canceled(f)
482 }
483}
484
485#[test]
486fn analysis_is_send() {
487 fn is_send<T: Send>() {}
488 is_send::<Analysis>();
489}
diff --git a/crates/ra_ide/src/line_index.rs b/crates/ra_ide/src/line_index.rs
new file mode 100644
index 000000000..710890d27
--- /dev/null
+++ b/crates/ra_ide/src/line_index.rs
@@ -0,0 +1,283 @@
1//! FIXME: write short doc here
2
3use crate::TextUnit;
4use rustc_hash::FxHashMap;
5use superslice::Ext;
6
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct LineIndex {
9 pub(crate) newlines: Vec<TextUnit>,
10 pub(crate) utf16_lines: FxHashMap<u32, Vec<Utf16Char>>,
11}
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub struct LineCol {
15 /// Zero-based
16 pub line: u32,
17 /// Zero-based
18 pub col_utf16: u32,
19}
20
21#[derive(Clone, Debug, Hash, PartialEq, Eq)]
22pub(crate) struct Utf16Char {
23 pub(crate) start: TextUnit,
24 pub(crate) end: TextUnit,
25}
26
27impl Utf16Char {
28 fn len(&self) -> TextUnit {
29 self.end - self.start
30 }
31}
32
33impl LineIndex {
34 pub fn new(text: &str) -> LineIndex {
35 let mut utf16_lines = FxHashMap::default();
36 let mut utf16_chars = Vec::new();
37
38 let mut newlines = vec![0.into()];
39 let mut curr_row = 0.into();
40 let mut curr_col = 0.into();
41 let mut line = 0;
42 for c in text.chars() {
43 curr_row += TextUnit::of_char(c);
44 if c == '\n' {
45 newlines.push(curr_row);
46
47 // Save any utf-16 characters seen in the previous line
48 if !utf16_chars.is_empty() {
49 utf16_lines.insert(line, utf16_chars);
50 utf16_chars = Vec::new();
51 }
52
53 // Prepare for processing the next line
54 curr_col = 0.into();
55 line += 1;
56 continue;
57 }
58
59 let char_len = TextUnit::of_char(c);
60 if char_len.to_usize() > 1 {
61 utf16_chars.push(Utf16Char { start: curr_col, end: curr_col + char_len });
62 }
63
64 curr_col += char_len;
65 }
66
67 // Save any utf-16 characters seen in the last line
68 if !utf16_chars.is_empty() {
69 utf16_lines.insert(line, utf16_chars);
70 }
71
72 LineIndex { newlines, utf16_lines }
73 }
74
75 pub fn line_col(&self, offset: TextUnit) -> LineCol {
76 let line = self.newlines.upper_bound(&offset) - 1;
77 let line_start_offset = self.newlines[line];
78 let col = offset - line_start_offset;
79
80 LineCol { line: line as u32, col_utf16: self.utf8_to_utf16_col(line as u32, col) as u32 }
81 }
82
83 pub fn offset(&self, line_col: LineCol) -> TextUnit {
84 //FIXME: return Result
85 let col = self.utf16_to_utf8_col(line_col.line, line_col.col_utf16);
86 self.newlines[line_col.line as usize] + col
87 }
88
89 fn utf8_to_utf16_col(&self, line: u32, mut col: TextUnit) -> usize {
90 if let Some(utf16_chars) = self.utf16_lines.get(&line) {
91 let mut correction = TextUnit::from_usize(0);
92 for c in utf16_chars {
93 if col >= c.end {
94 correction += c.len() - TextUnit::from_usize(1);
95 } else {
96 // From here on, all utf16 characters come *after* the character we are mapping,
97 // so we don't need to take them into account
98 break;
99 }
100 }
101
102 col -= correction;
103 }
104
105 col.to_usize()
106 }
107
108 fn utf16_to_utf8_col(&self, line: u32, col: u32) -> TextUnit {
109 let mut col: TextUnit = col.into();
110 if let Some(utf16_chars) = self.utf16_lines.get(&line) {
111 for c in utf16_chars {
112 if col >= c.start {
113 col += c.len() - TextUnit::from_usize(1);
114 } else {
115 // From here on, all utf16 characters come *after* the character we are mapping,
116 // so we don't need to take them into account
117 break;
118 }
119 }
120 }
121
122 col
123 }
124}
125
126#[cfg(test)]
127/// Simple reference implementation to use in proptests
128pub fn to_line_col(text: &str, offset: TextUnit) -> LineCol {
129 let mut res = LineCol { line: 0, col_utf16: 0 };
130 for (i, c) in text.char_indices() {
131 if i + c.len_utf8() > offset.to_usize() {
132 // if it's an invalid offset, inside a multibyte char
133 // return as if it was at the start of the char
134 break;
135 }
136 if c == '\n' {
137 res.line += 1;
138 res.col_utf16 = 0;
139 } else {
140 res.col_utf16 += 1;
141 }
142 }
143 res
144}
145
146#[cfg(test)]
147mod test_line_index {
148 use super::*;
149 use proptest::{prelude::*, proptest};
150 use ra_text_edit::test_utils::{arb_offset, arb_text};
151
152 #[test]
153 fn test_line_index() {
154 let text = "hello\nworld";
155 let index = LineIndex::new(text);
156 assert_eq!(index.line_col(0.into()), LineCol { line: 0, col_utf16: 0 });
157 assert_eq!(index.line_col(1.into()), LineCol { line: 0, col_utf16: 1 });
158 assert_eq!(index.line_col(5.into()), LineCol { line: 0, col_utf16: 5 });
159 assert_eq!(index.line_col(6.into()), LineCol { line: 1, col_utf16: 0 });
160 assert_eq!(index.line_col(7.into()), LineCol { line: 1, col_utf16: 1 });
161 assert_eq!(index.line_col(8.into()), LineCol { line: 1, col_utf16: 2 });
162 assert_eq!(index.line_col(10.into()), LineCol { line: 1, col_utf16: 4 });
163 assert_eq!(index.line_col(11.into()), LineCol { line: 1, col_utf16: 5 });
164 assert_eq!(index.line_col(12.into()), LineCol { line: 1, col_utf16: 6 });
165
166 let text = "\nhello\nworld";
167 let index = LineIndex::new(text);
168 assert_eq!(index.line_col(0.into()), LineCol { line: 0, col_utf16: 0 });
169 assert_eq!(index.line_col(1.into()), LineCol { line: 1, col_utf16: 0 });
170 assert_eq!(index.line_col(2.into()), LineCol { line: 1, col_utf16: 1 });
171 assert_eq!(index.line_col(6.into()), LineCol { line: 1, col_utf16: 5 });
172 assert_eq!(index.line_col(7.into()), LineCol { line: 2, col_utf16: 0 });
173 }
174
175 fn arb_text_with_offset() -> BoxedStrategy<(TextUnit, String)> {
176 arb_text().prop_flat_map(|text| (arb_offset(&text), Just(text))).boxed()
177 }
178
179 fn to_line_col(text: &str, offset: TextUnit) -> LineCol {
180 let mut res = LineCol { line: 0, col_utf16: 0 };
181 for (i, c) in text.char_indices() {
182 if i + c.len_utf8() > offset.to_usize() {
183 // if it's an invalid offset, inside a multibyte char
184 // return as if it was at the start of the char
185 break;
186 }
187 if c == '\n' {
188 res.line += 1;
189 res.col_utf16 = 0;
190 } else {
191 res.col_utf16 += 1;
192 }
193 }
194 res
195 }
196
197 proptest! {
198 #[test]
199 fn test_line_index_proptest((offset, text) in arb_text_with_offset()) {
200 let expected = to_line_col(&text, offset);
201 let line_index = LineIndex::new(&text);
202 let actual = line_index.line_col(offset);
203
204 assert_eq!(actual, expected);
205 }
206 }
207}
208
209#[cfg(test)]
210mod test_utf8_utf16_conv {
211 use super::*;
212
213 #[test]
214 fn test_char_len() {
215 assert_eq!('メ'.len_utf8(), 3);
216 assert_eq!('メ'.len_utf16(), 1);
217 }
218
219 #[test]
220 fn test_empty_index() {
221 let col_index = LineIndex::new(
222 "
223const C: char = 'x';
224",
225 );
226 assert_eq!(col_index.utf16_lines.len(), 0);
227 }
228
229 #[test]
230 fn test_single_char() {
231 let col_index = LineIndex::new(
232 "
233const C: char = 'メ';
234",
235 );
236
237 assert_eq!(col_index.utf16_lines.len(), 1);
238 assert_eq!(col_index.utf16_lines[&1].len(), 1);
239 assert_eq!(col_index.utf16_lines[&1][0], Utf16Char { start: 17.into(), end: 20.into() });
240
241 // UTF-8 to UTF-16, no changes
242 assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15);
243
244 // UTF-8 to UTF-16
245 assert_eq!(col_index.utf8_to_utf16_col(1, 22.into()), 20);
246
247 // UTF-16 to UTF-8, no changes
248 assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextUnit::from(15));
249
250 // UTF-16 to UTF-8
251 assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextUnit::from(21));
252 }
253
254 #[test]
255 fn test_string() {
256 let col_index = LineIndex::new(
257 "
258const C: char = \"メ メ\";
259",
260 );
261
262 assert_eq!(col_index.utf16_lines.len(), 1);
263 assert_eq!(col_index.utf16_lines[&1].len(), 2);
264 assert_eq!(col_index.utf16_lines[&1][0], Utf16Char { start: 17.into(), end: 20.into() });
265 assert_eq!(col_index.utf16_lines[&1][1], Utf16Char { start: 21.into(), end: 24.into() });
266
267 // UTF-8 to UTF-16
268 assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15);
269
270 assert_eq!(col_index.utf8_to_utf16_col(1, 21.into()), 19);
271 assert_eq!(col_index.utf8_to_utf16_col(1, 25.into()), 21);
272
273 assert!(col_index.utf8_to_utf16_col(2, 15.into()) == 15);
274
275 // UTF-16 to UTF-8
276 assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextUnit::from_usize(15));
277
278 assert_eq!(col_index.utf16_to_utf8_col(1, 18), TextUnit::from_usize(20));
279 assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextUnit::from_usize(23));
280
281 assert_eq!(col_index.utf16_to_utf8_col(2, 15), TextUnit::from_usize(15));
282 }
283}
diff --git a/crates/ra_ide/src/line_index_utils.rs b/crates/ra_ide/src/line_index_utils.rs
new file mode 100644
index 000000000..bd1e08feb
--- /dev/null
+++ b/crates/ra_ide/src/line_index_utils.rs
@@ -0,0 +1,331 @@
1//! FIXME: write short doc here
2
3use crate::{line_index::Utf16Char, LineCol, LineIndex};
4use ra_syntax::{TextRange, TextUnit};
5use ra_text_edit::{AtomTextEdit, TextEdit};
6
7#[derive(Debug, Clone)]
8enum Step {
9 Newline(TextUnit),
10 Utf16Char(TextRange),
11}
12
13#[derive(Debug)]
14struct LineIndexStepIter<'a> {
15 line_index: &'a LineIndex,
16 next_newline_idx: usize,
17 utf16_chars: Option<(TextUnit, std::slice::Iter<'a, Utf16Char>)>,
18}
19
20impl<'a> LineIndexStepIter<'a> {
21 fn from(line_index: &LineIndex) -> LineIndexStepIter {
22 let mut x = LineIndexStepIter { line_index, next_newline_idx: 0, utf16_chars: None };
23 // skip first newline since it's not real
24 x.next();
25 x
26 }
27}
28
29impl<'a> Iterator for LineIndexStepIter<'a> {
30 type Item = Step;
31 fn next(&mut self) -> Option<Step> {
32 self.utf16_chars
33 .as_mut()
34 .and_then(|(newline, x)| {
35 let x = x.next()?;
36 Some(Step::Utf16Char(TextRange::from_to(*newline + x.start, *newline + x.end)))
37 })
38 .or_else(|| {
39 let next_newline = *self.line_index.newlines.get(self.next_newline_idx)?;
40 self.utf16_chars = self
41 .line_index
42 .utf16_lines
43 .get(&(self.next_newline_idx as u32))
44 .map(|x| (next_newline, x.iter()));
45 self.next_newline_idx += 1;
46 Some(Step::Newline(next_newline))
47 })
48 }
49}
50
51#[derive(Debug)]
52struct OffsetStepIter<'a> {
53 text: &'a str,
54 offset: TextUnit,
55}
56
57impl<'a> Iterator for OffsetStepIter<'a> {
58 type Item = Step;
59 fn next(&mut self) -> Option<Step> {
60 let (next, next_offset) = self
61 .text
62 .char_indices()
63 .filter_map(|(i, c)| {
64 if c == '\n' {
65 let next_offset = self.offset + TextUnit::from_usize(i + 1);
66 let next = Step::Newline(next_offset);
67 Some((next, next_offset))
68 } else {
69 let char_len = TextUnit::of_char(c);
70 if char_len.to_usize() > 1 {
71 let start = self.offset + TextUnit::from_usize(i);
72 let end = start + char_len;
73 let next = Step::Utf16Char(TextRange::from_to(start, end));
74 let next_offset = end;
75 Some((next, next_offset))
76 } else {
77 None
78 }
79 }
80 })
81 .next()?;
82 let next_idx = (next_offset - self.offset).to_usize();
83 self.text = &self.text[next_idx..];
84 self.offset = next_offset;
85 Some(next)
86 }
87}
88
89#[derive(Debug)]
90enum NextSteps<'a> {
91 Use,
92 ReplaceMany(OffsetStepIter<'a>),
93 AddMany(OffsetStepIter<'a>),
94}
95
96#[derive(Debug)]
97struct TranslatedEdit<'a> {
98 delete: TextRange,
99 insert: &'a str,
100 diff: i64,
101}
102
103struct Edits<'a> {
104 edits: &'a [AtomTextEdit],
105 current: Option<TranslatedEdit<'a>>,
106 acc_diff: i64,
107}
108
109impl<'a> Edits<'a> {
110 fn from_text_edit(text_edit: &'a TextEdit) -> Edits<'a> {
111 let mut x = Edits { edits: text_edit.as_atoms(), current: None, acc_diff: 0 };
112 x.advance_edit();
113 x
114 }
115 fn advance_edit(&mut self) {
116 self.acc_diff += self.current.as_ref().map_or(0, |x| x.diff);
117 match self.edits.split_first() {
118 Some((next, rest)) => {
119 let delete = self.translate_range(next.delete);
120 let diff = next.insert.len() as i64 - next.delete.len().to_usize() as i64;
121 self.current = Some(TranslatedEdit { delete, insert: &next.insert, diff });
122 self.edits = rest;
123 }
124 None => {
125 self.current = None;
126 }
127 }
128 }
129
130 fn next_inserted_steps(&mut self) -> Option<OffsetStepIter<'a>> {
131 let cur = self.current.as_ref()?;
132 let res = Some(OffsetStepIter { offset: cur.delete.start(), text: &cur.insert });
133 self.advance_edit();
134 res
135 }
136
137 fn next_steps(&mut self, step: &Step) -> NextSteps {
138 let step_pos = match *step {
139 Step::Newline(n) => n,
140 Step::Utf16Char(r) => r.end(),
141 };
142 match &mut self.current {
143 Some(edit) => {
144 if step_pos <= edit.delete.start() {
145 NextSteps::Use
146 } else if step_pos <= edit.delete.end() {
147 let iter = OffsetStepIter { offset: edit.delete.start(), text: &edit.insert };
148 // empty slice to avoid returning steps again
149 edit.insert = &edit.insert[edit.insert.len()..];
150 NextSteps::ReplaceMany(iter)
151 } else {
152 let iter = OffsetStepIter { offset: edit.delete.start(), text: &edit.insert };
153 // empty slice to avoid returning steps again
154 edit.insert = &edit.insert[edit.insert.len()..];
155 self.advance_edit();
156 NextSteps::AddMany(iter)
157 }
158 }
159 None => NextSteps::Use,
160 }
161 }
162
163 fn translate_range(&self, range: TextRange) -> TextRange {
164 if self.acc_diff == 0 {
165 range
166 } else {
167 let start = self.translate(range.start());
168 let end = self.translate(range.end());
169 TextRange::from_to(start, end)
170 }
171 }
172
173 fn translate(&self, x: TextUnit) -> TextUnit {
174 if self.acc_diff == 0 {
175 x
176 } else {
177 TextUnit::from((x.to_usize() as i64 + self.acc_diff) as u32)
178 }
179 }
180
181 fn translate_step(&self, x: &Step) -> Step {
182 if self.acc_diff == 0 {
183 x.clone()
184 } else {
185 match *x {
186 Step::Newline(n) => Step::Newline(self.translate(n)),
187 Step::Utf16Char(r) => Step::Utf16Char(self.translate_range(r)),
188 }
189 }
190 }
191}
192
193#[derive(Debug)]
194struct RunningLineCol {
195 line: u32,
196 last_newline: TextUnit,
197 col_adjust: TextUnit,
198}
199
200impl RunningLineCol {
201 fn new() -> RunningLineCol {
202 RunningLineCol { line: 0, last_newline: TextUnit::from(0), col_adjust: TextUnit::from(0) }
203 }
204
205 fn to_line_col(&self, offset: TextUnit) -> LineCol {
206 LineCol {
207 line: self.line,
208 col_utf16: ((offset - self.last_newline) - self.col_adjust).into(),
209 }
210 }
211
212 fn add_line(&mut self, newline: TextUnit) {
213 self.line += 1;
214 self.last_newline = newline;
215 self.col_adjust = TextUnit::from(0);
216 }
217
218 fn adjust_col(&mut self, range: TextRange) {
219 self.col_adjust += range.len() - TextUnit::from(1);
220 }
221}
222
223pub fn translate_offset_with_edit(
224 line_index: &LineIndex,
225 offset: TextUnit,
226 text_edit: &TextEdit,
227) -> LineCol {
228 let mut state = Edits::from_text_edit(&text_edit);
229
230 let mut res = RunningLineCol::new();
231
232 macro_rules! test_step {
233 ($x:ident) => {
234 match &$x {
235 Step::Newline(n) => {
236 if offset < *n {
237 return res.to_line_col(offset);
238 } else {
239 res.add_line(*n);
240 }
241 }
242 Step::Utf16Char(x) => {
243 if offset < x.end() {
244 // if the offset is inside a multibyte char it's invalid
245 // clamp it to the start of the char
246 let clamp = offset.min(x.start());
247 return res.to_line_col(clamp);
248 } else {
249 res.adjust_col(*x);
250 }
251 }
252 }
253 };
254 }
255
256 for orig_step in LineIndexStepIter::from(line_index) {
257 loop {
258 let translated_step = state.translate_step(&orig_step);
259 match state.next_steps(&translated_step) {
260 NextSteps::Use => {
261 test_step!(translated_step);
262 break;
263 }
264 NextSteps::ReplaceMany(ns) => {
265 for n in ns {
266 test_step!(n);
267 }
268 break;
269 }
270 NextSteps::AddMany(ns) => {
271 for n in ns {
272 test_step!(n);
273 }
274 }
275 }
276 }
277 }
278
279 loop {
280 match state.next_inserted_steps() {
281 None => break,
282 Some(ns) => {
283 for n in ns {
284 test_step!(n);
285 }
286 }
287 }
288 }
289
290 res.to_line_col(offset)
291}
292
293#[cfg(test)]
294mod test {
295 use super::*;
296 use crate::line_index;
297 use proptest::{prelude::*, proptest};
298 use ra_text_edit::test_utils::{arb_offset, arb_text_with_edit};
299 use ra_text_edit::TextEdit;
300
301 #[derive(Debug)]
302 struct ArbTextWithEditAndOffset {
303 text: String,
304 edit: TextEdit,
305 edited_text: String,
306 offset: TextUnit,
307 }
308
309 fn arb_text_with_edit_and_offset() -> BoxedStrategy<ArbTextWithEditAndOffset> {
310 arb_text_with_edit()
311 .prop_flat_map(|x| {
312 let edited_text = x.edit.apply(&x.text);
313 let arb_offset = arb_offset(&edited_text);
314 (Just(x), Just(edited_text), arb_offset).prop_map(|(x, edited_text, offset)| {
315 ArbTextWithEditAndOffset { text: x.text, edit: x.edit, edited_text, offset }
316 })
317 })
318 .boxed()
319 }
320
321 proptest! {
322 #[test]
323 fn test_translate_offset_with_edit(x in arb_text_with_edit_and_offset()) {
324 let expected = line_index::to_line_col(&x.edited_text, x.offset);
325 let line_index = LineIndex::new(&x.text);
326 let actual = translate_offset_with_edit(&line_index, x.offset, &x.edit);
327
328 assert_eq!(actual, expected);
329 }
330 }
331}
diff --git a/crates/ra_ide/src/marks.rs b/crates/ra_ide/src/marks.rs
new file mode 100644
index 000000000..848ae4dc7
--- /dev/null
+++ b/crates/ra_ide/src/marks.rs
@@ -0,0 +1,13 @@
1//! See test_utils/src/marks.rs
2
3test_utils::marks!(
4 inserts_angle_brackets_for_generics
5 inserts_parens_for_function_calls
6 goto_definition_works_for_macros
7 goto_definition_works_for_methods
8 goto_definition_works_for_fields
9 goto_definition_works_for_record_fields
10 call_info_bad_offset
11 dont_complete_current_use
12 dont_complete_primitive_in_use
13);
diff --git a/crates/ra_ide/src/matching_brace.rs b/crates/ra_ide/src/matching_brace.rs
new file mode 100644
index 000000000..d1204fac0
--- /dev/null
+++ b/crates/ra_ide/src/matching_brace.rs
@@ -0,0 +1,43 @@
1//! FIXME: write short doc here
2
3use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextUnit, T};
4
5pub fn matching_brace(file: &SourceFile, offset: TextUnit) -> Option<TextUnit> {
6 const BRACES: &[SyntaxKind] =
7 &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>]];
8 let (brace_node, brace_idx) = file
9 .syntax()
10 .token_at_offset(offset)
11 .filter_map(|node| {
12 let idx = BRACES.iter().position(|&brace| brace == node.kind())?;
13 Some((node, idx))
14 })
15 .next()?;
16 let parent = brace_node.parent();
17 let matching_kind = BRACES[brace_idx ^ 1];
18 let matching_node = parent.children_with_tokens().find(|node| node.kind() == matching_kind)?;
19 Some(matching_node.text_range().start())
20}
21
22#[cfg(test)]
23mod tests {
24 use test_utils::{add_cursor, assert_eq_text, extract_offset};
25
26 use super::*;
27
28 #[test]
29 fn test_matching_brace() {
30 fn do_check(before: &str, after: &str) {
31 let (pos, before) = extract_offset(before);
32 let parse = SourceFile::parse(&before);
33 let new_pos = match matching_brace(&parse.tree(), pos) {
34 None => pos,
35 Some(pos) => pos,
36 };
37 let actual = add_cursor(&before, new_pos);
38 assert_eq_text!(after, &actual);
39 }
40
41 do_check("struct Foo { a: i32, }<|>", "struct Foo <|>{ a: i32, }");
42 }
43}
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs
new file mode 100644
index 000000000..bf8a54932
--- /dev/null
+++ b/crates/ra_ide/src/mock_analysis.rs
@@ -0,0 +1,149 @@
1//! FIXME: write short doc here
2
3use std::sync::Arc;
4
5use ra_cfg::CfgOptions;
6use ra_db::{Env, RelativePathBuf};
7use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER};
8
9use crate::{
10 Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition::Edition2018, FileId, FilePosition,
11 FileRange, SourceRootId,
12};
13
14/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis
15/// from a set of in-memory files.
16#[derive(Debug, Default)]
17pub struct MockAnalysis {
18 files: Vec<(String, String)>,
19}
20
21impl MockAnalysis {
22 pub fn new() -> MockAnalysis {
23 MockAnalysis::default()
24 }
25 /// Creates `MockAnalysis` using a fixture data in the following format:
26 ///
27 /// ```not_rust
28 /// //- /main.rs
29 /// mod foo;
30 /// fn main() {}
31 ///
32 /// //- /foo.rs
33 /// struct Baz;
34 /// ```
35 pub fn with_files(fixture: &str) -> MockAnalysis {
36 let mut res = MockAnalysis::new();
37 for entry in parse_fixture(fixture) {
38 res.add_file(&entry.meta, &entry.text);
39 }
40 res
41 }
42
43 /// Same as `with_files`, but requires that a single file contains a `<|>` marker,
44 /// whose position is also returned.
45 pub fn with_files_and_position(fixture: &str) -> (MockAnalysis, FilePosition) {
46 let mut position = None;
47 let mut res = MockAnalysis::new();
48 for entry in parse_fixture(fixture) {
49 if entry.text.contains(CURSOR_MARKER) {
50 assert!(position.is_none(), "only one marker (<|>) per fixture is allowed");
51 position = Some(res.add_file_with_position(&entry.meta, &entry.text));
52 } else {
53 res.add_file(&entry.meta, &entry.text);
54 }
55 }
56 let position = position.expect("expected a marker (<|>)");
57 (res, position)
58 }
59
60 pub fn add_file(&mut self, path: &str, text: &str) -> FileId {
61 let file_id = FileId((self.files.len() + 1) as u32);
62 self.files.push((path.to_string(), text.to_string()));
63 file_id
64 }
65 pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition {
66 let (offset, text) = extract_offset(text);
67 let file_id = FileId((self.files.len() + 1) as u32);
68 self.files.push((path.to_string(), text));
69 FilePosition { file_id, offset }
70 }
71 pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange {
72 let (range, text) = extract_range(text);
73 let file_id = FileId((self.files.len() + 1) as u32);
74 self.files.push((path.to_string(), text));
75 FileRange { file_id, range }
76 }
77 pub fn id_of(&self, path: &str) -> FileId {
78 let (idx, _) = self
79 .files
80 .iter()
81 .enumerate()
82 .find(|(_, (p, _text))| path == p)
83 .expect("no file in this mock");
84 FileId(idx as u32 + 1)
85 }
86 pub fn analysis_host(self) -> AnalysisHost {
87 let mut host = AnalysisHost::default();
88 let source_root = SourceRootId(0);
89 let mut change = AnalysisChange::new();
90 change.add_root(source_root, true);
91 let mut crate_graph = CrateGraph::default();
92 let mut root_crate = None;
93 for (i, (path, contents)) in self.files.into_iter().enumerate() {
94 assert!(path.starts_with('/'));
95 let path = RelativePathBuf::from_path(&path[1..]).unwrap();
96 let file_id = FileId(i as u32 + 1);
97 let cfg_options = CfgOptions::default();
98 if path == "/lib.rs" || path == "/main.rs" {
99 root_crate = Some(crate_graph.add_crate_root(
100 file_id,
101 Edition2018,
102 cfg_options,
103 Env::default(),
104 ));
105 } else if path.ends_with("/lib.rs") {
106 let other_crate =
107 crate_graph.add_crate_root(file_id, Edition2018, cfg_options, Env::default());
108 let crate_name = path.parent().unwrap().file_name().unwrap();
109 if let Some(root_crate) = root_crate {
110 crate_graph.add_dep(root_crate, crate_name.into(), other_crate).unwrap();
111 }
112 }
113 change.add_file(source_root, file_id, path, Arc::new(contents));
114 }
115 change.set_crate_graph(crate_graph);
116 host.apply_change(change);
117 host
118 }
119 pub fn analysis(self) -> Analysis {
120 self.analysis_host().analysis()
121 }
122}
123
124/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
125pub fn analysis_and_position(fixture: &str) -> (Analysis, FilePosition) {
126 let (mock, position) = MockAnalysis::with_files_and_position(fixture);
127 (mock.analysis(), position)
128}
129
130/// Creates analysis for a single file.
131pub fn single_file(code: &str) -> (Analysis, FileId) {
132 let mut mock = MockAnalysis::new();
133 let file_id = mock.add_file("/main.rs", code);
134 (mock.analysis(), file_id)
135}
136
137/// Creates analysis for a single file, returns position marked with <|>.
138pub fn single_file_with_position(code: &str) -> (Analysis, FilePosition) {
139 let mut mock = MockAnalysis::new();
140 let pos = mock.add_file_with_position("/main.rs", code);
141 (mock.analysis(), pos)
142}
143
144/// Creates analysis for a single file, returns range marked with a pair of <|>.
145pub fn single_file_with_range(code: &str) -> (Analysis, FileRange) {
146 let mut mock = MockAnalysis::new();
147 let pos = mock.add_file_with_range("/main.rs", code);
148 (mock.analysis(), pos)
149}
diff --git a/crates/ra_ide/src/parent_module.rs b/crates/ra_ide/src/parent_module.rs
new file mode 100644
index 000000000..6027e7d54
--- /dev/null
+++ b/crates/ra_ide/src/parent_module.rs
@@ -0,0 +1,104 @@
1//! FIXME: write short doc here
2
3use ra_db::{CrateId, FileId, FilePosition};
4
5use crate::{db::RootDatabase, NavigationTarget};
6
7/// This returns `Vec` because a module may be included from several places. We
8/// don't handle this case yet though, so the Vec has length at most one.
9pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> {
10 let src = hir::ModuleSource::from_position(db, position);
11 let module = match hir::Module::from_definition(
12 db,
13 hir::Source { file_id: position.file_id.into(), value: src },
14 ) {
15 None => return Vec::new(),
16 Some(it) => it,
17 };
18 let nav = NavigationTarget::from_module_to_decl(db, module);
19 vec![nav]
20}
21
22/// Returns `Vec` for the same reason as `parent_module`
23pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> {
24 let src = hir::ModuleSource::from_file_id(db, file_id);
25 let module =
26 match hir::Module::from_definition(db, hir::Source { file_id: file_id.into(), value: src })
27 {
28 Some(it) => it,
29 None => return Vec::new(),
30 };
31 let krate = module.krate();
32 vec![krate.crate_id()]
33}
34
35#[cfg(test)]
36mod tests {
37 use ra_cfg::CfgOptions;
38 use ra_db::Env;
39
40 use crate::{
41 mock_analysis::{analysis_and_position, MockAnalysis},
42 AnalysisChange, CrateGraph,
43 Edition::Edition2018,
44 };
45
46 #[test]
47 fn test_resolve_parent_module() {
48 let (analysis, pos) = analysis_and_position(
49 "
50 //- /lib.rs
51 mod foo;
52 //- /foo.rs
53 <|>// empty
54 ",
55 );
56 let nav = analysis.parent_module(pos).unwrap().pop().unwrap();
57 nav.assert_match("foo MODULE FileId(1) [0; 8)");
58 }
59
60 #[test]
61 fn test_resolve_parent_module_for_inline() {
62 let (analysis, pos) = analysis_and_position(
63 "
64 //- /lib.rs
65 mod foo {
66 mod bar {
67 mod baz { <|> }
68 }
69 }
70 ",
71 );
72 let nav = analysis.parent_module(pos).unwrap().pop().unwrap();
73 nav.assert_match("baz MODULE FileId(1) [32; 44)");
74 }
75
76 #[test]
77 fn test_resolve_crate_root() {
78 let mock = MockAnalysis::with_files(
79 "
80 //- /bar.rs
81 mod foo;
82 //- /foo.rs
83 // empty <|>
84 ",
85 );
86 let root_file = mock.id_of("/bar.rs");
87 let mod_file = mock.id_of("/foo.rs");
88 let mut host = mock.analysis_host();
89 assert!(host.analysis().crate_for(mod_file).unwrap().is_empty());
90
91 let mut crate_graph = CrateGraph::default();
92 let crate_id = crate_graph.add_crate_root(
93 root_file,
94 Edition2018,
95 CfgOptions::default(),
96 Env::default(),
97 );
98 let mut change = AnalysisChange::new();
99 change.set_crate_graph(crate_graph);
100 host.apply_change(change);
101
102 assert_eq!(host.analysis().crate_for(mod_file).unwrap(), vec![crate_id]);
103 }
104}
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs
new file mode 100644
index 000000000..21a1ea69e
--- /dev/null
+++ b/crates/ra_ide/src/references.rs
@@ -0,0 +1,389 @@
1//! This module implements a reference search.
2//! First, the element at the cursor position must be either an `ast::Name`
3//! or `ast::NameRef`. If it's a `ast::NameRef`, at the classification step we
4//! try to resolve the direct tree parent of this element, otherwise we
5//! already have a definition and just need to get its HIR together with
6//! some information that is needed for futher steps of searching.
7//! After that, we collect files that might contain references and look
8//! for text occurrences of the identifier. If there's an `ast::NameRef`
9//! at the index that the match starts at and its tree parent is
10//! resolved to the search element definition, we get a reference.
11
12mod classify;
13mod name_definition;
14mod rename;
15mod search_scope;
16
17use hir::Source;
18use once_cell::unsync::Lazy;
19use ra_db::{SourceDatabase, SourceDatabaseExt};
20use ra_prof::profile;
21use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxNode, TextUnit};
22
23use crate::{
24 db::RootDatabase, display::ToNav, FilePosition, FileRange, NavigationTarget, RangeInfo,
25};
26
27pub(crate) use self::{
28 classify::{classify_name, classify_name_ref},
29 name_definition::{NameDefinition, NameKind},
30 rename::rename,
31};
32
33pub use self::search_scope::SearchScope;
34
35#[derive(Debug, Clone)]
36pub struct ReferenceSearchResult {
37 declaration: NavigationTarget,
38 references: Vec<FileRange>,
39}
40
41impl ReferenceSearchResult {
42 pub fn declaration(&self) -> &NavigationTarget {
43 &self.declaration
44 }
45
46 pub fn references(&self) -> &[FileRange] {
47 &self.references
48 }
49
50 /// Total number of references
51 /// At least 1 since all valid references should
52 /// Have a declaration
53 pub fn len(&self) -> usize {
54 self.references.len() + 1
55 }
56}
57
58// allow turning ReferenceSearchResult into an iterator
59// over FileRanges
60impl IntoIterator for ReferenceSearchResult {
61 type Item = FileRange;
62 type IntoIter = std::vec::IntoIter<FileRange>;
63
64 fn into_iter(mut self) -> Self::IntoIter {
65 let mut v = Vec::with_capacity(self.len());
66 v.push(FileRange { file_id: self.declaration.file_id(), range: self.declaration.range() });
67 v.append(&mut self.references);
68 v.into_iter()
69 }
70}
71
72pub(crate) fn find_all_refs(
73 db: &RootDatabase,
74 position: FilePosition,
75 search_scope: Option<SearchScope>,
76) -> Option<RangeInfo<ReferenceSearchResult>> {
77 let parse = db.parse(position.file_id);
78 let syntax = parse.tree().syntax().clone();
79 let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position)?;
80
81 let declaration = match def.kind {
82 NameKind::Macro(mac) => mac.to_nav(db),
83 NameKind::Field(field) => field.to_nav(db),
84 NameKind::AssocItem(assoc) => assoc.to_nav(db),
85 NameKind::Def(def) => NavigationTarget::from_def(db, def)?,
86 NameKind::SelfType(imp) => imp.to_nav(db),
87 NameKind::Local(local) => local.to_nav(db),
88 NameKind::GenericParam(_) => return None,
89 };
90
91 let search_scope = {
92 let base = def.search_scope(db);
93 match search_scope {
94 None => base,
95 Some(scope) => base.intersection(&scope),
96 }
97 };
98
99 let references = process_definition(db, def, name, search_scope);
100
101 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references }))
102}
103
104fn find_name<'a>(
105 db: &RootDatabase,
106 syntax: &SyntaxNode,
107 position: FilePosition,
108) -> Option<RangeInfo<(String, NameDefinition)>> {
109 if let Some(name) = find_node_at_offset::<ast::Name>(&syntax, position.offset) {
110 let def = classify_name(db, Source::new(position.file_id.into(), &name))?;
111 let range = name.syntax().text_range();
112 return Some(RangeInfo::new(range, (name.text().to_string(), def)));
113 }
114 let name_ref = find_node_at_offset::<ast::NameRef>(&syntax, position.offset)?;
115 let def = classify_name_ref(db, Source::new(position.file_id.into(), &name_ref))?;
116 let range = name_ref.syntax().text_range();
117 Some(RangeInfo::new(range, (name_ref.text().to_string(), def)))
118}
119
120fn process_definition(
121 db: &RootDatabase,
122 def: NameDefinition,
123 name: String,
124 scope: SearchScope,
125) -> Vec<FileRange> {
126 let _p = profile("process_definition");
127
128 let pat = name.as_str();
129 let mut refs = vec![];
130
131 for (file_id, search_range) in scope {
132 let text = db.file_text(file_id);
133 let parse = Lazy::new(|| SourceFile::parse(&text));
134
135 for (idx, _) in text.match_indices(pat) {
136 let offset = TextUnit::from_usize(idx);
137
138 if let Some(name_ref) =
139 find_node_at_offset::<ast::NameRef>(parse.tree().syntax(), offset)
140 {
141 let range = name_ref.syntax().text_range();
142 if let Some(search_range) = search_range {
143 if !range.is_subrange(&search_range) {
144 continue;
145 }
146 }
147 if let Some(d) = classify_name_ref(db, Source::new(file_id.into(), &name_ref)) {
148 if d == def {
149 refs.push(FileRange { file_id, range });
150 }
151 }
152 }
153 }
154 }
155 refs
156}
157
158#[cfg(test)]
159mod tests {
160 use crate::{
161 mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis},
162 ReferenceSearchResult, SearchScope,
163 };
164
165 #[test]
166 fn test_find_all_refs_for_local() {
167 let code = r#"
168 fn main() {
169 let mut i = 1;
170 let j = 1;
171 i = i<|> + j;
172
173 {
174 i = 0;
175 }
176
177 i = 5;
178 }"#;
179
180 let refs = get_all_refs(code);
181 assert_eq!(refs.len(), 5);
182 }
183
184 #[test]
185 fn test_find_all_refs_for_param_inside() {
186 let code = r#"
187 fn foo(i : u32) -> u32 {
188 i<|>
189 }"#;
190
191 let refs = get_all_refs(code);
192 assert_eq!(refs.len(), 2);
193 }
194
195 #[test]
196 fn test_find_all_refs_for_fn_param() {
197 let code = r#"
198 fn foo(i<|> : u32) -> u32 {
199 i
200 }"#;
201
202 let refs = get_all_refs(code);
203 assert_eq!(refs.len(), 2);
204 }
205
206 #[test]
207 fn test_find_all_refs_field_name() {
208 let code = r#"
209 //- /lib.rs
210 struct Foo {
211 pub spam<|>: u32,
212 }
213
214 fn main(s: Foo) {
215 let f = s.spam;
216 }
217 "#;
218
219 let refs = get_all_refs(code);
220 assert_eq!(refs.len(), 2);
221 }
222
223 #[test]
224 fn test_find_all_refs_impl_item_name() {
225 let code = r#"
226 //- /lib.rs
227 struct Foo;
228 impl Foo {
229 fn f<|>(&self) { }
230 }
231 "#;
232
233 let refs = get_all_refs(code);
234 assert_eq!(refs.len(), 1);
235 }
236
237 #[test]
238 fn test_find_all_refs_enum_var_name() {
239 let code = r#"
240 //- /lib.rs
241 enum Foo {
242 A,
243 B<|>,
244 C,
245 }
246 "#;
247
248 let refs = get_all_refs(code);
249 assert_eq!(refs.len(), 1);
250 }
251
252 #[test]
253 fn test_find_all_refs_two_modules() {
254 let code = r#"
255 //- /lib.rs
256 pub mod foo;
257 pub mod bar;
258
259 fn f() {
260 let i = foo::Foo { n: 5 };
261 }
262
263 //- /foo.rs
264 use crate::bar;
265
266 pub struct Foo {
267 pub n: u32,
268 }
269
270 fn f() {
271 let i = bar::Bar { n: 5 };
272 }
273
274 //- /bar.rs
275 use crate::foo;
276
277 pub struct Bar {
278 pub n: u32,
279 }
280
281 fn f() {
282 let i = foo::Foo<|> { n: 5 };
283 }
284 "#;
285
286 let (analysis, pos) = analysis_and_position(code);
287 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
288 assert_eq!(refs.len(), 3);
289 }
290
291 // `mod foo;` is not in the results because `foo` is an `ast::Name`.
292 // So, there are two references: the first one is a definition of the `foo` module,
293 // which is the whole `foo.rs`, and the second one is in `use foo::Foo`.
294 #[test]
295 fn test_find_all_refs_decl_module() {
296 let code = r#"
297 //- /lib.rs
298 mod foo<|>;
299
300 use foo::Foo;
301
302 fn f() {
303 let i = Foo { n: 5 };
304 }
305
306 //- /foo.rs
307 pub struct Foo {
308 pub n: u32,
309 }
310 "#;
311
312 let (analysis, pos) = analysis_and_position(code);
313 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
314 assert_eq!(refs.len(), 2);
315 }
316
317 #[test]
318 fn test_find_all_refs_super_mod_vis() {
319 let code = r#"
320 //- /lib.rs
321 mod foo;
322
323 //- /foo.rs
324 mod some;
325 use some::Foo;
326
327 fn f() {
328 let i = Foo { n: 5 };
329 }
330
331 //- /foo/some.rs
332 pub(super) struct Foo<|> {
333 pub n: u32,
334 }
335 "#;
336
337 let (analysis, pos) = analysis_and_position(code);
338 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
339 assert_eq!(refs.len(), 3);
340 }
341
342 #[test]
343 fn test_find_all_refs_with_scope() {
344 let code = r#"
345 //- /lib.rs
346 mod foo;
347 mod bar;
348
349 pub fn quux<|>() {}
350
351 //- /foo.rs
352 fn f() { super::quux(); }
353
354 //- /bar.rs
355 fn f() { super::quux(); }
356 "#;
357
358 let (mock, pos) = MockAnalysis::with_files_and_position(code);
359 let bar = mock.id_of("/bar.rs");
360 let analysis = mock.analysis();
361
362 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
363 assert_eq!(refs.len(), 3);
364
365 let refs =
366 analysis.find_all_refs(pos, Some(SearchScope::single_file(bar))).unwrap().unwrap();
367 assert_eq!(refs.len(), 2);
368 }
369
370 #[test]
371 fn test_find_all_refs_macro_def() {
372 let code = r#"
373 #[macro_export]
374 macro_rules! m1<|> { () => (()) }
375
376 fn foo() {
377 m1();
378 m1();
379 }"#;
380
381 let refs = get_all_refs(code);
382 assert_eq!(refs.len(), 3);
383 }
384
385 fn get_all_refs(text: &str) -> ReferenceSearchResult {
386 let (analysis, position) = single_file_with_position(text);
387 analysis.find_all_refs(position, None).unwrap().unwrap()
388 }
389}
diff --git a/crates/ra_ide/src/references/classify.rs b/crates/ra_ide/src/references/classify.rs
new file mode 100644
index 000000000..5cea805ec
--- /dev/null
+++ b/crates/ra_ide/src/references/classify.rs
@@ -0,0 +1,186 @@
1//! Functions that are used to classify an element from its definition or reference.
2
3use hir::{FromSource, Module, ModuleSource, PathResolution, Source, SourceAnalyzer};
4use ra_prof::profile;
5use ra_syntax::{ast, match_ast, AstNode};
6use test_utils::tested_by;
7
8use super::{
9 name_definition::{from_assoc_item, from_module_def, from_struct_field},
10 NameDefinition, NameKind,
11};
12use crate::db::RootDatabase;
13
14pub(crate) fn classify_name(db: &RootDatabase, name: Source<&ast::Name>) -> Option<NameDefinition> {
15 let _p = profile("classify_name");
16 let parent = name.value.syntax().parent()?;
17
18 match_ast! {
19 match parent {
20 ast::BindPat(it) => {
21 let src = name.with_value(it);
22 let local = hir::Local::from_source(db, src)?;
23 Some(NameDefinition {
24 visibility: None,
25 container: local.module(db),
26 kind: NameKind::Local(local),
27 })
28 },
29 ast::RecordFieldDef(it) => {
30 let ast = hir::FieldSource::Named(it);
31 let src = name.with_value(ast);
32 let field = hir::StructField::from_source(db, src)?;
33 Some(from_struct_field(db, field))
34 },
35 ast::Module(it) => {
36 let def = {
37 if !it.has_semi() {
38 let ast = hir::ModuleSource::Module(it);
39 let src = name.with_value(ast);
40 hir::Module::from_definition(db, src)
41 } else {
42 let src = name.with_value(it);
43 hir::Module::from_declaration(db, src)
44 }
45 }?;
46 Some(from_module_def(db, def.into(), None))
47 },
48 ast::StructDef(it) => {
49 let src = name.with_value(it);
50 let def = hir::Struct::from_source(db, src)?;
51 Some(from_module_def(db, def.into(), None))
52 },
53 ast::EnumDef(it) => {
54 let src = name.with_value(it);
55 let def = hir::Enum::from_source(db, src)?;
56 Some(from_module_def(db, def.into(), None))
57 },
58 ast::TraitDef(it) => {
59 let src = name.with_value(it);
60 let def = hir::Trait::from_source(db, src)?;
61 Some(from_module_def(db, def.into(), None))
62 },
63 ast::StaticDef(it) => {
64 let src = name.with_value(it);
65 let def = hir::Static::from_source(db, src)?;
66 Some(from_module_def(db, def.into(), None))
67 },
68 ast::EnumVariant(it) => {
69 let src = name.with_value(it);
70 let def = hir::EnumVariant::from_source(db, src)?;
71 Some(from_module_def(db, def.into(), None))
72 },
73 ast::FnDef(it) => {
74 let src = name.with_value(it);
75 let def = hir::Function::from_source(db, src)?;
76 if parent.parent().and_then(ast::ItemList::cast).is_some() {
77 Some(from_assoc_item(db, def.into()))
78 } else {
79 Some(from_module_def(db, def.into(), None))
80 }
81 },
82 ast::ConstDef(it) => {
83 let src = name.with_value(it);
84 let def = hir::Const::from_source(db, src)?;
85 if parent.parent().and_then(ast::ItemList::cast).is_some() {
86 Some(from_assoc_item(db, def.into()))
87 } else {
88 Some(from_module_def(db, def.into(), None))
89 }
90 },
91 ast::TypeAliasDef(it) => {
92 let src = name.with_value(it);
93 let def = hir::TypeAlias::from_source(db, src)?;
94 if parent.parent().and_then(ast::ItemList::cast).is_some() {
95 Some(from_assoc_item(db, def.into()))
96 } else {
97 Some(from_module_def(db, def.into(), None))
98 }
99 },
100 ast::MacroCall(it) => {
101 let src = name.with_value(it);
102 let def = hir::MacroDef::from_source(db, src.clone())?;
103
104 let module_src = ModuleSource::from_child_node(db, src.as_ref().map(|it| it.syntax()));
105 let module = Module::from_definition(db, src.with_value(module_src))?;
106
107 Some(NameDefinition {
108 visibility: None,
109 container: module,
110 kind: NameKind::Macro(def),
111 })
112 },
113 _ => None,
114 }
115 }
116}
117
118pub(crate) fn classify_name_ref(
119 db: &RootDatabase,
120 name_ref: Source<&ast::NameRef>,
121) -> Option<NameDefinition> {
122 let _p = profile("classify_name_ref");
123
124 let parent = name_ref.value.syntax().parent()?;
125 let analyzer = SourceAnalyzer::new(db, name_ref.map(|it| it.syntax()), None);
126
127 if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) {
128 tested_by!(goto_definition_works_for_methods);
129 if let Some(func) = analyzer.resolve_method_call(&method_call) {
130 return Some(from_assoc_item(db, func.into()));
131 }
132 }
133
134 if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
135 tested_by!(goto_definition_works_for_fields);
136 if let Some(field) = analyzer.resolve_field(&field_expr) {
137 return Some(from_struct_field(db, field));
138 }
139 }
140
141 if let Some(record_field) = ast::RecordField::cast(parent.clone()) {
142 tested_by!(goto_definition_works_for_record_fields);
143 if let Some(field_def) = analyzer.resolve_record_field(&record_field) {
144 return Some(from_struct_field(db, field_def));
145 }
146 }
147
148 let ast = ModuleSource::from_child_node(db, name_ref.with_value(&parent));
149 // FIXME: find correct container and visibility for each case
150 let container = Module::from_definition(db, name_ref.with_value(ast))?;
151 let visibility = None;
152
153 if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) {
154 tested_by!(goto_definition_works_for_macros);
155 if let Some(macro_def) = analyzer.resolve_macro_call(db, name_ref.with_value(&macro_call)) {
156 let kind = NameKind::Macro(macro_def);
157 return Some(NameDefinition { kind, container, visibility });
158 }
159 }
160
161 let path = name_ref.value.syntax().ancestors().find_map(ast::Path::cast)?;
162 let resolved = analyzer.resolve_path(db, &path)?;
163 match resolved {
164 PathResolution::Def(def) => Some(from_module_def(db, def, Some(container))),
165 PathResolution::AssocItem(item) => Some(from_assoc_item(db, item)),
166 PathResolution::Local(local) => {
167 let container = local.module(db);
168 let kind = NameKind::Local(local);
169 Some(NameDefinition { kind, container, visibility: None })
170 }
171 PathResolution::GenericParam(par) => {
172 // FIXME: get generic param def
173 let kind = NameKind::GenericParam(par);
174 Some(NameDefinition { kind, container, visibility })
175 }
176 PathResolution::Macro(def) => {
177 let kind = NameKind::Macro(def);
178 Some(NameDefinition { kind, container, visibility })
179 }
180 PathResolution::SelfType(impl_block) => {
181 let kind = NameKind::SelfType(impl_block);
182 let container = impl_block.module(db);
183 Some(NameDefinition { kind, container, visibility })
184 }
185 }
186}
diff --git a/crates/ra_ide/src/references/name_definition.rs b/crates/ra_ide/src/references/name_definition.rs
new file mode 100644
index 000000000..10d3a2364
--- /dev/null
+++ b/crates/ra_ide/src/references/name_definition.rs
@@ -0,0 +1,83 @@
1//! `NameDefinition` keeps information about the element we want to search references for.
2//! The element is represented by `NameKind`. It's located inside some `container` and
3//! has a `visibility`, which defines a search scope.
4//! Note that the reference search is possible for not all of the classified items.
5
6use hir::{
7 Adt, AssocItem, GenericParam, HasSource, ImplBlock, Local, MacroDef, Module, ModuleDef,
8 StructField, VariantDef,
9};
10use ra_syntax::{ast, ast::VisibilityOwner};
11
12use crate::db::RootDatabase;
13
14#[derive(Debug, PartialEq, Eq)]
15pub enum NameKind {
16 Macro(MacroDef),
17 Field(StructField),
18 AssocItem(AssocItem),
19 Def(ModuleDef),
20 SelfType(ImplBlock),
21 Local(Local),
22 GenericParam(GenericParam),
23}
24
25#[derive(PartialEq, Eq)]
26pub(crate) struct NameDefinition {
27 pub visibility: Option<ast::Visibility>,
28 pub container: Module,
29 pub kind: NameKind,
30}
31
32pub(super) fn from_assoc_item(db: &RootDatabase, item: AssocItem) -> NameDefinition {
33 let container = item.module(db);
34 let visibility = match item {
35 AssocItem::Function(f) => f.source(db).value.visibility(),
36 AssocItem::Const(c) => c.source(db).value.visibility(),
37 AssocItem::TypeAlias(a) => a.source(db).value.visibility(),
38 };
39 let kind = NameKind::AssocItem(item);
40 NameDefinition { kind, container, visibility }
41}
42
43pub(super) fn from_struct_field(db: &RootDatabase, field: StructField) -> NameDefinition {
44 let kind = NameKind::Field(field);
45 let parent = field.parent_def(db);
46 let container = parent.module(db);
47 let visibility = match parent {
48 VariantDef::Struct(s) => s.source(db).value.visibility(),
49 VariantDef::Union(e) => e.source(db).value.visibility(),
50 VariantDef::EnumVariant(e) => e.source(db).value.parent_enum().visibility(),
51 };
52 NameDefinition { kind, container, visibility }
53}
54
55pub(super) fn from_module_def(
56 db: &RootDatabase,
57 def: ModuleDef,
58 module: Option<Module>,
59) -> NameDefinition {
60 let kind = NameKind::Def(def);
61 let (container, visibility) = match def {
62 ModuleDef::Module(it) => {
63 let container = it.parent(db).or_else(|| Some(it)).unwrap();
64 let visibility = it.declaration_source(db).and_then(|s| s.value.visibility());
65 (container, visibility)
66 }
67 ModuleDef::EnumVariant(it) => {
68 let container = it.module(db);
69 let visibility = it.source(db).value.parent_enum().visibility();
70 (container, visibility)
71 }
72 ModuleDef::Function(it) => (it.module(db), it.source(db).value.visibility()),
73 ModuleDef::Const(it) => (it.module(db), it.source(db).value.visibility()),
74 ModuleDef::Static(it) => (it.module(db), it.source(db).value.visibility()),
75 ModuleDef::Trait(it) => (it.module(db), it.source(db).value.visibility()),
76 ModuleDef::TypeAlias(it) => (it.module(db), it.source(db).value.visibility()),
77 ModuleDef::Adt(Adt::Struct(it)) => (it.module(db), it.source(db).value.visibility()),
78 ModuleDef::Adt(Adt::Union(it)) => (it.module(db), it.source(db).value.visibility()),
79 ModuleDef::Adt(Adt::Enum(it)) => (it.module(db), it.source(db).value.visibility()),
80 ModuleDef::BuiltinType(..) => (module.unwrap(), None),
81 };
82 NameDefinition { kind, container, visibility }
83}
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
new file mode 100644
index 000000000..d58496049
--- /dev/null
+++ b/crates/ra_ide/src/references/rename.rs
@@ -0,0 +1,328 @@
1//! FIXME: write short doc here
2
3use hir::ModuleSource;
4use ra_db::{RelativePath, RelativePathBuf, SourceDatabase, SourceDatabaseExt};
5use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SyntaxNode};
6use ra_text_edit::TextEdit;
7
8use crate::{
9 db::RootDatabase, FileId, FilePosition, FileSystemEdit, RangeInfo, SourceChange,
10 SourceFileEdit, TextRange,
11};
12
13use super::find_all_refs;
14
15pub(crate) fn rename(
16 db: &RootDatabase,
17 position: FilePosition,
18 new_name: &str,
19) -> Option<RangeInfo<SourceChange>> {
20 let parse = db.parse(position.file_id);
21 if let Some((ast_name, ast_module)) =
22 find_name_and_module_at_offset(parse.tree().syntax(), position)
23 {
24 let range = ast_name.syntax().text_range();
25 rename_mod(db, &ast_name, &ast_module, position, new_name)
26 .map(|info| RangeInfo::new(range, info))
27 } else {
28 rename_reference(db, position, new_name)
29 }
30}
31
32fn find_name_and_module_at_offset(
33 syntax: &SyntaxNode,
34 position: FilePosition,
35) -> Option<(ast::Name, ast::Module)> {
36 let ast_name = find_node_at_offset::<ast::Name>(syntax, position.offset)?;
37 let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?;
38 Some((ast_name, ast_module))
39}
40
41fn source_edit_from_file_id_range(
42 file_id: FileId,
43 range: TextRange,
44 new_name: &str,
45) -> SourceFileEdit {
46 SourceFileEdit { file_id, edit: TextEdit::replace(range, new_name.into()) }
47}
48
49fn rename_mod(
50 db: &RootDatabase,
51 ast_name: &ast::Name,
52 ast_module: &ast::Module,
53 position: FilePosition,
54 new_name: &str,
55) -> Option<SourceChange> {
56 let mut source_file_edits = Vec::new();
57 let mut file_system_edits = Vec::new();
58 let module_src = hir::Source { file_id: position.file_id.into(), value: ast_module.clone() };
59 if let Some(module) = hir::Module::from_declaration(db, module_src) {
60 let src = module.definition_source(db);
61 let file_id = src.file_id.original_file(db);
62 match src.value {
63 ModuleSource::SourceFile(..) => {
64 let mod_path: RelativePathBuf = db.file_relative_path(file_id);
65 // mod is defined in path/to/dir/mod.rs
66 let dst_path = if mod_path.file_stem() == Some("mod") {
67 mod_path
68 .parent()
69 .and_then(|p| p.parent())
70 .or_else(|| Some(RelativePath::new("")))
71 .map(|p| p.join(new_name).join("mod.rs"))
72 } else {
73 Some(mod_path.with_file_name(new_name).with_extension("rs"))
74 };
75 if let Some(path) = dst_path {
76 let move_file = FileSystemEdit::MoveFile {
77 src: file_id,
78 dst_source_root: db.file_source_root(position.file_id),
79 dst_path: path,
80 };
81 file_system_edits.push(move_file);
82 }
83 }
84 ModuleSource::Module(..) => {}
85 }
86 }
87
88 let edit = SourceFileEdit {
89 file_id: position.file_id,
90 edit: TextEdit::replace(ast_name.syntax().text_range(), new_name.into()),
91 };
92 source_file_edits.push(edit);
93
94 Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits))
95}
96
97fn rename_reference(
98 db: &RootDatabase,
99 position: FilePosition,
100 new_name: &str,
101) -> Option<RangeInfo<SourceChange>> {
102 let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?;
103
104 let edit = refs
105 .into_iter()
106 .map(|range| source_edit_from_file_id_range(range.file_id, range.range, new_name))
107 .collect::<Vec<_>>();
108
109 if edit.is_empty() {
110 return None;
111 }
112
113 Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit)))
114}
115
116#[cfg(test)]
117mod tests {
118 use insta::assert_debug_snapshot;
119 use ra_text_edit::TextEditBuilder;
120 use test_utils::assert_eq_text;
121
122 use crate::{
123 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId,
124 };
125
126 #[test]
127 fn test_rename_for_local() {
128 test_rename(
129 r#"
130 fn main() {
131 let mut i = 1;
132 let j = 1;
133 i = i<|> + j;
134
135 {
136 i = 0;
137 }
138
139 i = 5;
140 }"#,
141 "k",
142 r#"
143 fn main() {
144 let mut k = 1;
145 let j = 1;
146 k = k + j;
147
148 {
149 k = 0;
150 }
151
152 k = 5;
153 }"#,
154 );
155 }
156
157 #[test]
158 fn test_rename_for_param_inside() {
159 test_rename(
160 r#"
161 fn foo(i : u32) -> u32 {
162 i<|>
163 }"#,
164 "j",
165 r#"
166 fn foo(j : u32) -> u32 {
167 j
168 }"#,
169 );
170 }
171
172 #[test]
173 fn test_rename_refs_for_fn_param() {
174 test_rename(
175 r#"
176 fn foo(i<|> : u32) -> u32 {
177 i
178 }"#,
179 "new_name",
180 r#"
181 fn foo(new_name : u32) -> u32 {
182 new_name
183 }"#,
184 );
185 }
186
187 #[test]
188 fn test_rename_for_mut_param() {
189 test_rename(
190 r#"
191 fn foo(mut i<|> : u32) -> u32 {
192 i
193 }"#,
194 "new_name",
195 r#"
196 fn foo(mut new_name : u32) -> u32 {
197 new_name
198 }"#,
199 );
200 }
201
202 #[test]
203 fn test_rename_mod() {
204 let (analysis, position) = analysis_and_position(
205 "
206 //- /lib.rs
207 mod bar;
208
209 //- /bar.rs
210 mod foo<|>;
211
212 //- /bar/foo.rs
213 // emtpy
214 ",
215 );
216 let new_name = "foo2";
217 let source_change = analysis.rename(position, new_name).unwrap();
218 assert_debug_snapshot!(&source_change,
219@r###"
220 Some(
221 RangeInfo {
222 range: [4; 7),
223 info: SourceChange {
224 label: "rename",
225 source_file_edits: [
226 SourceFileEdit {
227 file_id: FileId(
228 2,
229 ),
230 edit: TextEdit {
231 atoms: [
232 AtomTextEdit {
233 delete: [4; 7),
234 insert: "foo2",
235 },
236 ],
237 },
238 },
239 ],
240 file_system_edits: [
241 MoveFile {
242 src: FileId(
243 3,
244 ),
245 dst_source_root: SourceRootId(
246 0,
247 ),
248 dst_path: "bar/foo2.rs",
249 },
250 ],
251 cursor_position: None,
252 },
253 },
254 )
255 "###);
256 }
257
258 #[test]
259 fn test_rename_mod_in_dir() {
260 let (analysis, position) = analysis_and_position(
261 "
262 //- /lib.rs
263 mod fo<|>o;
264 //- /foo/mod.rs
265 // emtpy
266 ",
267 );
268 let new_name = "foo2";
269 let source_change = analysis.rename(position, new_name).unwrap();
270 assert_debug_snapshot!(&source_change,
271 @r###"
272 Some(
273 RangeInfo {
274 range: [4; 7),
275 info: SourceChange {
276 label: "rename",
277 source_file_edits: [
278 SourceFileEdit {
279 file_id: FileId(
280 1,
281 ),
282 edit: TextEdit {
283 atoms: [
284 AtomTextEdit {
285 delete: [4; 7),
286 insert: "foo2",
287 },
288 ],
289 },
290 },
291 ],
292 file_system_edits: [
293 MoveFile {
294 src: FileId(
295 2,
296 ),
297 dst_source_root: SourceRootId(
298 0,
299 ),
300 dst_path: "foo2/mod.rs",
301 },
302 ],
303 cursor_position: None,
304 },
305 },
306 )
307 "###
308 );
309 }
310
311 fn test_rename(text: &str, new_name: &str, expected: &str) {
312 let (analysis, position) = single_file_with_position(text);
313 let source_change = analysis.rename(position, new_name).unwrap();
314 let mut text_edit_builder = TextEditBuilder::default();
315 let mut file_id: Option<FileId> = None;
316 if let Some(change) = source_change {
317 for edit in change.info.source_file_edits {
318 file_id = Some(edit.file_id);
319 for atom in edit.edit.as_atoms() {
320 text_edit_builder.replace(atom.delete, atom.insert.clone());
321 }
322 }
323 }
324 let result =
325 text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap());
326 assert_eq_text!(expected, &*result);
327 }
328}
diff --git a/crates/ra_ide/src/references/search_scope.rs b/crates/ra_ide/src/references/search_scope.rs
new file mode 100644
index 000000000..f5c9589f4
--- /dev/null
+++ b/crates/ra_ide/src/references/search_scope.rs
@@ -0,0 +1,145 @@
1//! Generally, `search_scope` returns files that might contain references for the element.
2//! For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates.
3//! In some cases, the location of the references is known to within a `TextRange`,
4//! e.g. for things like local variables.
5use std::mem;
6
7use hir::{DefWithBody, HasSource, ModuleSource};
8use ra_db::{FileId, SourceDatabase, SourceDatabaseExt};
9use ra_prof::profile;
10use ra_syntax::{AstNode, TextRange};
11use rustc_hash::FxHashMap;
12
13use crate::db::RootDatabase;
14
15use super::{NameDefinition, NameKind};
16
17pub struct SearchScope {
18 entries: FxHashMap<FileId, Option<TextRange>>,
19}
20
21impl SearchScope {
22 fn new(entries: FxHashMap<FileId, Option<TextRange>>) -> SearchScope {
23 SearchScope { entries }
24 }
25 pub fn single_file(file: FileId) -> SearchScope {
26 SearchScope::new(std::iter::once((file, None)).collect())
27 }
28 pub(crate) fn intersection(&self, other: &SearchScope) -> SearchScope {
29 let (mut small, mut large) = (&self.entries, &other.entries);
30 if small.len() > large.len() {
31 mem::swap(&mut small, &mut large)
32 }
33
34 let res = small
35 .iter()
36 .filter_map(|(file_id, r1)| {
37 let r2 = large.get(file_id)?;
38 let r = intersect_ranges(*r1, *r2)?;
39 Some((*file_id, r))
40 })
41 .collect();
42 return SearchScope::new(res);
43
44 fn intersect_ranges(
45 r1: Option<TextRange>,
46 r2: Option<TextRange>,
47 ) -> Option<Option<TextRange>> {
48 match (r1, r2) {
49 (None, r) | (r, None) => Some(r),
50 (Some(r1), Some(r2)) => {
51 let r = r1.intersection(&r2)?;
52 Some(Some(r))
53 }
54 }
55 }
56 }
57}
58
59impl IntoIterator for SearchScope {
60 type Item = (FileId, Option<TextRange>);
61 type IntoIter = std::collections::hash_map::IntoIter<FileId, Option<TextRange>>;
62 fn into_iter(self) -> Self::IntoIter {
63 self.entries.into_iter()
64 }
65}
66
67impl NameDefinition {
68 pub(crate) fn search_scope(&self, db: &RootDatabase) -> SearchScope {
69 let _p = profile("search_scope");
70
71 let module_src = self.container.definition_source(db);
72 let file_id = module_src.file_id.original_file(db);
73
74 if let NameKind::Local(var) = self.kind {
75 let range = match var.parent(db) {
76 DefWithBody::Function(f) => f.source(db).value.syntax().text_range(),
77 DefWithBody::Const(c) => c.source(db).value.syntax().text_range(),
78 DefWithBody::Static(s) => s.source(db).value.syntax().text_range(),
79 };
80 let mut res = FxHashMap::default();
81 res.insert(file_id, Some(range));
82 return SearchScope::new(res);
83 }
84
85 let vis =
86 self.visibility.as_ref().map(|v| v.syntax().to_string()).unwrap_or("".to_string());
87
88 if vis.as_str() == "pub(super)" {
89 if let Some(parent_module) = self.container.parent(db) {
90 let mut res = FxHashMap::default();
91 let parent_src = parent_module.definition_source(db);
92 let file_id = parent_src.file_id.original_file(db);
93
94 match parent_src.value {
95 ModuleSource::Module(m) => {
96 let range = Some(m.syntax().text_range());
97 res.insert(file_id, range);
98 }
99 ModuleSource::SourceFile(_) => {
100 res.insert(file_id, None);
101 res.extend(parent_module.children(db).map(|m| {
102 let src = m.definition_source(db);
103 (src.file_id.original_file(db), None)
104 }));
105 }
106 }
107 return SearchScope::new(res);
108 }
109 }
110
111 if vis.as_str() != "" {
112 let source_root_id = db.file_source_root(file_id);
113 let source_root = db.source_root(source_root_id);
114 let mut res = source_root.walk().map(|id| (id, None)).collect::<FxHashMap<_, _>>();
115
116 // FIXME: add "pub(in path)"
117
118 if vis.as_str() == "pub(crate)" {
119 return SearchScope::new(res);
120 }
121 if vis.as_str() == "pub" {
122 let krate = self.container.krate();
123 let crate_graph = db.crate_graph();
124 for crate_id in crate_graph.iter() {
125 let mut crate_deps = crate_graph.dependencies(crate_id);
126 if crate_deps.any(|dep| dep.crate_id() == krate.crate_id()) {
127 let root_file = crate_graph.crate_root(crate_id);
128 let source_root_id = db.file_source_root(root_file);
129 let source_root = db.source_root(source_root_id);
130 res.extend(source_root.walk().map(|id| (id, None)));
131 }
132 }
133 return SearchScope::new(res);
134 }
135 }
136
137 let mut res = FxHashMap::default();
138 let range = match module_src.value {
139 ModuleSource::Module(m) => Some(m.syntax().text_range()),
140 ModuleSource::SourceFile(_) => None,
141 };
142 res.insert(file_id, range);
143 SearchScope::new(res)
144 }
145}
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
new file mode 100644
index 000000000..8039a5164
--- /dev/null
+++ b/crates/ra_ide/src/runnables.rs
@@ -0,0 +1,242 @@
1//! FIXME: write short doc here
2
3use hir::Source;
4use itertools::Itertools;
5use ra_db::SourceDatabase;
6use ra_syntax::{
7 ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner},
8 match_ast, SyntaxNode, TextRange,
9};
10
11use crate::{db::RootDatabase, FileId};
12
13#[derive(Debug)]
14pub struct Runnable {
15 pub range: TextRange,
16 pub kind: RunnableKind,
17}
18
19#[derive(Debug)]
20pub enum RunnableKind {
21 Test { name: String },
22 TestMod { path: String },
23 Bench { name: String },
24 Bin,
25}
26
27pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
28 let parse = db.parse(file_id);
29 parse.tree().syntax().descendants().filter_map(|i| runnable(db, file_id, i)).collect()
30}
31
32fn runnable(db: &RootDatabase, file_id: FileId, item: SyntaxNode) -> Option<Runnable> {
33 match_ast! {
34 match item {
35 ast::FnDef(it) => { runnable_fn(it) },
36 ast::Module(it) => { runnable_mod(db, file_id, it) },
37 _ => { None },
38 }
39 }
40}
41
42fn runnable_fn(fn_def: ast::FnDef) -> Option<Runnable> {
43 let name = fn_def.name()?.text().clone();
44 let kind = if name == "main" {
45 RunnableKind::Bin
46 } else if fn_def.has_atom_attr("test") {
47 RunnableKind::Test { name: name.to_string() }
48 } else if fn_def.has_atom_attr("bench") {
49 RunnableKind::Bench { name: name.to_string() }
50 } else {
51 return None;
52 };
53 Some(Runnable { range: fn_def.syntax().text_range(), kind })
54}
55
56fn runnable_mod(db: &RootDatabase, file_id: FileId, module: ast::Module) -> Option<Runnable> {
57 let has_test_function = module
58 .item_list()?
59 .items()
60 .filter_map(|it| match it {
61 ast::ModuleItem::FnDef(it) => Some(it),
62 _ => None,
63 })
64 .any(|f| f.has_atom_attr("test"));
65 if !has_test_function {
66 return None;
67 }
68 let range = module.syntax().text_range();
69 let src = hir::ModuleSource::from_child_node(db, Source::new(file_id.into(), &module.syntax()));
70 let module = hir::Module::from_definition(db, Source::new(file_id.into(), src))?;
71
72 let path = module.path_to_root(db).into_iter().rev().filter_map(|it| it.name(db)).join("::");
73 Some(Runnable { range, kind: RunnableKind::TestMod { path } })
74}
75
76#[cfg(test)]
77mod tests {
78 use insta::assert_debug_snapshot;
79
80 use crate::mock_analysis::analysis_and_position;
81
82 #[test]
83 fn test_runnables() {
84 let (analysis, pos) = analysis_and_position(
85 r#"
86 //- /lib.rs
87 <|> //empty
88 fn main() {}
89
90 #[test]
91 fn test_foo() {}
92
93 #[test]
94 #[ignore]
95 fn test_foo() {}
96 "#,
97 );
98 let runnables = analysis.runnables(pos.file_id).unwrap();
99 assert_debug_snapshot!(&runnables,
100 @r###"
101 [
102 Runnable {
103 range: [1; 21),
104 kind: Bin,
105 },
106 Runnable {
107 range: [22; 46),
108 kind: Test {
109 name: "test_foo",
110 },
111 },
112 Runnable {
113 range: [47; 81),
114 kind: Test {
115 name: "test_foo",
116 },
117 },
118 ]
119 "###
120 );
121 }
122
123 #[test]
124 fn test_runnables_module() {
125 let (analysis, pos) = analysis_and_position(
126 r#"
127 //- /lib.rs
128 <|> //empty
129 mod test_mod {
130 #[test]
131 fn test_foo1() {}
132 }
133 "#,
134 );
135 let runnables = analysis.runnables(pos.file_id).unwrap();
136 assert_debug_snapshot!(&runnables,
137 @r###"
138 [
139 Runnable {
140 range: [1; 59),
141 kind: TestMod {
142 path: "test_mod",
143 },
144 },
145 Runnable {
146 range: [28; 57),
147 kind: Test {
148 name: "test_foo1",
149 },
150 },
151 ]
152 "###
153 );
154 }
155
156 #[test]
157 fn test_runnables_one_depth_layer_module() {
158 let (analysis, pos) = analysis_and_position(
159 r#"
160 //- /lib.rs
161 <|> //empty
162 mod foo {
163 mod test_mod {
164 #[test]
165 fn test_foo1() {}
166 }
167 }
168 "#,
169 );
170 let runnables = analysis.runnables(pos.file_id).unwrap();
171 assert_debug_snapshot!(&runnables,
172 @r###"
173 [
174 Runnable {
175 range: [23; 85),
176 kind: TestMod {
177 path: "foo::test_mod",
178 },
179 },
180 Runnable {
181 range: [46; 79),
182 kind: Test {
183 name: "test_foo1",
184 },
185 },
186 ]
187 "###
188 );
189 }
190
191 #[test]
192 fn test_runnables_multiple_depth_module() {
193 let (analysis, pos) = analysis_and_position(
194 r#"
195 //- /lib.rs
196 <|> //empty
197 mod foo {
198 mod bar {
199 mod test_mod {
200 #[test]
201 fn test_foo1() {}
202 }
203 }
204 }
205 "#,
206 );
207 let runnables = analysis.runnables(pos.file_id).unwrap();
208 assert_debug_snapshot!(&runnables,
209 @r###"
210 [
211 Runnable {
212 range: [41; 115),
213 kind: TestMod {
214 path: "foo::bar::test_mod",
215 },
216 },
217 Runnable {
218 range: [68; 105),
219 kind: Test {
220 name: "test_foo1",
221 },
222 },
223 ]
224 "###
225 );
226 }
227
228 #[test]
229 fn test_runnables_no_test_function_in_module() {
230 let (analysis, pos) = analysis_and_position(
231 r#"
232 //- /lib.rs
233 <|> //empty
234 mod test_mod {
235 fn foo1() {}
236 }
237 "#,
238 );
239 let runnables = analysis.runnables(pos.file_id).unwrap();
240 assert!(runnables.is_empty())
241 }
242}
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html
new file mode 100644
index 000000000..b39c4d371
--- /dev/null
+++ b/crates/ra_ide/src/snapshots/highlighting.html
@@ -0,0 +1,48 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.comment { color: #7F9F7F; }
7.string { color: #CC9393; }
8.function { color: #93E0E3; }
9.parameter { color: #94BFF3; }
10.builtin { color: #DD6718; }
11.text { color: #DCDCCC; }
12.attribute { color: #94BFF3; }
13.literal { color: #BFEBBF; }
14.macro { color: #94BFF3; }
15.variable { color: #DCDCCC; }
16.variable\.mut { color: #DCDCCC; text-decoration: underline; }
17
18.keyword { color: #F0DFAF; }
19.keyword\.unsafe { color: #DFAF8F; }
20.keyword\.control { color: #F0DFAF; font-weight: bold; }
21</style>
22<pre><code><span class="attribute">#</span><span class="attribute">[</span><span class="attribute">derive</span><span class="attribute">(</span><span class="attribute">Clone</span><span class="attribute">,</span><span class="attribute"> </span><span class="attribute">Debug</span><span class="attribute">)</span><span class="attribute">]</span>
23<span class="keyword">struct</span> <span class="type">Foo</span> {
24 <span class="keyword">pub</span> <span class="field">x</span>: <span class="type">i32</span>,
25 <span class="keyword">pub</span> <span class="field">y</span>: <span class="type">i32</span>,
26}
27
28<span class="keyword">fn</span> <span class="function">foo</span>&lt;<span class="type">T</span>&gt;() -&gt; <span class="type">T</span> {
29 <span class="macro">unimplemented</span><span class="macro">!</span>();
30 <span class="function">foo</span>::&lt;<span class="type">i32</span>&gt;();
31}
32
33<span class="comment">// comment</span>
34<span class="keyword">fn</span> <span class="function">main</span>() {
35 <span class="macro">println</span><span class="macro">!</span>(<span class="string">"Hello, {}!"</span>, <span class="literal">92</span>);
36
37 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable.mut">vec</span> = <span class="text">Vec</span>::<span class="text">new</span>();
38 <span class="keyword.control">if</span> <span class="keyword">true</span> {
39 <span class="variable.mut">vec</span>.<span class="text">push</span>(<span class="type">Foo</span> { <span class="field">x</span>: <span class="literal">0</span>, <span class="field">y</span>: <span class="literal">1</span> });
40 }
41 <span class="keyword.unsafe">unsafe</span> { <span class="variable.mut">vec</span>.<span class="text">set_len</span>(<span class="literal">0</span>); }
42
43 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable.mut">x</span> = <span class="literal">42</span>;
44 <span class="keyword">let</span> <span class="variable.mut">y</span> = &<span class="keyword">mut</span> <span class="variable.mut">x</span>;
45 <span class="keyword">let</span> <span class="variable">z</span> = &<span class="variable.mut">y</span>;
46
47 <span class="variable.mut">y</span>;
48}</code></pre> \ No newline at end of file
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
new file mode 100644
index 000000000..79f11ea80
--- /dev/null
+++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
@@ -0,0 +1,33 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.comment { color: #7F9F7F; }
7.string { color: #CC9393; }
8.function { color: #93E0E3; }
9.parameter { color: #94BFF3; }
10.builtin { color: #DD6718; }
11.text { color: #DCDCCC; }
12.attribute { color: #94BFF3; }
13.literal { color: #BFEBBF; }
14.macro { color: #94BFF3; }
15.variable { color: #DCDCCC; }
16.variable\.mut { color: #DCDCCC; text-decoration: underline; }
17
18.keyword { color: #F0DFAF; }
19.keyword\.unsafe { color: #DFAF8F; }
20.keyword\.control { color: #F0DFAF; font-weight: bold; }
21</style>
22<pre><code><span class="keyword">fn</span> <span class="function">main</span>() {
23 <span class="keyword">let</span> <span class="variable" data-binding-hash="8723171760279909834" style="color: hsl(307,91%,75%);">hello</span> = <span class="string">"hello"</span>;
24 <span class="keyword">let</span> <span class="variable" data-binding-hash="14702933417323009544" style="color: hsl(108,90%,49%);">x</span> = <span class="variable" data-binding-hash="8723171760279909834" style="color: hsl(307,91%,75%);">hello</span>.<span class="text">to_string</span>();
25 <span class="keyword">let</span> <span class="variable" data-binding-hash="5443150872754369068" style="color: hsl(215,43%,43%);">y</span> = <span class="variable" data-binding-hash="8723171760279909834" style="color: hsl(307,91%,75%);">hello</span>.<span class="text">to_string</span>();
26
27 <span class="keyword">let</span> <span class="variable" data-binding-hash="17358108296605513516" style="color: hsl(331,46%,60%);">x</span> = <span class="string">"other color please!"</span>;
28 <span class="keyword">let</span> <span class="variable" data-binding-hash="2073121142529774969" style="color: hsl(320,43%,74%);">y</span> = <span class="variable" data-binding-hash="17358108296605513516" style="color: hsl(331,46%,60%);">x</span>.<span class="text">to_string</span>();
29}
30
31<span class="keyword">fn</span> <span class="function">bar</span>() {
32 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable.mut" data-binding-hash="8723171760279909834" style="color: hsl(307,91%,75%);">hello</span> = <span class="string">"hello"</span>;
33}</code></pre> \ No newline at end of file
diff --git a/crates/ra_ide/src/source_change.rs b/crates/ra_ide/src/source_change.rs
new file mode 100644
index 000000000..f5f7f8807
--- /dev/null
+++ b/crates/ra_ide/src/source_change.rs
@@ -0,0 +1,119 @@
1//! This modules defines type to represent changes to the source code, that flow
2//! from the server to the client.
3//!
4//! It can be viewed as a dual for `AnalysisChange`.
5
6use ra_db::RelativePathBuf;
7use ra_text_edit::TextEdit;
8
9use crate::{FileId, FilePosition, SourceRootId, TextUnit};
10
11#[derive(Debug)]
12pub struct SourceChange {
13 pub label: String,
14 pub source_file_edits: Vec<SourceFileEdit>,
15 pub file_system_edits: Vec<FileSystemEdit>,
16 pub cursor_position: Option<FilePosition>,
17}
18
19impl SourceChange {
20 /// Creates a new SourceChange with the given label
21 /// from the edits.
22 pub(crate) fn from_edits<L: Into<String>>(
23 label: L,
24 source_file_edits: Vec<SourceFileEdit>,
25 file_system_edits: Vec<FileSystemEdit>,
26 ) -> Self {
27 SourceChange {
28 label: label.into(),
29 source_file_edits,
30 file_system_edits,
31 cursor_position: None,
32 }
33 }
34
35 /// Creates a new SourceChange with the given label,
36 /// containing only the given `SourceFileEdits`.
37 pub(crate) fn source_file_edits<L: Into<String>>(label: L, edits: Vec<SourceFileEdit>) -> Self {
38 SourceChange {
39 label: label.into(),
40 source_file_edits: edits,
41 file_system_edits: vec![],
42 cursor_position: None,
43 }
44 }
45
46 /// Creates a new SourceChange with the given label,
47 /// containing only the given `FileSystemEdits`.
48 pub(crate) fn file_system_edits<L: Into<String>>(label: L, edits: Vec<FileSystemEdit>) -> Self {
49 SourceChange {
50 label: label.into(),
51 source_file_edits: vec![],
52 file_system_edits: edits,
53 cursor_position: None,
54 }
55 }
56
57 /// Creates a new SourceChange with the given label,
58 /// containing only a single `SourceFileEdit`.
59 pub(crate) fn source_file_edit<L: Into<String>>(label: L, edit: SourceFileEdit) -> Self {
60 SourceChange::source_file_edits(label, vec![edit])
61 }
62
63 /// Creates a new SourceChange with the given label
64 /// from the given `FileId` and `TextEdit`
65 pub(crate) fn source_file_edit_from<L: Into<String>>(
66 label: L,
67 file_id: FileId,
68 edit: TextEdit,
69 ) -> Self {
70 SourceChange::source_file_edit(label, SourceFileEdit { file_id, edit })
71 }
72
73 /// Creates a new SourceChange with the given label
74 /// from the given `FileId` and `TextEdit`
75 pub(crate) fn file_system_edit<L: Into<String>>(label: L, edit: FileSystemEdit) -> Self {
76 SourceChange::file_system_edits(label, vec![edit])
77 }
78
79 /// Sets the cursor position to the given `FilePosition`
80 pub(crate) fn with_cursor(mut self, cursor_position: FilePosition) -> Self {
81 self.cursor_position = Some(cursor_position);
82 self
83 }
84
85 /// Sets the cursor position to the given `FilePosition`
86 pub(crate) fn with_cursor_opt(mut self, cursor_position: Option<FilePosition>) -> Self {
87 self.cursor_position = cursor_position;
88 self
89 }
90}
91
92#[derive(Debug)]
93pub struct SourceFileEdit {
94 pub file_id: FileId,
95 pub edit: TextEdit,
96}
97
98#[derive(Debug)]
99pub enum FileSystemEdit {
100 CreateFile { source_root: SourceRootId, path: RelativePathBuf },
101 MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf },
102}
103
104pub(crate) struct SingleFileChange {
105 pub label: String,
106 pub edit: TextEdit,
107 pub cursor_position: Option<TextUnit>,
108}
109
110impl SingleFileChange {
111 pub(crate) fn into_source_change(self, file_id: FileId) -> SourceChange {
112 SourceChange {
113 label: self.label,
114 source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }],
115 file_system_edits: Vec::new(),
116 cursor_position: self.cursor_position.map(|offset| FilePosition { file_id, offset }),
117 }
118 }
119}
diff --git a/crates/ra_ide/src/status.rs b/crates/ra_ide/src/status.rs
new file mode 100644
index 000000000..1bb27eb85
--- /dev/null
+++ b/crates/ra_ide/src/status.rs
@@ -0,0 +1,136 @@
1//! FIXME: write short doc here
2
3use std::{fmt, iter::FromIterator, sync::Arc};
4
5use hir::MacroFile;
6use ra_db::{
7 salsa::{
8 debug::{DebugQueryTable, TableEntry},
9 Database,
10 },
11 FileTextQuery, SourceRootId,
12};
13use ra_prof::{memory_usage, Bytes};
14use ra_syntax::{ast, Parse, SyntaxNode};
15
16use crate::{
17 db::RootDatabase,
18 symbol_index::{LibrarySymbolsQuery, SymbolIndex},
19 FileId,
20};
21
22fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
23 db.query(ra_db::ParseQuery).entries::<SyntaxTreeStats>()
24}
25fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
26 db.query(hir::db::ParseMacroQuery).entries::<SyntaxTreeStats>()
27}
28
29pub(crate) fn status(db: &RootDatabase) -> String {
30 let files_stats = db.query(FileTextQuery).entries::<FilesStats>();
31 let syntax_tree_stats = syntax_tree_stats(db);
32 let macro_syntax_tree_stats = macro_syntax_tree_stats(db);
33 let symbols_stats = db.query(LibrarySymbolsQuery).entries::<LibrarySymbolsStats>();
34 format!(
35 "{}\n{}\n{}\n{} (macros)\n\n\nmemory:\n{}\ngc {:?} seconds ago",
36 files_stats,
37 symbols_stats,
38 syntax_tree_stats,
39 macro_syntax_tree_stats,
40 memory_usage(),
41 db.last_gc.elapsed().as_secs(),
42 )
43}
44
45#[derive(Default)]
46struct FilesStats {
47 total: usize,
48 size: Bytes,
49}
50
51impl fmt::Display for FilesStats {
52 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
53 write!(fmt, "{} ({}) files", self.total, self.size)
54 }
55}
56
57impl FromIterator<TableEntry<FileId, Arc<String>>> for FilesStats {
58 fn from_iter<T>(iter: T) -> FilesStats
59 where
60 T: IntoIterator<Item = TableEntry<FileId, Arc<String>>>,
61 {
62 let mut res = FilesStats::default();
63 for entry in iter {
64 res.total += 1;
65 res.size += entry.value.unwrap().len();
66 }
67 res
68 }
69}
70
71#[derive(Default)]
72pub(crate) struct SyntaxTreeStats {
73 total: usize,
74 pub(crate) retained: usize,
75}
76
77impl fmt::Display for SyntaxTreeStats {
78 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
79 write!(fmt, "{} trees, {} retained", self.total, self.retained)
80 }
81}
82
83impl FromIterator<TableEntry<FileId, Parse<ast::SourceFile>>> for SyntaxTreeStats {
84 fn from_iter<T>(iter: T) -> SyntaxTreeStats
85 where
86 T: IntoIterator<Item = TableEntry<FileId, Parse<ast::SourceFile>>>,
87 {
88 let mut res = SyntaxTreeStats::default();
89 for entry in iter {
90 res.total += 1;
91 res.retained += entry.value.is_some() as usize;
92 }
93 res
94 }
95}
96
97impl<M> FromIterator<TableEntry<MacroFile, Option<(Parse<SyntaxNode>, M)>>> for SyntaxTreeStats {
98 fn from_iter<T>(iter: T) -> SyntaxTreeStats
99 where
100 T: IntoIterator<Item = TableEntry<MacroFile, Option<(Parse<SyntaxNode>, M)>>>,
101 {
102 let mut res = SyntaxTreeStats::default();
103 for entry in iter {
104 res.total += 1;
105 res.retained += entry.value.is_some() as usize;
106 }
107 res
108 }
109}
110
111#[derive(Default)]
112struct LibrarySymbolsStats {
113 total: usize,
114 size: Bytes,
115}
116
117impl fmt::Display for LibrarySymbolsStats {
118 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
119 write!(fmt, "{} ({}) symbols", self.total, self.size,)
120 }
121}
122
123impl FromIterator<TableEntry<SourceRootId, Arc<SymbolIndex>>> for LibrarySymbolsStats {
124 fn from_iter<T>(iter: T) -> LibrarySymbolsStats
125 where
126 T: IntoIterator<Item = TableEntry<SourceRootId, Arc<SymbolIndex>>>,
127 {
128 let mut res = LibrarySymbolsStats::default();
129 for entry in iter {
130 let value = entry.value.unwrap();
131 res.total += value.len();
132 res.size += value.memory_size();
133 }
134 res
135 }
136}
diff --git a/crates/ra_ide/src/symbol_index.rs b/crates/ra_ide/src/symbol_index.rs
new file mode 100644
index 000000000..5729eb5b3
--- /dev/null
+++ b/crates/ra_ide/src/symbol_index.rs
@@ -0,0 +1,405 @@
1//! This module handles fuzzy-searching of functions, structs and other symbols
2//! by name across the whole workspace and dependencies.
3//!
4//! It works by building an incrementally-updated text-search index of all
5//! symbols. The backbone of the index is the **awesome** `fst` crate by
6//! @BurntSushi.
7//!
8//! In a nutshell, you give a set of strings to `fst`, and it builds a
9//! finite state machine describing this set of strings. The strings which
10//! could fuzzy-match a pattern can also be described by a finite state machine.
11//! What is freaking cool is that you can now traverse both state machines in
12//! lock-step to enumerate the strings which are both in the input set and
13//! fuzz-match the query. Or, more formally, given two languages described by
14//! FSTs, one can build a product FST which describes the intersection of the
15//! languages.
16//!
17//! `fst` does not support cheap updating of the index, but it supports unioning
18//! of state machines. So, to account for changing source code, we build an FST
19//! for each library (which is assumed to never change) and an FST for each Rust
20//! file in the current workspace, and run a query against the union of all
21//! those FSTs.
22use std::{
23 fmt,
24 hash::{Hash, Hasher},
25 mem,
26 sync::Arc,
27};
28
29use fst::{self, Streamer};
30use ra_db::{
31 salsa::{self, ParallelDatabase},
32 SourceDatabaseExt, SourceRootId,
33};
34use ra_syntax::{
35 ast::{self, NameOwner},
36 match_ast, AstNode, Parse, SmolStr, SourceFile,
37 SyntaxKind::{self, *},
38 SyntaxNode, SyntaxNodePtr, TextRange, WalkEvent,
39};
40#[cfg(not(feature = "wasm"))]
41use rayon::prelude::*;
42
43use crate::{db::RootDatabase, FileId, Query};
44
45#[salsa::query_group(SymbolsDatabaseStorage)]
46pub(crate) trait SymbolsDatabase: hir::db::HirDatabase {
47 fn file_symbols(&self, file_id: FileId) -> Arc<SymbolIndex>;
48 #[salsa::input]
49 fn library_symbols(&self, id: SourceRootId) -> Arc<SymbolIndex>;
50 /// The set of "local" (that is, from the current workspace) roots.
51 /// Files in local roots are assumed to change frequently.
52 #[salsa::input]
53 fn local_roots(&self) -> Arc<Vec<SourceRootId>>;
54 /// The set of roots for crates.io libraries.
55 /// Files in libraries are assumed to never change.
56 #[salsa::input]
57 fn library_roots(&self) -> Arc<Vec<SourceRootId>>;
58}
59
60fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc<SymbolIndex> {
61 db.check_canceled();
62 let parse = db.parse(file_id);
63
64 let symbols = source_file_to_file_symbols(&parse.tree(), file_id);
65
66 // FIXME: add macros here
67
68 Arc::new(SymbolIndex::new(symbols))
69}
70
71pub(crate) fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> {
72 /// Need to wrap Snapshot to provide `Clone` impl for `map_with`
73 struct Snap(salsa::Snapshot<RootDatabase>);
74 impl Clone for Snap {
75 fn clone(&self) -> Snap {
76 Snap(self.0.snapshot())
77 }
78 }
79
80 let buf: Vec<Arc<SymbolIndex>> = if query.libs {
81 let snap = Snap(db.snapshot());
82 #[cfg(not(feature = "wasm"))]
83 let buf = db
84 .library_roots()
85 .par_iter()
86 .map_with(snap, |db, &lib_id| db.0.library_symbols(lib_id))
87 .collect();
88
89 #[cfg(feature = "wasm")]
90 let buf = db.library_roots().iter().map(|&lib_id| snap.0.library_symbols(lib_id)).collect();
91
92 buf
93 } else {
94 let mut files = Vec::new();
95 for &root in db.local_roots().iter() {
96 let sr = db.source_root(root);
97 files.extend(sr.walk())
98 }
99
100 let snap = Snap(db.snapshot());
101 #[cfg(not(feature = "wasm"))]
102 let buf =
103 files.par_iter().map_with(snap, |db, &file_id| db.0.file_symbols(file_id)).collect();
104
105 #[cfg(feature = "wasm")]
106 let buf = files.iter().map(|&file_id| snap.0.file_symbols(file_id)).collect();
107
108 buf
109 };
110 query.search(&buf)
111}
112
113pub(crate) fn index_resolve(db: &RootDatabase, name_ref: &ast::NameRef) -> Vec<FileSymbol> {
114 let name = name_ref.text();
115 let mut query = Query::new(name.to_string());
116 query.exact();
117 query.limit(4);
118 crate::symbol_index::world_symbols(db, query)
119}
120
121#[derive(Default)]
122pub(crate) struct SymbolIndex {
123 symbols: Vec<FileSymbol>,
124 map: fst::Map,
125}
126
127impl fmt::Debug for SymbolIndex {
128 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
129 f.debug_struct("SymbolIndex").field("n_symbols", &self.symbols.len()).finish()
130 }
131}
132
133impl PartialEq for SymbolIndex {
134 fn eq(&self, other: &SymbolIndex) -> bool {
135 self.symbols == other.symbols
136 }
137}
138
139impl Eq for SymbolIndex {}
140
141impl Hash for SymbolIndex {
142 fn hash<H: Hasher>(&self, hasher: &mut H) {
143 self.symbols.hash(hasher)
144 }
145}
146
147impl SymbolIndex {
148 fn new(mut symbols: Vec<FileSymbol>) -> SymbolIndex {
149 fn cmp_key<'a>(s1: &'a FileSymbol) -> impl Ord + 'a {
150 unicase::Ascii::new(s1.name.as_str())
151 }
152 #[cfg(not(feature = "wasm"))]
153 symbols.par_sort_by(|s1, s2| cmp_key(s1).cmp(&cmp_key(s2)));
154
155 #[cfg(feature = "wasm")]
156 symbols.sort_by(|s1, s2| cmp_key(s1).cmp(&cmp_key(s2)));
157
158 let mut builder = fst::MapBuilder::memory();
159
160 let mut last_batch_start = 0;
161
162 for idx in 0..symbols.len() {
163 if symbols.get(last_batch_start).map(cmp_key) == symbols.get(idx + 1).map(cmp_key) {
164 continue;
165 }
166
167 let start = last_batch_start;
168 let end = idx + 1;
169 last_batch_start = end;
170
171 let key = symbols[start].name.as_str().to_lowercase();
172 let value = SymbolIndex::range_to_map_value(start, end);
173
174 builder.insert(key, value).unwrap();
175 }
176
177 let map = fst::Map::from_bytes(builder.into_inner().unwrap()).unwrap();
178 SymbolIndex { symbols, map }
179 }
180
181 pub(crate) fn len(&self) -> usize {
182 self.symbols.len()
183 }
184
185 pub(crate) fn memory_size(&self) -> usize {
186 self.map.as_fst().size() + self.symbols.len() * mem::size_of::<FileSymbol>()
187 }
188
189 #[cfg(not(feature = "wasm"))]
190 pub(crate) fn for_files(
191 files: impl ParallelIterator<Item = (FileId, Parse<ast::SourceFile>)>,
192 ) -> SymbolIndex {
193 let symbols = files
194 .flat_map(|(file_id, file)| source_file_to_file_symbols(&file.tree(), file_id))
195 .collect::<Vec<_>>();
196 SymbolIndex::new(symbols)
197 }
198
199 #[cfg(feature = "wasm")]
200 pub(crate) fn for_files(
201 files: impl Iterator<Item = (FileId, Parse<ast::SourceFile>)>,
202 ) -> SymbolIndex {
203 let symbols = files
204 .flat_map(|(file_id, file)| source_file_to_file_symbols(&file.tree(), file_id))
205 .collect::<Vec<_>>();
206 SymbolIndex::new(symbols)
207 }
208
209 fn range_to_map_value(start: usize, end: usize) -> u64 {
210 debug_assert![start <= (std::u32::MAX as usize)];
211 debug_assert![end <= (std::u32::MAX as usize)];
212
213 ((start as u64) << 32) | end as u64
214 }
215
216 fn map_value_to_range(value: u64) -> (usize, usize) {
217 let end = value as u32 as usize;
218 let start = (value >> 32) as usize;
219 (start, end)
220 }
221}
222
223impl Query {
224 pub(crate) fn search(self, indices: &[Arc<SymbolIndex>]) -> Vec<FileSymbol> {
225 let mut op = fst::map::OpBuilder::new();
226 for file_symbols in indices.iter() {
227 let automaton = fst::automaton::Subsequence::new(&self.lowercased);
228 op = op.add(file_symbols.map.search(automaton))
229 }
230 let mut stream = op.union();
231 let mut res = Vec::new();
232 while let Some((_, indexed_values)) = stream.next() {
233 if res.len() >= self.limit {
234 break;
235 }
236 for indexed_value in indexed_values {
237 let symbol_index = &indices[indexed_value.index];
238 let (start, end) = SymbolIndex::map_value_to_range(indexed_value.value);
239
240 for symbol in &symbol_index.symbols[start..end] {
241 if self.only_types && !is_type(symbol.ptr.kind()) {
242 continue;
243 }
244 if self.exact && symbol.name != self.query {
245 continue;
246 }
247 res.push(symbol.clone());
248 }
249 }
250 }
251 res
252 }
253}
254
255fn is_type(kind: SyntaxKind) -> bool {
256 match kind {
257 STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => true,
258 _ => false,
259 }
260}
261
262/// The actual data that is stored in the index. It should be as compact as
263/// possible.
264#[derive(Debug, Clone, PartialEq, Eq, Hash)]
265pub(crate) struct FileSymbol {
266 pub(crate) file_id: FileId,
267 pub(crate) name: SmolStr,
268 pub(crate) ptr: SyntaxNodePtr,
269 pub(crate) name_range: Option<TextRange>,
270 pub(crate) container_name: Option<SmolStr>,
271}
272
273fn source_file_to_file_symbols(source_file: &SourceFile, file_id: FileId) -> Vec<FileSymbol> {
274 let mut symbols = Vec::new();
275 let mut stack = Vec::new();
276
277 for event in source_file.syntax().preorder() {
278 match event {
279 WalkEvent::Enter(node) => {
280 if let Some(mut symbol) = to_file_symbol(&node, file_id) {
281 symbol.container_name = stack.last().cloned();
282
283 stack.push(symbol.name.clone());
284 symbols.push(symbol);
285 }
286 }
287
288 WalkEvent::Leave(node) => {
289 if to_symbol(&node).is_some() {
290 stack.pop();
291 }
292 }
293 }
294 }
295
296 symbols
297}
298
299fn to_symbol(node: &SyntaxNode) -> Option<(SmolStr, SyntaxNodePtr, TextRange)> {
300 fn decl<N: NameOwner>(node: N) -> Option<(SmolStr, SyntaxNodePtr, TextRange)> {
301 let name = node.name()?;
302 let name_range = name.syntax().text_range();
303 let name = name.text().clone();
304 let ptr = SyntaxNodePtr::new(node.syntax());
305
306 Some((name, ptr, name_range))
307 }
308 match_ast! {
309 match node {
310 ast::FnDef(it) => { decl(it) },
311 ast::StructDef(it) => { decl(it) },
312 ast::EnumDef(it) => { decl(it) },
313 ast::TraitDef(it) => { decl(it) },
314 ast::Module(it) => { decl(it) },
315 ast::TypeAliasDef(it) => { decl(it) },
316 ast::ConstDef(it) => { decl(it) },
317 ast::StaticDef(it) => { decl(it) },
318 _ => None,
319 }
320 }
321}
322
323fn to_file_symbol(node: &SyntaxNode, file_id: FileId) -> Option<FileSymbol> {
324 to_symbol(node).map(move |(name, ptr, name_range)| FileSymbol {
325 name,
326 ptr,
327 file_id,
328 name_range: Some(name_range),
329 container_name: None,
330 })
331}
332
333#[cfg(test)]
334mod tests {
335 use crate::{display::NavigationTarget, mock_analysis::single_file, Query};
336 use ra_syntax::{
337 SmolStr,
338 SyntaxKind::{FN_DEF, STRUCT_DEF},
339 };
340
341 #[test]
342 fn test_world_symbols_with_no_container() {
343 let code = r#"
344 enum FooInner { }
345 "#;
346
347 let mut symbols = get_symbols_matching(code, "FooInner");
348
349 let s = symbols.pop().unwrap();
350
351 assert_eq!(s.name(), "FooInner");
352 assert!(s.container_name().is_none());
353 }
354
355 #[test]
356 fn test_world_symbols_include_container_name() {
357 let code = r#"
358fn foo() {
359 enum FooInner { }
360}
361 "#;
362
363 let mut symbols = get_symbols_matching(code, "FooInner");
364
365 let s = symbols.pop().unwrap();
366
367 assert_eq!(s.name(), "FooInner");
368 assert_eq!(s.container_name(), Some(&SmolStr::new("foo")));
369
370 let code = r#"
371mod foo {
372 struct FooInner;
373}
374 "#;
375
376 let mut symbols = get_symbols_matching(code, "FooInner");
377
378 let s = symbols.pop().unwrap();
379
380 assert_eq!(s.name(), "FooInner");
381 assert_eq!(s.container_name(), Some(&SmolStr::new("foo")));
382 }
383
384 #[test]
385 fn test_world_symbols_are_case_sensitive() {
386 let code = r#"
387fn foo() {}
388
389struct Foo;
390 "#;
391
392 let symbols = get_symbols_matching(code, "Foo");
393
394 let fn_match = symbols.iter().find(|s| s.name() == "foo").map(|s| s.kind());
395 let struct_match = symbols.iter().find(|s| s.name() == "Foo").map(|s| s.kind());
396
397 assert_eq!(fn_match, Some(FN_DEF));
398 assert_eq!(struct_match, Some(STRUCT_DEF));
399 }
400
401 fn get_symbols_matching(text: &str, query: &str) -> Vec<NavigationTarget> {
402 let (analysis, _) = single_file(text);
403 analysis.symbol_search(Query::new(query.into())).unwrap()
404 }
405}
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
new file mode 100644
index 000000000..2c568a747
--- /dev/null
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -0,0 +1,342 @@
1//! FIXME: write short doc here
2
3use rustc_hash::{FxHashMap, FxHashSet};
4
5use hir::{Name, Source};
6use ra_db::SourceDatabase;
7use ra_prof::profile;
8use ra_syntax::{ast, AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxKind::*, TextRange, T};
9
10use crate::{
11 db::RootDatabase,
12 references::{
13 classify_name, classify_name_ref,
14 NameKind::{self, *},
15 },
16 FileId,
17};
18
19#[derive(Debug)]
20pub struct HighlightedRange {
21 pub range: TextRange,
22 pub tag: &'static str,
23 pub binding_hash: Option<u64>,
24}
25
26fn is_control_keyword(kind: SyntaxKind) -> bool {
27 match kind {
28 T![for]
29 | T![loop]
30 | T![while]
31 | T![continue]
32 | T![break]
33 | T![if]
34 | T![else]
35 | T![match]
36 | T![return] => true,
37 _ => false,
38 }
39}
40
41pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRange> {
42 let _p = profile("highlight");
43 let parse = db.parse(file_id);
44 let root = parse.tree().syntax().clone();
45
46 fn calc_binding_hash(file_id: FileId, name: &Name, shadow_count: u32) -> u64 {
47 fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
48 use std::{collections::hash_map::DefaultHasher, hash::Hasher};
49
50 let mut hasher = DefaultHasher::new();
51 x.hash(&mut hasher);
52 hasher.finish()
53 }
54
55 hash((file_id, name, shadow_count))
56 }
57
58 // Visited nodes to handle highlighting priorities
59 // FIXME: retain only ranges here
60 let mut highlighted: FxHashSet<SyntaxElement> = FxHashSet::default();
61 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
62
63 let mut res = Vec::new();
64 for node in root.descendants_with_tokens() {
65 if highlighted.contains(&node) {
66 continue;
67 }
68 let mut binding_hash = None;
69 let tag = match node.kind() {
70 FN_DEF => {
71 bindings_shadow_count.clear();
72 continue;
73 }
74 COMMENT => "comment",
75 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => "string",
76 ATTR => "attribute",
77 NAME_REF => {
78 if node.ancestors().any(|it| it.kind() == ATTR) {
79 continue;
80 }
81
82 let name_ref = node.as_node().cloned().and_then(ast::NameRef::cast).unwrap();
83 let name_kind =
84 classify_name_ref(db, Source::new(file_id.into(), &name_ref)).map(|d| d.kind);
85
86 if let Some(Local(local)) = &name_kind {
87 if let Some(name) = local.name(db) {
88 let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
89 binding_hash = Some(calc_binding_hash(file_id, &name, *shadow_count))
90 }
91 };
92
93 name_kind.map_or("text", |it| highlight_name(db, it))
94 }
95 NAME => {
96 let name = node.as_node().cloned().and_then(ast::Name::cast).unwrap();
97 let name_kind =
98 classify_name(db, Source::new(file_id.into(), &name)).map(|d| d.kind);
99
100 if let Some(Local(local)) = &name_kind {
101 if let Some(name) = local.name(db) {
102 let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
103 *shadow_count += 1;
104 binding_hash = Some(calc_binding_hash(file_id, &name, *shadow_count))
105 }
106 };
107
108 match name_kind {
109 Some(name_kind) => highlight_name(db, name_kind),
110 None => name.syntax().parent().map_or("function", |x| match x.kind() {
111 TYPE_PARAM | STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => "type",
112 RECORD_FIELD_DEF => "field",
113 _ => "function",
114 }),
115 }
116 }
117 INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal",
118 LIFETIME => "parameter",
119 T![unsafe] => "keyword.unsafe",
120 k if is_control_keyword(k) => "keyword.control",
121 k if k.is_keyword() => "keyword",
122 _ => {
123 if let Some(macro_call) = node.as_node().cloned().and_then(ast::MacroCall::cast) {
124 if let Some(path) = macro_call.path() {
125 if let Some(segment) = path.segment() {
126 if let Some(name_ref) = segment.name_ref() {
127 highlighted.insert(name_ref.syntax().clone().into());
128 let range_start = name_ref.syntax().text_range().start();
129 let mut range_end = name_ref.syntax().text_range().end();
130 for sibling in path.syntax().siblings_with_tokens(Direction::Next) {
131 match sibling.kind() {
132 T![!] | IDENT => range_end = sibling.text_range().end(),
133 _ => (),
134 }
135 }
136 res.push(HighlightedRange {
137 range: TextRange::from_to(range_start, range_end),
138 tag: "macro",
139 binding_hash: None,
140 })
141 }
142 }
143 }
144 }
145 continue;
146 }
147 };
148 res.push(HighlightedRange { range: node.text_range(), tag, binding_hash })
149 }
150 res
151}
152
153pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String {
154 let parse = db.parse(file_id);
155
156 fn rainbowify(seed: u64) -> String {
157 use rand::prelude::*;
158 let mut rng = SmallRng::seed_from_u64(seed);
159 format!(
160 "hsl({h},{s}%,{l}%)",
161 h = rng.gen_range::<u16, _, _>(0, 361),
162 s = rng.gen_range::<u16, _, _>(42, 99),
163 l = rng.gen_range::<u16, _, _>(40, 91),
164 )
165 }
166
167 let mut ranges = highlight(db, file_id);
168 ranges.sort_by_key(|it| it.range.start());
169 // quick non-optimal heuristic to intersect token ranges and highlighted ranges
170 let mut frontier = 0;
171 let mut could_intersect: Vec<&HighlightedRange> = Vec::new();
172
173 let mut buf = String::new();
174 buf.push_str(&STYLE);
175 buf.push_str("<pre><code>");
176 let tokens = parse.tree().syntax().descendants_with_tokens().filter_map(|it| it.into_token());
177 for token in tokens {
178 could_intersect.retain(|it| token.text_range().start() <= it.range.end());
179 while let Some(r) = ranges.get(frontier) {
180 if r.range.start() <= token.text_range().end() {
181 could_intersect.push(r);
182 frontier += 1;
183 } else {
184 break;
185 }
186 }
187 let text = html_escape(&token.text());
188 let ranges = could_intersect
189 .iter()
190 .filter(|it| token.text_range().is_subrange(&it.range))
191 .collect::<Vec<_>>();
192 if ranges.is_empty() {
193 buf.push_str(&text);
194 } else {
195 let classes = ranges.iter().map(|x| x.tag).collect::<Vec<_>>().join(" ");
196 let binding_hash = ranges.first().and_then(|x| x.binding_hash);
197 let color = match (rainbow, binding_hash) {
198 (true, Some(hash)) => format!(
199 " data-binding-hash=\"{}\" style=\"color: {};\"",
200 hash,
201 rainbowify(hash)
202 ),
203 _ => "".into(),
204 };
205 buf.push_str(&format!("<span class=\"{}\"{}>{}</span>", classes, color, text));
206 }
207 }
208 buf.push_str("</code></pre>");
209 buf
210}
211
212fn highlight_name(db: &RootDatabase, name_kind: NameKind) -> &'static str {
213 match name_kind {
214 Macro(_) => "macro",
215 Field(_) => "field",
216 AssocItem(hir::AssocItem::Function(_)) => "function",
217 AssocItem(hir::AssocItem::Const(_)) => "constant",
218 AssocItem(hir::AssocItem::TypeAlias(_)) => "type",
219 Def(hir::ModuleDef::Module(_)) => "module",
220 Def(hir::ModuleDef::Function(_)) => "function",
221 Def(hir::ModuleDef::Adt(_)) => "type",
222 Def(hir::ModuleDef::EnumVariant(_)) => "constant",
223 Def(hir::ModuleDef::Const(_)) => "constant",
224 Def(hir::ModuleDef::Static(_)) => "constant",
225 Def(hir::ModuleDef::Trait(_)) => "type",
226 Def(hir::ModuleDef::TypeAlias(_)) => "type",
227 Def(hir::ModuleDef::BuiltinType(_)) => "type",
228 SelfType(_) => "type",
229 GenericParam(_) => "type",
230 Local(local) => {
231 if local.is_mut(db) {
232 "variable.mut"
233 } else if local.ty(db).is_mutable_reference() {
234 "variable.mut"
235 } else {
236 "variable"
237 }
238 }
239 }
240}
241
242//FIXME: like, real html escaping
243fn html_escape(text: &str) -> String {
244 text.replace("<", "&lt;").replace(">", "&gt;")
245}
246
247const STYLE: &str = "
248<style>
249body { margin: 0; }
250pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
251
252.comment { color: #7F9F7F; }
253.string { color: #CC9393; }
254.function { color: #93E0E3; }
255.parameter { color: #94BFF3; }
256.builtin { color: #DD6718; }
257.text { color: #DCDCCC; }
258.attribute { color: #94BFF3; }
259.literal { color: #BFEBBF; }
260.macro { color: #94BFF3; }
261.variable { color: #DCDCCC; }
262.variable\\.mut { color: #DCDCCC; text-decoration: underline; }
263
264.keyword { color: #F0DFAF; }
265.keyword\\.unsafe { color: #DFAF8F; }
266.keyword\\.control { color: #F0DFAF; font-weight: bold; }
267</style>
268";
269
270#[cfg(test)]
271mod tests {
272 use crate::mock_analysis::single_file;
273 use test_utils::{assert_eq_text, project_dir, read_text};
274
275 #[test]
276 fn test_highlighting() {
277 let (analysis, file_id) = single_file(
278 r#"
279#[derive(Clone, Debug)]
280struct Foo {
281 pub x: i32,
282 pub y: i32,
283}
284
285fn foo<T>() -> T {
286 unimplemented!();
287 foo::<i32>();
288}
289
290// comment
291fn main() {
292 println!("Hello, {}!", 92);
293
294 let mut vec = Vec::new();
295 if true {
296 vec.push(Foo { x: 0, y: 1 });
297 }
298 unsafe { vec.set_len(0); }
299
300 let mut x = 42;
301 let y = &mut x;
302 let z = &y;
303
304 y;
305}
306"#
307 .trim(),
308 );
309 let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlighting.html");
310 let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
311 let expected_html = &read_text(&dst_file);
312 std::fs::write(dst_file, &actual_html).unwrap();
313 assert_eq_text!(expected_html, actual_html);
314 }
315
316 #[test]
317 fn test_rainbow_highlighting() {
318 let (analysis, file_id) = single_file(
319 r#"
320fn main() {
321 let hello = "hello";
322 let x = hello.to_string();
323 let y = hello.to_string();
324
325 let x = "other color please!";
326 let y = x.to_string();
327}
328
329fn bar() {
330 let mut hello = "hello";
331}
332"#
333 .trim(),
334 );
335 let dst_file =
336 project_dir().join("crates/ra_ide/src/snapshots/rainbow_highlighting.html");
337 let actual_html = &analysis.highlight_as_html(file_id, true).unwrap();
338 let expected_html = &read_text(&dst_file);
339 std::fs::write(dst_file, &actual_html).unwrap();
340 assert_eq_text!(expected_html, actual_html);
341 }
342}
diff --git a/crates/ra_ide/src/syntax_tree.rs b/crates/ra_ide/src/syntax_tree.rs
new file mode 100644
index 000000000..4d0f0fc47
--- /dev/null
+++ b/crates/ra_ide/src/syntax_tree.rs
@@ -0,0 +1,359 @@
1//! FIXME: write short doc here
2
3use crate::db::RootDatabase;
4use ra_db::SourceDatabase;
5use ra_syntax::{
6 algo, AstNode, NodeOrToken, SourceFile,
7 SyntaxKind::{RAW_STRING, STRING},
8 SyntaxToken, TextRange,
9};
10
11pub use ra_db::FileId;
12
13pub(crate) fn syntax_tree(
14 db: &RootDatabase,
15 file_id: FileId,
16 text_range: Option<TextRange>,
17) -> String {
18 let parse = db.parse(file_id);
19 if let Some(text_range) = text_range {
20 let node = match algo::find_covering_element(parse.tree().syntax(), text_range) {
21 NodeOrToken::Node(node) => node,
22 NodeOrToken::Token(token) => {
23 if let Some(tree) = syntax_tree_for_string(&token, text_range) {
24 return tree;
25 }
26 token.parent()
27 }
28 };
29
30 format!("{:#?}", node)
31 } else {
32 format!("{:#?}", parse.tree().syntax())
33 }
34}
35
36/// Attempts parsing the selected contents of a string literal
37/// as rust syntax and returns its syntax tree
38fn syntax_tree_for_string(token: &SyntaxToken, text_range: TextRange) -> Option<String> {
39 // When the range is inside a string
40 // we'll attempt parsing it as rust syntax
41 // to provide the syntax tree of the contents of the string
42 match token.kind() {
43 STRING | RAW_STRING => syntax_tree_for_token(token, text_range),
44 _ => None,
45 }
46}
47
48fn syntax_tree_for_token(node: &SyntaxToken, text_range: TextRange) -> Option<String> {
49 // Range of the full node
50 let node_range = node.text_range();
51 let text = node.text().to_string();
52
53 // We start at some point inside the node
54 // Either we have selected the whole string
55 // or our selection is inside it
56 let start = text_range.start() - node_range.start();
57
58 // how many characters we have selected
59 let len = text_range.len().to_usize();
60
61 let node_len = node_range.len().to_usize();
62
63 let start = start.to_usize();
64
65 // We want to cap our length
66 let len = len.min(node_len);
67
68 // Ensure our slice is inside the actual string
69 let end = if start + len < text.len() { start + len } else { text.len() - start };
70
71 let text = &text[start..end];
72
73 // Remove possible extra string quotes from the start
74 // and the end of the string
75 let text = text
76 .trim_start_matches('r')
77 .trim_start_matches('#')
78 .trim_start_matches('"')
79 .trim_end_matches('#')
80 .trim_end_matches('"')
81 .trim()
82 // Remove custom markers
83 .replace("<|>", "");
84
85 let parsed = SourceFile::parse(&text);
86
87 // If the "file" parsed without errors,
88 // return its syntax
89 if parsed.errors().is_empty() {
90 return Some(format!("{:#?}", parsed.tree().syntax()));
91 }
92
93 None
94}
95
96#[cfg(test)]
97mod tests {
98 use test_utils::assert_eq_text;
99
100 use crate::mock_analysis::{single_file, single_file_with_range};
101
102 #[test]
103 fn test_syntax_tree_without_range() {
104 // Basic syntax
105 let (analysis, file_id) = single_file(r#"fn foo() {}"#);
106 let syn = analysis.syntax_tree(file_id, None).unwrap();
107
108 assert_eq_text!(
109 syn.trim(),
110 r#"
111SOURCE_FILE@[0; 11)
112 FN_DEF@[0; 11)
113 FN_KW@[0; 2) "fn"
114 WHITESPACE@[2; 3) " "
115 NAME@[3; 6)
116 IDENT@[3; 6) "foo"
117 PARAM_LIST@[6; 8)
118 L_PAREN@[6; 7) "("
119 R_PAREN@[7; 8) ")"
120 WHITESPACE@[8; 9) " "
121 BLOCK_EXPR@[9; 11)
122 BLOCK@[9; 11)
123 L_CURLY@[9; 10) "{"
124 R_CURLY@[10; 11) "}"
125"#
126 .trim()
127 );
128
129 let (analysis, file_id) = single_file(
130 r#"
131fn test() {
132 assert!("
133 fn foo() {
134 }
135 ", "");
136}"#
137 .trim(),
138 );
139 let syn = analysis.syntax_tree(file_id, None).unwrap();
140
141 assert_eq_text!(
142 syn.trim(),
143 r#"
144SOURCE_FILE@[0; 60)
145 FN_DEF@[0; 60)
146 FN_KW@[0; 2) "fn"
147 WHITESPACE@[2; 3) " "
148 NAME@[3; 7)
149 IDENT@[3; 7) "test"
150 PARAM_LIST@[7; 9)
151 L_PAREN@[7; 8) "("
152 R_PAREN@[8; 9) ")"
153 WHITESPACE@[9; 10) " "
154 BLOCK_EXPR@[10; 60)
155 BLOCK@[10; 60)
156 L_CURLY@[10; 11) "{"
157 WHITESPACE@[11; 16) "\n "
158 EXPR_STMT@[16; 58)
159 MACRO_CALL@[16; 57)
160 PATH@[16; 22)
161 PATH_SEGMENT@[16; 22)
162 NAME_REF@[16; 22)
163 IDENT@[16; 22) "assert"
164 EXCL@[22; 23) "!"
165 TOKEN_TREE@[23; 57)
166 L_PAREN@[23; 24) "("
167 STRING@[24; 52) "\"\n fn foo() {\n ..."
168 COMMA@[52; 53) ","
169 WHITESPACE@[53; 54) " "
170 STRING@[54; 56) "\"\""
171 R_PAREN@[56; 57) ")"
172 SEMI@[57; 58) ";"
173 WHITESPACE@[58; 59) "\n"
174 R_CURLY@[59; 60) "}"
175"#
176 .trim()
177 );
178 }
179
180 #[test]
181 fn test_syntax_tree_with_range() {
182 let (analysis, range) = single_file_with_range(r#"<|>fn foo() {}<|>"#.trim());
183 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
184
185 assert_eq_text!(
186 syn.trim(),
187 r#"
188FN_DEF@[0; 11)
189 FN_KW@[0; 2) "fn"
190 WHITESPACE@[2; 3) " "
191 NAME@[3; 6)
192 IDENT@[3; 6) "foo"
193 PARAM_LIST@[6; 8)
194 L_PAREN@[6; 7) "("
195 R_PAREN@[7; 8) ")"
196 WHITESPACE@[8; 9) " "
197 BLOCK_EXPR@[9; 11)
198 BLOCK@[9; 11)
199 L_CURLY@[9; 10) "{"
200 R_CURLY@[10; 11) "}"
201"#
202 .trim()
203 );
204
205 let (analysis, range) = single_file_with_range(
206 r#"fn test() {
207 <|>assert!("
208 fn foo() {
209 }
210 ", "");<|>
211}"#
212 .trim(),
213 );
214 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
215
216 assert_eq_text!(
217 syn.trim(),
218 r#"
219EXPR_STMT@[16; 58)
220 MACRO_CALL@[16; 57)
221 PATH@[16; 22)
222 PATH_SEGMENT@[16; 22)
223 NAME_REF@[16; 22)
224 IDENT@[16; 22) "assert"
225 EXCL@[22; 23) "!"
226 TOKEN_TREE@[23; 57)
227 L_PAREN@[23; 24) "("
228 STRING@[24; 52) "\"\n fn foo() {\n ..."
229 COMMA@[52; 53) ","
230 WHITESPACE@[53; 54) " "
231 STRING@[54; 56) "\"\""
232 R_PAREN@[56; 57) ")"
233 SEMI@[57; 58) ";"
234"#
235 .trim()
236 );
237 }
238
239 #[test]
240 fn test_syntax_tree_inside_string() {
241 let (analysis, range) = single_file_with_range(
242 r#"fn test() {
243 assert!("
244<|>fn foo() {
245}<|>
246fn bar() {
247}
248 ", "");
249}"#
250 .trim(),
251 );
252 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
253 assert_eq_text!(
254 syn.trim(),
255 r#"
256SOURCE_FILE@[0; 12)
257 FN_DEF@[0; 12)
258 FN_KW@[0; 2) "fn"
259 WHITESPACE@[2; 3) " "
260 NAME@[3; 6)
261 IDENT@[3; 6) "foo"
262 PARAM_LIST@[6; 8)
263 L_PAREN@[6; 7) "("
264 R_PAREN@[7; 8) ")"
265 WHITESPACE@[8; 9) " "
266 BLOCK_EXPR@[9; 12)
267 BLOCK@[9; 12)
268 L_CURLY@[9; 10) "{"
269 WHITESPACE@[10; 11) "\n"
270 R_CURLY@[11; 12) "}"
271"#
272 .trim()
273 );
274
275 // With a raw string
276 let (analysis, range) = single_file_with_range(
277 r###"fn test() {
278 assert!(r#"
279<|>fn foo() {
280}<|>
281fn bar() {
282}
283 "#, "");
284}"###
285 .trim(),
286 );
287 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
288 assert_eq_text!(
289 syn.trim(),
290 r#"
291SOURCE_FILE@[0; 12)
292 FN_DEF@[0; 12)
293 FN_KW@[0; 2) "fn"
294 WHITESPACE@[2; 3) " "
295 NAME@[3; 6)
296 IDENT@[3; 6) "foo"
297 PARAM_LIST@[6; 8)
298 L_PAREN@[6; 7) "("
299 R_PAREN@[7; 8) ")"
300 WHITESPACE@[8; 9) " "
301 BLOCK_EXPR@[9; 12)
302 BLOCK@[9; 12)
303 L_CURLY@[9; 10) "{"
304 WHITESPACE@[10; 11) "\n"
305 R_CURLY@[11; 12) "}"
306"#
307 .trim()
308 );
309
310 // With a raw string
311 let (analysis, range) = single_file_with_range(
312 r###"fn test() {
313 assert!(r<|>#"
314fn foo() {
315}
316fn bar() {
317}"<|>#, "");
318}"###
319 .trim(),
320 );
321 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
322 assert_eq_text!(
323 syn.trim(),
324 r#"
325SOURCE_FILE@[0; 25)
326 FN_DEF@[0; 12)
327 FN_KW@[0; 2) "fn"
328 WHITESPACE@[2; 3) " "
329 NAME@[3; 6)
330 IDENT@[3; 6) "foo"
331 PARAM_LIST@[6; 8)
332 L_PAREN@[6; 7) "("
333 R_PAREN@[7; 8) ")"
334 WHITESPACE@[8; 9) " "
335 BLOCK_EXPR@[9; 12)
336 BLOCK@[9; 12)
337 L_CURLY@[9; 10) "{"
338 WHITESPACE@[10; 11) "\n"
339 R_CURLY@[11; 12) "}"
340 WHITESPACE@[12; 13) "\n"
341 FN_DEF@[13; 25)
342 FN_KW@[13; 15) "fn"
343 WHITESPACE@[15; 16) " "
344 NAME@[16; 19)
345 IDENT@[16; 19) "bar"
346 PARAM_LIST@[19; 21)
347 L_PAREN@[19; 20) "("
348 R_PAREN@[20; 21) ")"
349 WHITESPACE@[21; 22) " "
350 BLOCK_EXPR@[22; 25)
351 BLOCK@[22; 25)
352 L_CURLY@[22; 23) "{"
353 WHITESPACE@[23; 24) "\n"
354 R_CURLY@[24; 25) "}"
355"#
356 .trim()
357 );
358 }
359}
diff --git a/crates/ra_ide/src/test_utils.rs b/crates/ra_ide/src/test_utils.rs
new file mode 100644
index 000000000..8adb214d4
--- /dev/null
+++ b/crates/ra_ide/src/test_utils.rs
@@ -0,0 +1,21 @@
1//! FIXME: write short doc here
2
3use ra_syntax::{SourceFile, TextUnit};
4use ra_text_edit::TextEdit;
5
6pub use test_utils::*;
7
8pub fn check_action<F: Fn(&SourceFile, TextUnit) -> Option<TextEdit>>(
9 before: &str,
10 after: &str,
11 f: F,
12) {
13 let (before_cursor_pos, before) = extract_offset(before);
14 let file = SourceFile::parse(&before).ok().unwrap();
15 let result = f(&file, before_cursor_pos).expect("code action is not applicable");
16 let actual = result.apply(&before);
17 let actual_cursor_pos =
18 result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit");
19 let actual = add_cursor(&actual, actual_cursor_pos);
20 assert_eq_text!(after, &actual);
21}
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs
new file mode 100644
index 000000000..21e5be9b3
--- /dev/null
+++ b/crates/ra_ide/src/typing.rs
@@ -0,0 +1,490 @@
1//! This module handles auto-magic editing actions applied together with users
2//! edits. For example, if the user typed
3//!
4//! ```text
5//! foo
6//! .bar()
7//! .baz()
8//! | // <- cursor is here
9//! ```
10//!
11//! and types `.` next, we want to indent the dot.
12//!
13//! Language server executes such typing assists synchronously. That is, they
14//! block user's typing and should be pretty fast for this reason!
15
16use ra_db::{FilePosition, SourceDatabase};
17use ra_fmt::leading_indent;
18use ra_syntax::{
19 algo::find_node_at_offset,
20 ast::{self, AstToken},
21 AstNode, SmolStr, SourceFile,
22 SyntaxKind::*,
23 SyntaxToken, TextRange, TextUnit, TokenAtOffset,
24};
25use ra_text_edit::TextEdit;
26
27use crate::{db::RootDatabase, source_change::SingleFileChange, SourceChange, SourceFileEdit};
28
29pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<SourceChange> {
30 let parse = db.parse(position.file_id);
31 let file = parse.tree();
32 let comment = file
33 .syntax()
34 .token_at_offset(position.offset)
35 .left_biased()
36 .and_then(ast::Comment::cast)?;
37
38 if comment.kind().shape.is_block() {
39 return None;
40 }
41
42 let prefix = comment.prefix();
43 let comment_range = comment.syntax().text_range();
44 if position.offset < comment_range.start() + TextUnit::of_str(prefix) + TextUnit::from(1) {
45 return None;
46 }
47
48 // Continuing non-doc line comments (like this one :) ) is annoying
49 if prefix == "//" && comment_range.end() == position.offset {
50 return None;
51 }
52
53 let indent = node_indent(&file, comment.syntax())?;
54 let inserted = format!("\n{}{} ", indent, prefix);
55 let cursor_position = position.offset + TextUnit::of_str(&inserted);
56 let edit = TextEdit::insert(position.offset, inserted);
57
58 Some(
59 SourceChange::source_file_edit(
60 "on enter",
61 SourceFileEdit { edit, file_id: position.file_id },
62 )
63 .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }),
64 )
65}
66
67fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option<SmolStr> {
68 let ws = match file.syntax().token_at_offset(token.text_range().start()) {
69 TokenAtOffset::Between(l, r) => {
70 assert!(r == *token);
71 l
72 }
73 TokenAtOffset::Single(n) => {
74 assert!(n == *token);
75 return Some("".into());
76 }
77 TokenAtOffset::None => unreachable!(),
78 };
79 if ws.kind() != WHITESPACE {
80 return None;
81 }
82 let text = ws.text();
83 let pos = text.rfind('\n').map(|it| it + 1).unwrap_or(0);
84 Some(text[pos..].into())
85}
86
87pub(crate) const TRIGGER_CHARS: &str = ".=>";
88
89pub(crate) fn on_char_typed(
90 db: &RootDatabase,
91 position: FilePosition,
92 char_typed: char,
93) -> Option<SourceChange> {
94 assert!(TRIGGER_CHARS.contains(char_typed));
95 let file = &db.parse(position.file_id).tree();
96 assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed));
97 let single_file_change = on_char_typed_inner(file, position.offset, char_typed)?;
98 Some(single_file_change.into_source_change(position.file_id))
99}
100
101fn on_char_typed_inner(
102 file: &SourceFile,
103 offset: TextUnit,
104 char_typed: char,
105) -> Option<SingleFileChange> {
106 assert!(TRIGGER_CHARS.contains(char_typed));
107 match char_typed {
108 '.' => on_dot_typed(file, offset),
109 '=' => on_eq_typed(file, offset),
110 '>' => on_arrow_typed(file, offset),
111 _ => unreachable!(),
112 }
113}
114
115/// Returns an edit which should be applied after `=` was typed. Primarily,
116/// this works when adding `let =`.
117// FIXME: use a snippet completion instead of this hack here.
118fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option<SingleFileChange> {
119 assert_eq!(file.syntax().text().char_at(offset), Some('='));
120 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
121 if let_stmt.has_semi() {
122 return None;
123 }
124 if let Some(expr) = let_stmt.initializer() {
125 let expr_range = expr.syntax().text_range();
126 if expr_range.contains(offset) && offset != expr_range.start() {
127 return None;
128 }
129 if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') {
130 return None;
131 }
132 } else {
133 return None;
134 }
135 let offset = let_stmt.syntax().text_range().end();
136 Some(SingleFileChange {
137 label: "add semicolon".to_string(),
138 edit: TextEdit::insert(offset, ";".to_string()),
139 cursor_position: None,
140 })
141}
142
143/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
144fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option<SingleFileChange> {
145 assert_eq!(file.syntax().text().char_at(offset), Some('.'));
146 let whitespace =
147 file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?;
148
149 let current_indent = {
150 let text = whitespace.text();
151 let newline = text.rfind('\n')?;
152 &text[newline + 1..]
153 };
154 let current_indent_len = TextUnit::of_str(current_indent);
155
156 // Make sure dot is a part of call chain
157 let field_expr = ast::FieldExpr::cast(whitespace.syntax().parent())?;
158 let prev_indent = leading_indent(field_expr.syntax())?;
159 let target_indent = format!(" {}", prev_indent);
160 let target_indent_len = TextUnit::of_str(&target_indent);
161 if current_indent_len == target_indent_len {
162 return None;
163 }
164
165 Some(SingleFileChange {
166 label: "reindent dot".to_string(),
167 edit: TextEdit::replace(
168 TextRange::from_to(offset - current_indent_len, offset),
169 target_indent,
170 ),
171 cursor_position: Some(
172 offset + target_indent_len - current_indent_len + TextUnit::of_char('.'),
173 ),
174 })
175}
176
177/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
178fn on_arrow_typed(file: &SourceFile, offset: TextUnit) -> Option<SingleFileChange> {
179 let file_text = file.syntax().text();
180 assert_eq!(file_text.char_at(offset), Some('>'));
181 let after_arrow = offset + TextUnit::of_char('>');
182 if file_text.char_at(after_arrow) != Some('{') {
183 return None;
184 }
185 if find_node_at_offset::<ast::RetType>(file.syntax(), offset).is_none() {
186 return None;
187 }
188
189 Some(SingleFileChange {
190 label: "add space after return type".to_string(),
191 edit: TextEdit::insert(after_arrow, " ".to_string()),
192 cursor_position: Some(after_arrow),
193 })
194}
195
196#[cfg(test)]
197mod tests {
198 use test_utils::{add_cursor, assert_eq_text, extract_offset};
199
200 use crate::mock_analysis::single_file;
201
202 use super::*;
203
204 #[test]
205 fn test_on_enter() {
206 fn apply_on_enter(before: &str) -> Option<String> {
207 let (offset, before) = extract_offset(before);
208 let (analysis, file_id) = single_file(&before);
209 let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?;
210
211 assert_eq!(result.source_file_edits.len(), 1);
212 let actual = result.source_file_edits[0].edit.apply(&before);
213 let actual = add_cursor(&actual, result.cursor_position.unwrap().offset);
214 Some(actual)
215 }
216
217 fn do_check(before: &str, after: &str) {
218 let actual = apply_on_enter(before).unwrap();
219 assert_eq_text!(after, &actual);
220 }
221
222 fn do_check_noop(text: &str) {
223 assert!(apply_on_enter(text).is_none())
224 }
225
226 do_check(
227 r"
228/// Some docs<|>
229fn foo() {
230}
231",
232 r"
233/// Some docs
234/// <|>
235fn foo() {
236}
237",
238 );
239 do_check(
240 r"
241impl S {
242 /// Some<|> docs.
243 fn foo() {}
244}
245",
246 r"
247impl S {
248 /// Some
249 /// <|> docs.
250 fn foo() {}
251}
252",
253 );
254 do_check(
255 r"
256fn main() {
257 // Fix<|> me
258 let x = 1 + 1;
259}
260",
261 r"
262fn main() {
263 // Fix
264 // <|> me
265 let x = 1 + 1;
266}
267",
268 );
269 do_check_noop(
270 r"
271fn main() {
272 // Fix me<|>
273 let x = 1 + 1;
274}
275",
276 );
277
278 do_check_noop(r"<|>//! docz");
279 }
280
281 fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> {
282 let (offset, before) = extract_offset(before);
283 let edit = TextEdit::insert(offset, char_typed.to_string());
284 let before = edit.apply(&before);
285 let parse = SourceFile::parse(&before);
286 on_char_typed_inner(&parse.tree(), offset, char_typed)
287 .map(|it| (it.edit.apply(&before), it))
288 }
289
290 fn type_char(char_typed: char, before: &str, after: &str) {
291 let (actual, file_change) = do_type_char(char_typed, before)
292 .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed));
293
294 if after.contains("<|>") {
295 let (offset, after) = extract_offset(after);
296 assert_eq_text!(&after, &actual);
297 assert_eq!(file_change.cursor_position, Some(offset))
298 } else {
299 assert_eq_text!(after, &actual);
300 }
301 }
302
303 fn type_char_noop(char_typed: char, before: &str) {
304 let file_change = do_type_char(char_typed, before);
305 assert!(file_change.is_none())
306 }
307
308 #[test]
309 fn test_on_eq_typed() {
310 // do_check(r"
311 // fn foo() {
312 // let foo =<|>
313 // }
314 // ", r"
315 // fn foo() {
316 // let foo =;
317 // }
318 // ");
319 type_char(
320 '=',
321 r"
322fn foo() {
323 let foo <|> 1 + 1
324}
325",
326 r"
327fn foo() {
328 let foo = 1 + 1;
329}
330",
331 );
332 // do_check(r"
333 // fn foo() {
334 // let foo =<|>
335 // let bar = 1;
336 // }
337 // ", r"
338 // fn foo() {
339 // let foo =;
340 // let bar = 1;
341 // }
342 // ");
343 }
344
345 #[test]
346 fn indents_new_chain_call() {
347 type_char(
348 '.',
349 r"
350 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
351 self.child_impl(db, name)
352 <|>
353 }
354 ",
355 r"
356 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
357 self.child_impl(db, name)
358 .
359 }
360 ",
361 );
362 type_char_noop(
363 '.',
364 r"
365 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
366 self.child_impl(db, name)
367 <|>
368 }
369 ",
370 )
371 }
372
373 #[test]
374 fn indents_new_chain_call_with_semi() {
375 type_char(
376 '.',
377 r"
378 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
379 self.child_impl(db, name)
380 <|>;
381 }
382 ",
383 r"
384 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
385 self.child_impl(db, name)
386 .;
387 }
388 ",
389 );
390 type_char_noop(
391 '.',
392 r"
393 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
394 self.child_impl(db, name)
395 <|>;
396 }
397 ",
398 )
399 }
400
401 #[test]
402 fn indents_continued_chain_call() {
403 type_char(
404 '.',
405 r"
406 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
407 self.child_impl(db, name)
408 .first()
409 <|>
410 }
411 ",
412 r"
413 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
414 self.child_impl(db, name)
415 .first()
416 .
417 }
418 ",
419 );
420 type_char_noop(
421 '.',
422 r"
423 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
424 self.child_impl(db, name)
425 .first()
426 <|>
427 }
428 ",
429 );
430 }
431
432 #[test]
433 fn indents_middle_of_chain_call() {
434 type_char(
435 '.',
436 r"
437 fn source_impl() {
438 let var = enum_defvariant_list().unwrap()
439 <|>
440 .nth(92)
441 .unwrap();
442 }
443 ",
444 r"
445 fn source_impl() {
446 let var = enum_defvariant_list().unwrap()
447 .
448 .nth(92)
449 .unwrap();
450 }
451 ",
452 );
453 type_char_noop(
454 '.',
455 r"
456 fn source_impl() {
457 let var = enum_defvariant_list().unwrap()
458 <|>
459 .nth(92)
460 .unwrap();
461 }
462 ",
463 );
464 }
465
466 #[test]
467 fn dont_indent_freestanding_dot() {
468 type_char_noop(
469 '.',
470 r"
471 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
472 <|>
473 }
474 ",
475 );
476 type_char_noop(
477 '.',
478 r"
479 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
480 <|>
481 }
482 ",
483 );
484 }
485
486 #[test]
487 fn adds_space_after_return_type() {
488 type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -><|> { 92 }")
489 }
490}
diff --git a/crates/ra_ide/src/wasm_shims.rs b/crates/ra_ide/src/wasm_shims.rs
new file mode 100644
index 000000000..088cc9be4
--- /dev/null
+++ b/crates/ra_ide/src/wasm_shims.rs
@@ -0,0 +1,19 @@
1//! FIXME: write short doc here
2
3#[cfg(not(feature = "wasm"))]
4pub use std::time::Instant;
5
6#[cfg(feature = "wasm")]
7#[derive(Clone, Copy, Debug)]
8pub struct Instant;
9
10#[cfg(feature = "wasm")]
11impl Instant {
12 pub fn now() -> Self {
13 Self
14 }
15
16 pub fn elapsed(&self) -> std::time::Duration {
17 std::time::Duration::new(0, 0)
18 }
19}