diff options
author | Mikhail Rakhmanov <[email protected]> | 2020-06-03 19:10:54 +0100 |
---|---|---|
committer | Mikhail Rakhmanov <[email protected]> | 2020-06-03 19:10:54 +0100 |
commit | eefa10bc6bff3624ddd0bbb6bc89d8beb4bed186 (patch) | |
tree | 15c38c2993c52f4065d338090ca9185cc1fcd3da /crates/ra_ide/src | |
parent | a9d567584857b1be4ca8eaa5ef2c7d85f7b2845e (diff) | |
parent | 794f6da821c5d6e2490b996baffe162e4753262d (diff) |
Merge branch 'master' into assists_extract_enum
Diffstat (limited to 'crates/ra_ide/src')
41 files changed, 1505 insertions, 391 deletions
diff --git a/crates/ra_ide/src/call_hierarchy.rs b/crates/ra_ide/src/call_hierarchy.rs index 85d1f0cb1..defd8176f 100644 --- a/crates/ra_ide/src/call_hierarchy.rs +++ b/crates/ra_ide/src/call_hierarchy.rs | |||
@@ -246,6 +246,35 @@ mod tests { | |||
246 | } | 246 | } |
247 | 247 | ||
248 | #[test] | 248 | #[test] |
249 | fn test_call_hierarchy_in_tests_mod() { | ||
250 | check_hierarchy( | ||
251 | r#" | ||
252 | //- /lib.rs cfg:test | ||
253 | fn callee() {} | ||
254 | fn caller1() { | ||
255 | call<|>ee(); | ||
256 | } | ||
257 | |||
258 | #[cfg(test)] | ||
259 | mod tests { | ||
260 | use super::*; | ||
261 | |||
262 | #[test] | ||
263 | fn test_caller() { | ||
264 | callee(); | ||
265 | } | ||
266 | } | ||
267 | "#, | ||
268 | "callee FN_DEF FileId(1) 0..14 3..9", | ||
269 | &[ | ||
270 | "caller1 FN_DEF FileId(1) 15..45 18..25 : [34..40]", | ||
271 | "test_caller FN_DEF FileId(1) 93..147 108..119 : [132..138]", | ||
272 | ], | ||
273 | &[], | ||
274 | ); | ||
275 | } | ||
276 | |||
277 | #[test] | ||
249 | fn test_call_hierarchy_in_different_files() { | 278 | fn test_call_hierarchy_in_different_files() { |
250 | check_hierarchy( | 279 | check_hierarchy( |
251 | r#" | 280 | r#" |
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index 191300704..a721e23c6 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | mod completion_config; | 1 | mod completion_config; |
4 | mod completion_item; | 2 | mod completion_item; |
5 | mod completion_context; | 3 | mod completion_context; |
@@ -35,6 +33,51 @@ pub use crate::completion::{ | |||
35 | completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat}, | 33 | completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat}, |
36 | }; | 34 | }; |
37 | 35 | ||
36 | //FIXME: split the following feature into fine-grained features. | ||
37 | |||
38 | // Feature: Magic Completions | ||
39 | // | ||
40 | // In addition to usual reference completion, rust-analyzer provides some ✨magic✨ | ||
41 | // completions as well: | ||
42 | // | ||
43 | // Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor | ||
44 | // is placed at the appropriate position. Even though `if` is easy to type, you | ||
45 | // still want to complete it, to get ` { }` for free! `return` is inserted with a | ||
46 | // space or `;` depending on the return type of the function. | ||
47 | // | ||
48 | // When completing a function call, `()` are automatically inserted. If a function | ||
49 | // takes arguments, the cursor is positioned inside the parenthesis. | ||
50 | // | ||
51 | // There are postfix completions, which can be triggered by typing something like | ||
52 | // `foo().if`. The word after `.` determines postfix completion. Possible variants are: | ||
53 | // | ||
54 | // - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result` | ||
55 | // - `expr.match` -> `match expr {}` | ||
56 | // - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result` | ||
57 | // - `expr.ref` -> `&expr` | ||
58 | // - `expr.refm` -> `&mut expr` | ||
59 | // - `expr.not` -> `!expr` | ||
60 | // - `expr.dbg` -> `dbg!(expr)` | ||
61 | // | ||
62 | // There also snippet completions: | ||
63 | // | ||
64 | // .Expressions | ||
65 | // - `pd` -> `println!("{:?}")` | ||
66 | // - `ppd` -> `println!("{:#?}")` | ||
67 | // | ||
68 | // .Items | ||
69 | // - `tfn` -> `#[test] fn f(){}` | ||
70 | // - `tmod` -> | ||
71 | // ```rust | ||
72 | // #[cfg(test)] | ||
73 | // mod tests { | ||
74 | // use super::*; | ||
75 | // | ||
76 | // #[test] | ||
77 | // fn test_fn() {} | ||
78 | // } | ||
79 | // ``` | ||
80 | |||
38 | /// Main entry point for completion. We run completion as a two-phase process. | 81 | /// Main entry point for completion. We run completion as a two-phase process. |
39 | /// | 82 | /// |
40 | /// First, we look at the position and collect a so-called `CompletionContext. | 83 | /// First, we look at the position and collect a so-called `CompletionContext. |
@@ -82,3 +125,81 @@ pub(crate) fn completions( | |||
82 | 125 | ||
83 | Some(acc) | 126 | Some(acc) |
84 | } | 127 | } |
128 | |||
129 | #[cfg(test)] | ||
130 | mod tests { | ||
131 | use crate::completion::completion_config::CompletionConfig; | ||
132 | use crate::mock_analysis::analysis_and_position; | ||
133 | |||
134 | struct DetailAndDocumentation<'a> { | ||
135 | detail: &'a str, | ||
136 | documentation: &'a str, | ||
137 | } | ||
138 | |||
139 | fn check_detail_and_documentation(fixture: &str, expected: DetailAndDocumentation) { | ||
140 | let (analysis, position) = analysis_and_position(fixture); | ||
141 | let config = CompletionConfig::default(); | ||
142 | let completions = analysis.completions(&config, position).unwrap().unwrap(); | ||
143 | for item in completions { | ||
144 | if item.detail() == Some(expected.detail) { | ||
145 | let opt = item.documentation(); | ||
146 | let doc = opt.as_ref().map(|it| it.as_str()); | ||
147 | assert_eq!(doc, Some(expected.documentation)); | ||
148 | return; | ||
149 | } | ||
150 | } | ||
151 | panic!("completion detail not found: {}", expected.detail) | ||
152 | } | ||
153 | |||
154 | #[test] | ||
155 | fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() { | ||
156 | check_detail_and_documentation( | ||
157 | r#" | ||
158 | //- /lib.rs | ||
159 | macro_rules! bar { | ||
160 | () => { | ||
161 | struct Bar; | ||
162 | impl Bar { | ||
163 | #[doc = "Do the foo"] | ||
164 | fn foo(&self) {} | ||
165 | } | ||
166 | } | ||
167 | } | ||
168 | |||
169 | bar!(); | ||
170 | |||
171 | fn foo() { | ||
172 | let bar = Bar; | ||
173 | bar.fo<|>; | ||
174 | } | ||
175 | "#, | ||
176 | DetailAndDocumentation { detail: "fn foo(&self)", documentation: "Do the foo" }, | ||
177 | ); | ||
178 | } | ||
179 | |||
180 | #[test] | ||
181 | fn test_completion_detail_from_macro_generated_struct_fn_doc_comment() { | ||
182 | check_detail_and_documentation( | ||
183 | r#" | ||
184 | //- /lib.rs | ||
185 | macro_rules! bar { | ||
186 | () => { | ||
187 | struct Bar; | ||
188 | impl Bar { | ||
189 | /// Do the foo | ||
190 | fn foo(&self) {} | ||
191 | } | ||
192 | } | ||
193 | } | ||
194 | |||
195 | bar!(); | ||
196 | |||
197 | fn foo() { | ||
198 | let bar = Bar; | ||
199 | bar.fo<|>; | ||
200 | } | ||
201 | "#, | ||
202 | DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" }, | ||
203 | ); | ||
204 | } | ||
205 | } | ||
diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs index f17266221..fb3f0b743 100644 --- a/crates/ra_ide/src/completion/complete_attribute.rs +++ b/crates/ra_ide/src/completion/complete_attribute.rs | |||
@@ -112,7 +112,7 @@ const ATTRIBUTES: &[AttrCompletion] = &[ | |||
112 | AttrCompletion { label: "repr", snippet: Some("repr(${0:C})"), should_be_inner: false }, | 112 | AttrCompletion { label: "repr", snippet: Some("repr(${0:C})"), should_be_inner: false }, |
113 | AttrCompletion { | 113 | AttrCompletion { |
114 | label: "should_panic", | 114 | label: "should_panic", |
115 | snippet: Some(r#"expected = "${0:reason}""#), | 115 | snippet: Some(r#"should_panic(expected = "${0:reason}")"#), |
116 | should_be_inner: false, | 116 | should_be_inner: false, |
117 | }, | 117 | }, |
118 | AttrCompletion { | 118 | AttrCompletion { |
@@ -571,7 +571,7 @@ mod tests { | |||
571 | label: "should_panic", | 571 | label: "should_panic", |
572 | source_range: 19..19, | 572 | source_range: 19..19, |
573 | delete: 19..19, | 573 | delete: 19..19, |
574 | insert: "expected = \"${0:reason}\"", | 574 | insert: "should_panic(expected = \"${0:reason}\")", |
575 | kind: Attribute, | 575 | kind: Attribute, |
576 | }, | 576 | }, |
577 | CompletionItem { | 577 | CompletionItem { |
@@ -810,7 +810,7 @@ mod tests { | |||
810 | label: "should_panic", | 810 | label: "should_panic", |
811 | source_range: 20..20, | 811 | source_range: 20..20, |
812 | delete: 20..20, | 812 | delete: 20..20, |
813 | insert: "expected = \"${0:reason}\"", | 813 | insert: "should_panic(expected = \"${0:reason}\")", |
814 | kind: Attribute, | 814 | kind: Attribute, |
815 | }, | 815 | }, |
816 | CompletionItem { | 816 | CompletionItem { |
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index f2a52a407..59b58bf98 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs | |||
@@ -1,12 +1,11 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | use ra_assists::utils::TryEnum; | |
3 | use ra_syntax::{ | 3 | use ra_syntax::{ |
4 | ast::{self, AstNode}, | 4 | ast::{self, AstNode}, |
5 | TextRange, TextSize, | 5 | TextRange, TextSize, |
6 | }; | 6 | }; |
7 | use ra_text_edit::TextEdit; | 7 | use ra_text_edit::TextEdit; |
8 | 8 | ||
9 | use super::completion_config::SnippetCap; | ||
10 | use crate::{ | 9 | use crate::{ |
11 | completion::{ | 10 | completion::{ |
12 | completion_context::CompletionContext, | 11 | completion_context::CompletionContext, |
@@ -14,7 +13,8 @@ use crate::{ | |||
14 | }, | 13 | }, |
15 | CompletionItem, | 14 | CompletionItem, |
16 | }; | 15 | }; |
17 | use ra_assists::utils::TryEnum; | 16 | |
17 | use super::completion_config::SnippetCap; | ||
18 | 18 | ||
19 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | 19 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { |
20 | if !ctx.config.enable_postfix_completions { | 20 | if !ctx.config.enable_postfix_completions { |
@@ -184,6 +184,16 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
184 | &format!("dbg!({})", receiver_text), | 184 | &format!("dbg!({})", receiver_text), |
185 | ) | 185 | ) |
186 | .add_to(acc); | 186 | .add_to(acc); |
187 | |||
188 | postfix_snippet( | ||
189 | ctx, | ||
190 | cap, | ||
191 | &dot_receiver, | ||
192 | "call", | ||
193 | "function(expr)", | ||
194 | &format!("${{1}}({})", receiver_text), | ||
195 | ) | ||
196 | .add_to(acc); | ||
187 | } | 197 | } |
188 | 198 | ||
189 | fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String { | 199 | fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String { |
@@ -256,6 +266,13 @@ mod tests { | |||
256 | detail: "Box::new(expr)", | 266 | detail: "Box::new(expr)", |
257 | }, | 267 | }, |
258 | CompletionItem { | 268 | CompletionItem { |
269 | label: "call", | ||
270 | source_range: 89..89, | ||
271 | delete: 85..89, | ||
272 | insert: "${1}(bar)", | ||
273 | detail: "function(expr)", | ||
274 | }, | ||
275 | CompletionItem { | ||
259 | label: "dbg", | 276 | label: "dbg", |
260 | source_range: 89..89, | 277 | source_range: 89..89, |
261 | delete: 85..89, | 278 | delete: 85..89, |
@@ -335,6 +352,13 @@ mod tests { | |||
335 | detail: "Box::new(expr)", | 352 | detail: "Box::new(expr)", |
336 | }, | 353 | }, |
337 | CompletionItem { | 354 | CompletionItem { |
355 | label: "call", | ||
356 | source_range: 210..210, | ||
357 | delete: 206..210, | ||
358 | insert: "${1}(bar)", | ||
359 | detail: "function(expr)", | ||
360 | }, | ||
361 | CompletionItem { | ||
338 | label: "dbg", | 362 | label: "dbg", |
339 | source_range: 210..210, | 363 | source_range: 210..210, |
340 | delete: 206..210, | 364 | delete: 206..210, |
@@ -414,6 +438,13 @@ mod tests { | |||
414 | detail: "Box::new(expr)", | 438 | detail: "Box::new(expr)", |
415 | }, | 439 | }, |
416 | CompletionItem { | 440 | CompletionItem { |
441 | label: "call", | ||
442 | source_range: 211..211, | ||
443 | delete: 207..211, | ||
444 | insert: "${1}(bar)", | ||
445 | detail: "function(expr)", | ||
446 | }, | ||
447 | CompletionItem { | ||
417 | label: "dbg", | 448 | label: "dbg", |
418 | source_range: 211..211, | 449 | source_range: 211..211, |
419 | delete: 207..211, | 450 | delete: 207..211, |
@@ -488,6 +519,13 @@ mod tests { | |||
488 | detail: "Box::new(expr)", | 519 | detail: "Box::new(expr)", |
489 | }, | 520 | }, |
490 | CompletionItem { | 521 | CompletionItem { |
522 | label: "call", | ||
523 | source_range: 91..91, | ||
524 | delete: 87..91, | ||
525 | insert: "${1}(bar)", | ||
526 | detail: "function(expr)", | ||
527 | }, | ||
528 | CompletionItem { | ||
491 | label: "dbg", | 529 | label: "dbg", |
492 | source_range: 91..91, | 530 | source_range: 91..91, |
493 | delete: 87..91, | 531 | delete: 87..91, |
@@ -547,6 +585,13 @@ mod tests { | |||
547 | detail: "Box::new(expr)", | 585 | detail: "Box::new(expr)", |
548 | }, | 586 | }, |
549 | CompletionItem { | 587 | CompletionItem { |
588 | label: "call", | ||
589 | source_range: 52..52, | ||
590 | delete: 49..52, | ||
591 | insert: "${1}(42)", | ||
592 | detail: "function(expr)", | ||
593 | }, | ||
594 | CompletionItem { | ||
550 | label: "dbg", | 595 | label: "dbg", |
551 | source_range: 52..52, | 596 | source_range: 52..52, |
552 | delete: 49..52, | 597 | delete: 49..52, |
@@ -608,6 +653,13 @@ mod tests { | |||
608 | detail: "Box::new(expr)", | 653 | detail: "Box::new(expr)", |
609 | }, | 654 | }, |
610 | CompletionItem { | 655 | CompletionItem { |
656 | label: "call", | ||
657 | source_range: 149..150, | ||
658 | delete: 145..150, | ||
659 | insert: "${1}(bar)", | ||
660 | detail: "function(expr)", | ||
661 | }, | ||
662 | CompletionItem { | ||
611 | label: "dbg", | 663 | label: "dbg", |
612 | source_range: 149..150, | 664 | source_range: 149..150, |
613 | delete: 145..150, | 665 | delete: 145..150, |
@@ -667,6 +719,13 @@ mod tests { | |||
667 | detail: "Box::new(expr)", | 719 | detail: "Box::new(expr)", |
668 | }, | 720 | }, |
669 | CompletionItem { | 721 | CompletionItem { |
722 | label: "call", | ||
723 | source_range: 56..56, | ||
724 | delete: 49..56, | ||
725 | insert: "${1}(&&&&42)", | ||
726 | detail: "function(expr)", | ||
727 | }, | ||
728 | CompletionItem { | ||
670 | label: "dbg", | 729 | label: "dbg", |
671 | source_range: 56..56, | 730 | source_range: 56..56, |
672 | delete: 49..56, | 731 | delete: 49..56, |
diff --git a/crates/ra_ide/src/completion/complete_trait_impl.rs b/crates/ra_ide/src/completion/complete_trait_impl.rs index 039df03e0..21c9316e6 100644 --- a/crates/ra_ide/src/completion/complete_trait_impl.rs +++ b/crates/ra_ide/src/completion/complete_trait_impl.rs | |||
@@ -49,56 +49,53 @@ use crate::{ | |||
49 | pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { | 49 | pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { |
50 | if let Some((trigger, impl_def)) = completion_match(ctx) { | 50 | if let Some((trigger, impl_def)) = completion_match(ctx) { |
51 | match trigger.kind() { | 51 | match trigger.kind() { |
52 | SyntaxKind::NAME_REF => { | 52 | SyntaxKind::NAME_REF => get_missing_assoc_items(&ctx.sema, &impl_def) |
53 | get_missing_assoc_items(&ctx.sema, &impl_def).iter().for_each(|item| match item { | 53 | .into_iter() |
54 | .for_each(|item| match item { | ||
54 | hir::AssocItem::Function(fn_item) => { | 55 | hir::AssocItem::Function(fn_item) => { |
55 | add_function_impl(&trigger, acc, ctx, &fn_item) | 56 | add_function_impl(&trigger, acc, ctx, fn_item) |
56 | } | 57 | } |
57 | hir::AssocItem::TypeAlias(type_item) => { | 58 | hir::AssocItem::TypeAlias(type_item) => { |
58 | add_type_alias_impl(&trigger, acc, ctx, &type_item) | 59 | add_type_alias_impl(&trigger, acc, ctx, type_item) |
59 | } | 60 | } |
60 | hir::AssocItem::Const(const_item) => { | 61 | hir::AssocItem::Const(const_item) => { |
61 | add_const_impl(&trigger, acc, ctx, &const_item) | 62 | add_const_impl(&trigger, acc, ctx, const_item) |
62 | } | 63 | } |
63 | }) | 64 | }), |
64 | } | ||
65 | 65 | ||
66 | SyntaxKind::FN_DEF => { | 66 | SyntaxKind::FN_DEF => { |
67 | for missing_fn in | 67 | for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) |
68 | get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { | 68 | .into_iter() |
69 | match item { | 69 | .filter_map(|item| match item { |
70 | hir::AssocItem::Function(fn_item) => Some(fn_item), | 70 | hir::AssocItem::Function(fn_item) => Some(fn_item), |
71 | _ => None, | 71 | _ => None, |
72 | } | ||
73 | }) | 72 | }) |
74 | { | 73 | { |
75 | add_function_impl(&trigger, acc, ctx, &missing_fn); | 74 | add_function_impl(&trigger, acc, ctx, missing_fn); |
76 | } | 75 | } |
77 | } | 76 | } |
78 | 77 | ||
79 | SyntaxKind::TYPE_ALIAS_DEF => { | 78 | SyntaxKind::TYPE_ALIAS_DEF => { |
80 | for missing_fn in | 79 | for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) |
81 | get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { | 80 | .into_iter() |
82 | match item { | 81 | .filter_map(|item| match item { |
83 | hir::AssocItem::TypeAlias(type_item) => Some(type_item), | 82 | hir::AssocItem::TypeAlias(type_item) => Some(type_item), |
84 | _ => None, | 83 | _ => None, |
85 | } | ||
86 | }) | 84 | }) |
87 | { | 85 | { |
88 | add_type_alias_impl(&trigger, acc, ctx, &missing_fn); | 86 | add_type_alias_impl(&trigger, acc, ctx, missing_fn); |
89 | } | 87 | } |
90 | } | 88 | } |
91 | 89 | ||
92 | SyntaxKind::CONST_DEF => { | 90 | SyntaxKind::CONST_DEF => { |
93 | for missing_fn in | 91 | for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) |
94 | get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { | 92 | .into_iter() |
95 | match item { | 93 | .filter_map(|item| match item { |
96 | hir::AssocItem::Const(const_item) => Some(const_item), | 94 | hir::AssocItem::Const(const_item) => Some(const_item), |
97 | _ => None, | 95 | _ => None, |
98 | } | ||
99 | }) | 96 | }) |
100 | { | 97 | { |
101 | add_const_impl(&trigger, acc, ctx, &missing_fn); | 98 | add_const_impl(&trigger, acc, ctx, missing_fn); |
102 | } | 99 | } |
103 | } | 100 | } |
104 | 101 | ||
@@ -126,9 +123,9 @@ fn add_function_impl( | |||
126 | fn_def_node: &SyntaxNode, | 123 | fn_def_node: &SyntaxNode, |
127 | acc: &mut Completions, | 124 | acc: &mut Completions, |
128 | ctx: &CompletionContext, | 125 | ctx: &CompletionContext, |
129 | func: &hir::Function, | 126 | func: hir::Function, |
130 | ) { | 127 | ) { |
131 | let signature = FunctionSignature::from_hir(ctx.db, *func); | 128 | let signature = FunctionSignature::from_hir(ctx.db, func); |
132 | 129 | ||
133 | let fn_name = func.name(ctx.db).to_string(); | 130 | let fn_name = func.name(ctx.db).to_string(); |
134 | 131 | ||
@@ -167,7 +164,7 @@ fn add_type_alias_impl( | |||
167 | type_def_node: &SyntaxNode, | 164 | type_def_node: &SyntaxNode, |
168 | acc: &mut Completions, | 165 | acc: &mut Completions, |
169 | ctx: &CompletionContext, | 166 | ctx: &CompletionContext, |
170 | type_alias: &hir::TypeAlias, | 167 | type_alias: hir::TypeAlias, |
171 | ) { | 168 | ) { |
172 | let alias_name = type_alias.name(ctx.db).to_string(); | 169 | let alias_name = type_alias.name(ctx.db).to_string(); |
173 | 170 | ||
@@ -187,7 +184,7 @@ fn add_const_impl( | |||
187 | const_def_node: &SyntaxNode, | 184 | const_def_node: &SyntaxNode, |
188 | acc: &mut Completions, | 185 | acc: &mut Completions, |
189 | ctx: &CompletionContext, | 186 | ctx: &CompletionContext, |
190 | const_: &hir::Const, | 187 | const_: hir::Const, |
191 | ) { | 188 | ) { |
192 | let const_name = const_.name(ctx.db).map(|n| n.to_string()); | 189 | let const_name = const_.name(ctx.db).map(|n| n.to_string()); |
193 | 190 | ||
diff --git a/crates/ra_ide/src/completion/complete_unqualified_path.rs b/crates/ra_ide/src/completion/complete_unqualified_path.rs index db791660a..68032c37e 100644 --- a/crates/ra_ide/src/completion/complete_unqualified_path.rs +++ b/crates/ra_ide/src/completion/complete_unqualified_path.rs | |||
@@ -298,6 +298,42 @@ mod tests { | |||
298 | } | 298 | } |
299 | 299 | ||
300 | #[test] | 300 | #[test] |
301 | fn completes_bindings_from_for_with_in_prefix() { | ||
302 | mark::check!(completes_bindings_from_for_with_in_prefix); | ||
303 | assert_debug_snapshot!( | ||
304 | do_reference_completion( | ||
305 | r" | ||
306 | fn test() { | ||
307 | for index in &[1, 2, 3] { | ||
308 | let t = in<|> | ||
309 | } | ||
310 | } | ||
311 | " | ||
312 | ), | ||
313 | @r###" | ||
314 | [ | ||
315 | CompletionItem { | ||
316 | label: "index", | ||
317 | source_range: 107..107, | ||
318 | delete: 107..107, | ||
319 | insert: "index", | ||
320 | kind: Binding, | ||
321 | }, | ||
322 | CompletionItem { | ||
323 | label: "test()", | ||
324 | source_range: 107..107, | ||
325 | delete: 107..107, | ||
326 | insert: "test()$0", | ||
327 | kind: Function, | ||
328 | lookup: "test", | ||
329 | detail: "fn test()", | ||
330 | }, | ||
331 | ] | ||
332 | "### | ||
333 | ); | ||
334 | } | ||
335 | |||
336 | #[test] | ||
301 | fn completes_generic_params() { | 337 | fn completes_generic_params() { |
302 | assert_debug_snapshot!( | 338 | assert_debug_snapshot!( |
303 | do_reference_completion( | 339 | do_reference_completion( |
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index da336973c..c4646b727 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
@@ -12,6 +12,7 @@ use ra_syntax::{ | |||
12 | use ra_text_edit::Indel; | 12 | use ra_text_edit::Indel; |
13 | 13 | ||
14 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; | 14 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; |
15 | use test_utils::mark; | ||
15 | 16 | ||
16 | /// `CompletionContext` is created early during completion to figure out, where | 17 | /// `CompletionContext` is created early during completion to figure out, where |
17 | /// exactly is the cursor, syntax-wise. | 18 | /// exactly is the cursor, syntax-wise. |
@@ -169,7 +170,17 @@ impl<'a> CompletionContext<'a> { | |||
169 | match self.token.kind() { | 170 | match self.token.kind() { |
170 | // workaroud when completion is triggered by trigger characters. | 171 | // workaroud when completion is triggered by trigger characters. |
171 | IDENT => self.original_token.text_range(), | 172 | IDENT => self.original_token.text_range(), |
172 | _ => TextRange::empty(self.offset), | 173 | _ => { |
174 | // If we haven't characters between keyword and our cursor we take the keyword start range to edit | ||
175 | if self.token.kind().is_keyword() | ||
176 | && self.offset == self.original_token.text_range().end() | ||
177 | { | ||
178 | mark::hit!(completes_bindings_from_for_with_in_prefix); | ||
179 | TextRange::empty(self.original_token.text_range().start()) | ||
180 | } else { | ||
181 | TextRange::empty(self.offset) | ||
182 | } | ||
183 | } | ||
173 | } | 184 | } |
174 | } | 185 | } |
175 | 186 | ||
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs index 440ffa31d..61565c84f 100644 --- a/crates/ra_ide/src/completion/presentation.rs +++ b/crates/ra_ide/src/completion/presentation.rs | |||
@@ -211,7 +211,7 @@ impl Completions { | |||
211 | .parameter_names | 211 | .parameter_names |
212 | .iter() | 212 | .iter() |
213 | .skip(if function_signature.has_self_param { 1 } else { 0 }) | 213 | .skip(if function_signature.has_self_param { 1 } else { 0 }) |
214 | .cloned() | 214 | .map(|name| name.trim_start_matches('_').into()) |
215 | .collect(); | 215 | .collect(); |
216 | 216 | ||
217 | builder = builder.add_call_parens(ctx, name, Params::Named(params)); | 217 | builder = builder.add_call_parens(ctx, name, Params::Named(params)); |
@@ -672,6 +672,37 @@ mod tests { | |||
672 | assert_debug_snapshot!( | 672 | assert_debug_snapshot!( |
673 | do_reference_completion( | 673 | do_reference_completion( |
674 | r" | 674 | r" |
675 | fn with_ignored_args(_foo: i32, ___bar: bool, ho_ge_: String) {} | ||
676 | fn main() { with_<|> } | ||
677 | " | ||
678 | ), | ||
679 | @r###" | ||
680 | [ | ||
681 | CompletionItem { | ||
682 | label: "main()", | ||
683 | source_range: 110..115, | ||
684 | delete: 110..115, | ||
685 | insert: "main()$0", | ||
686 | kind: Function, | ||
687 | lookup: "main", | ||
688 | detail: "fn main()", | ||
689 | }, | ||
690 | CompletionItem { | ||
691 | label: "with_ignored_args(…)", | ||
692 | source_range: 110..115, | ||
693 | delete: 110..115, | ||
694 | insert: "with_ignored_args(${1:foo}, ${2:bar}, ${3:ho_ge_})$0", | ||
695 | kind: Function, | ||
696 | lookup: "with_ignored_args", | ||
697 | detail: "fn with_ignored_args(_foo: i32, ___bar: bool, ho_ge_: String)", | ||
698 | trigger_call_info: true, | ||
699 | }, | ||
700 | ] | ||
701 | "### | ||
702 | ); | ||
703 | assert_debug_snapshot!( | ||
704 | do_reference_completion( | ||
705 | r" | ||
675 | struct S {} | 706 | struct S {} |
676 | impl S { | 707 | impl S { |
677 | fn foo(&self) {} | 708 | fn foo(&self) {} |
@@ -695,6 +726,33 @@ mod tests { | |||
695 | ] | 726 | ] |
696 | "### | 727 | "### |
697 | ); | 728 | ); |
729 | assert_debug_snapshot!( | ||
730 | do_reference_completion( | ||
731 | r" | ||
732 | struct S {} | ||
733 | impl S { | ||
734 | fn foo_ignored_args(&self, _a: bool, b: i32) {} | ||
735 | } | ||
736 | fn bar(s: &S) { | ||
737 | s.f<|> | ||
738 | } | ||
739 | " | ||
740 | ), | ||
741 | @r###" | ||
742 | [ | ||
743 | CompletionItem { | ||
744 | label: "foo_ignored_args(…)", | ||
745 | source_range: 194..195, | ||
746 | delete: 194..195, | ||
747 | insert: "foo_ignored_args(${1:a}, ${2:b})$0", | ||
748 | kind: Method, | ||
749 | lookup: "foo_ignored_args", | ||
750 | detail: "fn foo_ignored_args(&self, _a: bool, b: i32)", | ||
751 | trigger_call_info: true, | ||
752 | }, | ||
753 | ] | ||
754 | "### | ||
755 | ); | ||
698 | } | 756 | } |
699 | 757 | ||
700 | #[test] | 758 | #[test] |
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index 3d83c0f71..15dc50cf1 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -664,7 +664,7 @@ mod tests { | |||
664 | assert_debug_snapshot!(diagnostics, @r###" | 664 | assert_debug_snapshot!(diagnostics, @r###" |
665 | [ | 665 | [ |
666 | Diagnostic { | 666 | Diagnostic { |
667 | message: "Missing structure fields:\n- b", | 667 | message: "Missing structure fields:\n- b\n", |
668 | range: 224..233, | 668 | range: 224..233, |
669 | severity: Error, | 669 | severity: Error, |
670 | fix: Some( | 670 | fix: Some( |
diff --git a/crates/ra_ide/src/display.rs b/crates/ra_ide/src/display.rs index 722092de9..827c094e7 100644 --- a/crates/ra_ide/src/display.rs +++ b/crates/ra_ide/src/display.rs | |||
@@ -79,16 +79,17 @@ pub(crate) fn rust_code_markup_with_doc( | |||
79 | doc: Option<&str>, | 79 | doc: Option<&str>, |
80 | mod_path: Option<&str>, | 80 | mod_path: Option<&str>, |
81 | ) -> String { | 81 | ) -> String { |
82 | let mut buf = "```rust\n".to_owned(); | 82 | let mut buf = String::new(); |
83 | 83 | ||
84 | if let Some(mod_path) = mod_path { | 84 | if let Some(mod_path) = mod_path { |
85 | if !mod_path.is_empty() { | 85 | if !mod_path.is_empty() { |
86 | format_to!(buf, "{}\n", mod_path); | 86 | format_to!(buf, "```rust\n{}\n```\n\n", mod_path); |
87 | } | 87 | } |
88 | } | 88 | } |
89 | format_to!(buf, "{}\n```", code); | 89 | format_to!(buf, "```rust\n{}\n```", code); |
90 | 90 | ||
91 | if let Some(doc) = doc { | 91 | if let Some(doc) = doc { |
92 | format_to!(buf, "\n___"); | ||
92 | format_to!(buf, "\n\n{}", doc); | 93 | format_to!(buf, "\n\n{}", doc); |
93 | } | 94 | } |
94 | 95 | ||
diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs index 9572debd8..ca8a6a650 100644 --- a/crates/ra_ide/src/display/function_signature.rs +++ b/crates/ra_ide/src/display/function_signature.rs | |||
@@ -10,7 +10,7 @@ use std::{ | |||
10 | use hir::{Docs, Documentation, HasSource, HirDisplay}; | 10 | use hir::{Docs, Documentation, HasSource, HirDisplay}; |
11 | use ra_ide_db::RootDatabase; | 11 | use ra_ide_db::RootDatabase; |
12 | use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; | 12 | use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; |
13 | use stdx::SepBy; | 13 | use stdx::{split1, SepBy}; |
14 | 14 | ||
15 | use crate::display::{generic_parameters, where_predicates}; | 15 | use crate::display::{generic_parameters, where_predicates}; |
16 | 16 | ||
@@ -207,7 +207,16 @@ impl From<&'_ ast::FnDef> for FunctionSignature { | |||
207 | res.push(raw_param); | 207 | res.push(raw_param); |
208 | } | 208 | } |
209 | 209 | ||
210 | res.extend(param_list.params().map(|param| param.syntax().text().to_string())); | 210 | // macro-generated functions are missing whitespace |
211 | fn fmt_param(param: ast::Param) -> String { | ||
212 | let text = param.syntax().text().to_string(); | ||
213 | match split1(&text, ':') { | ||
214 | Some((left, right)) => format!("{}: {}", left.trim(), right.trim()), | ||
215 | _ => text, | ||
216 | } | ||
217 | } | ||
218 | |||
219 | res.extend(param_list.params().map(fmt_param)); | ||
211 | res_types.extend(param_list.params().map(|param| { | 220 | res_types.extend(param_list.params().map(|param| { |
212 | let param_text = param.syntax().text().to_string(); | 221 | let param_text = param.syntax().text().to_string(); |
213 | match param_text.split(':').nth(1).and_then(|it| it.get(1..)) { | 222 | match param_text.split(':').nth(1).and_then(|it| it.get(1..)) { |
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs index 5da28edd2..c7bb1e69f 100644 --- a/crates/ra_ide/src/display/navigation_target.rs +++ b/crates/ra_ide/src/display/navigation_target.rs | |||
@@ -92,15 +92,16 @@ impl NavigationTarget { | |||
92 | let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); | 92 | let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); |
93 | if let Some(src) = module.declaration_source(db) { | 93 | if let Some(src) = module.declaration_source(db) { |
94 | let frange = original_range(db, src.as_ref().map(|it| it.syntax())); | 94 | let frange = original_range(db, src.as_ref().map(|it| it.syntax())); |
95 | return NavigationTarget::from_syntax( | 95 | let mut res = NavigationTarget::from_syntax( |
96 | frange.file_id, | 96 | frange.file_id, |
97 | name, | 97 | name, |
98 | None, | 98 | None, |
99 | frange.range, | 99 | frange.range, |
100 | src.value.syntax().kind(), | 100 | src.value.syntax().kind(), |
101 | src.value.doc_comment_text(), | ||
102 | src.value.short_label(), | ||
103 | ); | 101 | ); |
102 | res.docs = src.value.doc_comment_text(); | ||
103 | res.description = src.value.short_label(); | ||
104 | return res; | ||
104 | } | 105 | } |
105 | module.to_nav(db) | 106 | module.to_nav(db) |
106 | } | 107 | } |
@@ -130,11 +131,9 @@ impl NavigationTarget { | |||
130 | } | 131 | } |
131 | 132 | ||
132 | /// Allows `NavigationTarget` to be created from a `NameOwner` | 133 | /// Allows `NavigationTarget` to be created from a `NameOwner` |
133 | fn from_named( | 134 | pub(crate) fn from_named( |
134 | db: &RootDatabase, | 135 | db: &RootDatabase, |
135 | node: InFile<&dyn ast::NameOwner>, | 136 | node: InFile<&dyn ast::NameOwner>, |
136 | docs: Option<String>, | ||
137 | description: Option<String>, | ||
138 | ) -> NavigationTarget { | 137 | ) -> NavigationTarget { |
139 | //FIXME: use `_` instead of empty string | 138 | //FIXME: use `_` instead of empty string |
140 | let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default(); | 139 | let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default(); |
@@ -148,8 +147,6 @@ impl NavigationTarget { | |||
148 | focus_range, | 147 | focus_range, |
149 | frange.range, | 148 | frange.range, |
150 | node.value.syntax().kind(), | 149 | node.value.syntax().kind(), |
151 | docs, | ||
152 | description, | ||
153 | ) | 150 | ) |
154 | } | 151 | } |
155 | 152 | ||
@@ -159,8 +156,6 @@ impl NavigationTarget { | |||
159 | focus_range: Option<TextRange>, | 156 | focus_range: Option<TextRange>, |
160 | full_range: TextRange, | 157 | full_range: TextRange, |
161 | kind: SyntaxKind, | 158 | kind: SyntaxKind, |
162 | docs: Option<String>, | ||
163 | description: Option<String>, | ||
164 | ) -> NavigationTarget { | 159 | ) -> NavigationTarget { |
165 | NavigationTarget { | 160 | NavigationTarget { |
166 | file_id, | 161 | file_id, |
@@ -169,8 +164,8 @@ impl NavigationTarget { | |||
169 | full_range, | 164 | full_range, |
170 | focus_range, | 165 | focus_range, |
171 | container_name: None, | 166 | container_name: None, |
172 | description, | 167 | description: None, |
173 | docs, | 168 | docs: None, |
174 | } | 169 | } |
175 | } | 170 | } |
176 | } | 171 | } |
@@ -238,12 +233,11 @@ where | |||
238 | { | 233 | { |
239 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { | 234 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { |
240 | let src = self.source(db); | 235 | let src = self.source(db); |
241 | NavigationTarget::from_named( | 236 | let mut res = |
242 | db, | 237 | NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner)); |
243 | src.as_ref().map(|it| it as &dyn ast::NameOwner), | 238 | res.docs = src.value.doc_comment_text(); |
244 | src.value.doc_comment_text(), | 239 | res.description = src.value.short_label(); |
245 | src.value.short_label(), | 240 | res |
246 | ) | ||
247 | } | 241 | } |
248 | } | 242 | } |
249 | 243 | ||
@@ -258,15 +252,7 @@ impl ToNav for hir::Module { | |||
258 | } | 252 | } |
259 | }; | 253 | }; |
260 | let frange = original_range(db, src.with_value(syntax)); | 254 | let frange = original_range(db, src.with_value(syntax)); |
261 | NavigationTarget::from_syntax( | 255 | NavigationTarget::from_syntax(frange.file_id, name, focus, frange.range, syntax.kind()) |
262 | frange.file_id, | ||
263 | name, | ||
264 | focus, | ||
265 | frange.range, | ||
266 | syntax.kind(), | ||
267 | None, | ||
268 | None, | ||
269 | ) | ||
270 | } | 256 | } |
271 | } | 257 | } |
272 | 258 | ||
@@ -285,8 +271,6 @@ impl ToNav for hir::ImplDef { | |||
285 | None, | 271 | None, |
286 | frange.range, | 272 | frange.range, |
287 | src.value.syntax().kind(), | 273 | src.value.syntax().kind(), |
288 | None, | ||
289 | None, | ||
290 | ) | 274 | ) |
291 | } | 275 | } |
292 | } | 276 | } |
@@ -296,12 +280,12 @@ impl ToNav for hir::Field { | |||
296 | let src = self.source(db); | 280 | let src = self.source(db); |
297 | 281 | ||
298 | match &src.value { | 282 | match &src.value { |
299 | FieldSource::Named(it) => NavigationTarget::from_named( | 283 | FieldSource::Named(it) => { |
300 | db, | 284 | let mut res = NavigationTarget::from_named(db, src.with_value(it)); |
301 | src.with_value(it), | 285 | res.docs = it.doc_comment_text(); |
302 | it.doc_comment_text(), | 286 | res.description = it.short_label(); |
303 | it.short_label(), | 287 | res |
304 | ), | 288 | } |
305 | FieldSource::Pos(it) => { | 289 | FieldSource::Pos(it) => { |
306 | let frange = original_range(db, src.with_value(it.syntax())); | 290 | let frange = original_range(db, src.with_value(it.syntax())); |
307 | NavigationTarget::from_syntax( | 291 | NavigationTarget::from_syntax( |
@@ -310,8 +294,6 @@ impl ToNav for hir::Field { | |||
310 | None, | 294 | None, |
311 | frange.range, | 295 | frange.range, |
312 | it.syntax().kind(), | 296 | it.syntax().kind(), |
313 | None, | ||
314 | None, | ||
315 | ) | 297 | ) |
316 | } | 298 | } |
317 | } | 299 | } |
@@ -322,12 +304,10 @@ impl ToNav for hir::MacroDef { | |||
322 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { | 304 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { |
323 | let src = self.source(db); | 305 | let src = self.source(db); |
324 | log::debug!("nav target {:#?}", src.value.syntax()); | 306 | log::debug!("nav target {:#?}", src.value.syntax()); |
325 | NavigationTarget::from_named( | 307 | let mut res = |
326 | db, | 308 | NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner)); |
327 | src.as_ref().map(|it| it as &dyn ast::NameOwner), | 309 | res.docs = src.value.doc_comment_text(); |
328 | src.value.doc_comment_text(), | 310 | res |
329 | None, | ||
330 | ) | ||
331 | } | 311 | } |
332 | } | 312 | } |
333 | 313 | ||
diff --git a/crates/ra_ide/src/display/structure.rs b/crates/ra_ide/src/display/structure.rs index 967eee5d2..aad5a8e4d 100644 --- a/crates/ra_ide/src/display/structure.rs +++ b/crates/ra_ide/src/display/structure.rs | |||
@@ -1,10 +1,6 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::TextRange; | ||
4 | |||
5 | use ra_syntax::{ | 1 | use ra_syntax::{ |
6 | ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, | 2 | ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, |
7 | match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent, | 3 | match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, WalkEvent, |
8 | }; | 4 | }; |
9 | 5 | ||
10 | #[derive(Debug, Clone)] | 6 | #[derive(Debug, Clone)] |
@@ -18,6 +14,19 @@ pub struct StructureNode { | |||
18 | pub deprecated: bool, | 14 | pub deprecated: bool, |
19 | } | 15 | } |
20 | 16 | ||
17 | // Feature: File Structure | ||
18 | // | ||
19 | // Provides a tree of the symbols defined in the file. Can be used to | ||
20 | // | ||
21 | // * fuzzy search symbol in a file (super useful) | ||
22 | // * draw breadcrumbs to describe the context around the cursor | ||
23 | // * draw outline of the file | ||
24 | // | ||
25 | // |=== | ||
26 | // | Editor | Shortcut | ||
27 | // | ||
28 | // | VS Code | kbd:[Ctrl+Shift+O] | ||
29 | // |=== | ||
21 | pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> { | 30 | pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> { |
22 | let mut res = Vec::new(); | 31 | let mut res = Vec::new(); |
23 | let mut stack = Vec::new(); | 32 | let mut stack = Vec::new(); |
diff --git a/crates/ra_ide/src/expand_macro.rs b/crates/ra_ide/src/expand_macro.rs index f536ba3e7..54a47aac0 100644 --- a/crates/ra_ide/src/expand_macro.rs +++ b/crates/ra_ide/src/expand_macro.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! This modules implements "expand macro" functionality in the IDE | ||
2 | |||
3 | use hir::Semantics; | 1 | use hir::Semantics; |
4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
5 | use ra_syntax::{ | 3 | use ra_syntax::{ |
@@ -14,6 +12,15 @@ pub struct ExpandedMacro { | |||
14 | pub expansion: String, | 12 | pub expansion: String, |
15 | } | 13 | } |
16 | 14 | ||
15 | // Feature: Expand Macro Recursively | ||
16 | // | ||
17 | // Shows the full macro expansion of the macro at current cursor. | ||
18 | // | ||
19 | // |=== | ||
20 | // | Editor | Action Name | ||
21 | // | ||
22 | // | VS Code | **Rust Analyzer: Expand macro recursively** | ||
23 | // |=== | ||
17 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { | 24 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { |
18 | let sema = Semantics::new(db); | 25 | let sema = Semantics::new(db); |
19 | let file = sema.parse(position.file_id); | 26 | let file = sema.parse(position.file_id); |
diff --git a/crates/ra_ide/src/extend_selection.rs b/crates/ra_ide/src/extend_selection.rs index 554594a43..a4bc93cdb 100644 --- a/crates/ra_ide/src/extend_selection.rs +++ b/crates/ra_ide/src/extend_selection.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use std::iter::successors; | 1 | use std::iter::successors; |
4 | 2 | ||
5 | use hir::Semantics; | 3 | use hir::Semantics; |
@@ -14,6 +12,16 @@ use ra_syntax::{ | |||
14 | 12 | ||
15 | use crate::FileRange; | 13 | use crate::FileRange; |
16 | 14 | ||
15 | // Feature: Extend Selection | ||
16 | // | ||
17 | // Extends the current selection to the encompassing syntactic construct | ||
18 | // (expression, statement, item, module, etc). It works with multiple cursors. | ||
19 | // | ||
20 | // |=== | ||
21 | // | Editor | Shortcut | ||
22 | // | ||
23 | // | VS Code | kbd:[Ctrl+Shift+→] | ||
24 | // |=== | ||
17 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { | 25 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { |
18 | let sema = Semantics::new(db); | 26 | let sema = Semantics::new(db); |
19 | let src = sema.parse(frange.file_id); | 27 | let src = sema.parse(frange.file_id); |
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index 90e85d419..a6c86e99c 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::Semantics; | 1 | use hir::Semantics; |
4 | use ra_ide_db::{ | 2 | use ra_ide_db::{ |
5 | defs::{classify_name, classify_name_ref}, | 3 | defs::{classify_name, classify_name_ref}, |
@@ -17,6 +15,15 @@ use crate::{ | |||
17 | FilePosition, NavigationTarget, RangeInfo, | 15 | FilePosition, NavigationTarget, RangeInfo, |
18 | }; | 16 | }; |
19 | 17 | ||
18 | // Feature: Go to Definition | ||
19 | // | ||
20 | // Navigates to the definition of an identifier. | ||
21 | // | ||
22 | // |=== | ||
23 | // | Editor | Shortcut | ||
24 | // | ||
25 | // | VS Code | kbd:[F12] | ||
26 | // |=== | ||
20 | pub(crate) fn goto_definition( | 27 | pub(crate) fn goto_definition( |
21 | db: &RootDatabase, | 28 | db: &RootDatabase, |
22 | position: FilePosition, | 29 | position: FilePosition, |
diff --git a/crates/ra_ide/src/impls.rs b/crates/ra_ide/src/goto_implementation.rs index ea2225f70..0cec0657e 100644 --- a/crates/ra_ide/src/impls.rs +++ b/crates/ra_ide/src/goto_implementation.rs | |||
@@ -1,11 +1,18 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::{Crate, ImplDef, Semantics}; | 1 | use hir::{Crate, ImplDef, Semantics}; |
4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
5 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; | 3 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; |
6 | 4 | ||
7 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; | 5 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; |
8 | 6 | ||
7 | // Feature: Go to Implementation | ||
8 | // | ||
9 | // Navigates to the impl block of structs, enums or traits. Also implemented as a code lens. | ||
10 | // | ||
11 | // |=== | ||
12 | // | Editor | Shortcut | ||
13 | // | ||
14 | // | VS Code | kbd:[Ctrl+F12] | ||
15 | // |=== | ||
9 | pub(crate) fn goto_implementation( | 16 | pub(crate) fn goto_implementation( |
10 | db: &RootDatabase, | 17 | db: &RootDatabase, |
11 | position: FilePosition, | 18 | position: FilePosition, |
diff --git a/crates/ra_ide/src/goto_type_definition.rs b/crates/ra_ide/src/goto_type_definition.rs index a84637489..91a3097fb 100644 --- a/crates/ra_ide/src/goto_type_definition.rs +++ b/crates/ra_ide/src/goto_type_definition.rs | |||
@@ -1,10 +1,17 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use ra_ide_db::RootDatabase; | 1 | use ra_ide_db::RootDatabase; |
4 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; | 2 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; |
5 | 3 | ||
6 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; | 4 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; |
7 | 5 | ||
6 | // Feature: Go to Type Definition | ||
7 | // | ||
8 | // Navigates to the type of an identifier. | ||
9 | // | ||
10 | // |=== | ||
11 | // | Editor | Action Name | ||
12 | // | ||
13 | // | VS Code | **Go to Type Definition* | ||
14 | // |=== | ||
8 | pub(crate) fn goto_type_definition( | 15 | pub(crate) fn goto_type_definition( |
9 | db: &RootDatabase, | 16 | db: &RootDatabase, |
10 | position: FilePosition, | 17 | position: FilePosition, |
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index befa977c7..9636cd0d6 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -1,28 +1,21 @@ | |||
1 | //! Logic for computing info that is displayed when the user hovers over any | 1 | use std::iter::once; |
2 | //! source code items (e.g. function call, struct field, variable symbol...) | ||
3 | 2 | ||
4 | use hir::{ | 3 | use hir::{ |
5 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, | 4 | Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, |
6 | ModuleSource, Semantics, | 5 | ModuleDef, ModuleSource, Semantics, |
7 | }; | 6 | }; |
7 | use itertools::Itertools; | ||
8 | use ra_db::SourceDatabase; | 8 | use ra_db::SourceDatabase; |
9 | use ra_ide_db::{ | 9 | use ra_ide_db::{ |
10 | defs::{classify_name, classify_name_ref, Definition}, | 10 | defs::{classify_name, classify_name_ref, Definition}, |
11 | RootDatabase, | 11 | RootDatabase, |
12 | }; | 12 | }; |
13 | use ra_syntax::{ | 13 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; |
14 | ast::{self, DocCommentsOwner}, | ||
15 | match_ast, AstNode, | ||
16 | SyntaxKind::*, | ||
17 | SyntaxToken, TokenAtOffset, | ||
18 | }; | ||
19 | 14 | ||
20 | use crate::{ | 15 | use crate::{ |
21 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, | 16 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, |
22 | FilePosition, RangeInfo, | 17 | FilePosition, RangeInfo, |
23 | }; | 18 | }; |
24 | use itertools::Itertools; | ||
25 | use std::iter::once; | ||
26 | 19 | ||
27 | /// Contains the results when hovering over an item | 20 | /// Contains the results when hovering over an item |
28 | #[derive(Debug, Default)] | 21 | #[derive(Debug, Default)] |
@@ -62,6 +55,63 @@ impl HoverResult { | |||
62 | } | 55 | } |
63 | } | 56 | } |
64 | 57 | ||
58 | // Feature: Hover | ||
59 | // | ||
60 | // Shows additional information, like type of an expression or documentation for definition when "focusing" code. | ||
61 | // Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. | ||
62 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | ||
63 | let sema = Semantics::new(db); | ||
64 | let file = sema.parse(position.file_id).syntax().clone(); | ||
65 | let token = pick_best(file.token_at_offset(position.offset))?; | ||
66 | let token = sema.descend_into_macros(token); | ||
67 | |||
68 | let mut res = HoverResult::new(); | ||
69 | |||
70 | if let Some((node, name_kind)) = match_ast! { | ||
71 | match (token.parent()) { | ||
72 | ast::NameRef(name_ref) => { | ||
73 | classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition())) | ||
74 | }, | ||
75 | ast::Name(name) => { | ||
76 | classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) | ||
77 | }, | ||
78 | _ => None, | ||
79 | } | ||
80 | } { | ||
81 | let range = sema.original_range(&node).range; | ||
82 | res.extend(hover_text_from_name_kind(db, name_kind)); | ||
83 | |||
84 | if !res.is_empty() { | ||
85 | return Some(RangeInfo::new(range, res)); | ||
86 | } | ||
87 | } | ||
88 | |||
89 | let node = token | ||
90 | .ancestors() | ||
91 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; | ||
92 | |||
93 | let ty = match_ast! { | ||
94 | match node { | ||
95 | ast::MacroCall(_it) => { | ||
96 | // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. | ||
97 | // (e.g expanding a builtin macro). So we give up here. | ||
98 | return None; | ||
99 | }, | ||
100 | ast::Expr(it) => { | ||
101 | sema.type_of_expr(&it) | ||
102 | }, | ||
103 | ast::Pat(it) => { | ||
104 | sema.type_of_pat(&it) | ||
105 | }, | ||
106 | _ => None, | ||
107 | } | ||
108 | }?; | ||
109 | |||
110 | res.extend(Some(rust_code_markup(&ty.display(db)))); | ||
111 | let range = sema.original_range(&node).range; | ||
112 | Some(RangeInfo::new(range, res)) | ||
113 | } | ||
114 | |||
65 | fn hover_text( | 115 | fn hover_text( |
66 | docs: Option<String>, | 116 | docs: Option<String>, |
67 | desc: Option<String>, | 117 | desc: Option<String>, |
@@ -114,13 +164,15 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
114 | return match def { | 164 | return match def { |
115 | Definition::Macro(it) => { | 165 | Definition::Macro(it) => { |
116 | let src = it.source(db); | 166 | let src = it.source(db); |
117 | hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value)), mod_path) | 167 | let docs = Documentation::from_ast(&src.value).map(Into::into); |
168 | hover_text(docs, Some(macro_label(&src.value)), mod_path) | ||
118 | } | 169 | } |
119 | Definition::Field(it) => { | 170 | Definition::Field(it) => { |
120 | let src = it.source(db); | 171 | let src = it.source(db); |
121 | match src.value { | 172 | match src.value { |
122 | FieldSource::Named(it) => { | 173 | FieldSource::Named(it) => { |
123 | hover_text(it.doc_comment_text(), it.short_label(), mod_path) | 174 | let docs = Documentation::from_ast(&it).map(Into::into); |
175 | hover_text(docs, it.short_label(), mod_path) | ||
124 | } | 176 | } |
125 | _ => None, | 177 | _ => None, |
126 | } | 178 | } |
@@ -128,7 +180,8 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
128 | Definition::ModuleDef(it) => match it { | 180 | Definition::ModuleDef(it) => match it { |
129 | ModuleDef::Module(it) => match it.definition_source(db).value { | 181 | ModuleDef::Module(it) => match it.definition_source(db).value { |
130 | ModuleSource::Module(it) => { | 182 | ModuleSource::Module(it) => { |
131 | hover_text(it.doc_comment_text(), it.short_label(), mod_path) | 183 | let docs = Documentation::from_ast(&it).map(Into::into); |
184 | hover_text(docs, it.short_label(), mod_path) | ||
132 | } | 185 | } |
133 | _ => None, | 186 | _ => None, |
134 | }, | 187 | }, |
@@ -153,66 +206,14 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
153 | fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<String> | 206 | fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<String> |
154 | where | 207 | where |
155 | D: HasSource<Ast = A>, | 208 | D: HasSource<Ast = A>, |
156 | A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, | 209 | A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner, |
157 | { | 210 | { |
158 | let src = def.source(db); | 211 | let src = def.source(db); |
159 | hover_text(src.value.doc_comment_text(), src.value.short_label(), mod_path) | 212 | let docs = Documentation::from_ast(&src.value).map(Into::into); |
213 | hover_text(docs, src.value.short_label(), mod_path) | ||
160 | } | 214 | } |
161 | } | 215 | } |
162 | 216 | ||
163 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | ||
164 | let sema = Semantics::new(db); | ||
165 | let file = sema.parse(position.file_id).syntax().clone(); | ||
166 | let token = pick_best(file.token_at_offset(position.offset))?; | ||
167 | let token = sema.descend_into_macros(token); | ||
168 | |||
169 | let mut res = HoverResult::new(); | ||
170 | |||
171 | if let Some((node, name_kind)) = match_ast! { | ||
172 | match (token.parent()) { | ||
173 | ast::NameRef(name_ref) => { | ||
174 | classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition())) | ||
175 | }, | ||
176 | ast::Name(name) => { | ||
177 | classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) | ||
178 | }, | ||
179 | _ => None, | ||
180 | } | ||
181 | } { | ||
182 | let range = sema.original_range(&node).range; | ||
183 | res.extend(hover_text_from_name_kind(db, name_kind)); | ||
184 | |||
185 | if !res.is_empty() { | ||
186 | return Some(RangeInfo::new(range, res)); | ||
187 | } | ||
188 | } | ||
189 | |||
190 | let node = token | ||
191 | .ancestors() | ||
192 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; | ||
193 | |||
194 | let ty = match_ast! { | ||
195 | match node { | ||
196 | ast::MacroCall(_it) => { | ||
197 | // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. | ||
198 | // (e.g expanding a builtin macro). So we give up here. | ||
199 | return None; | ||
200 | }, | ||
201 | ast::Expr(it) => { | ||
202 | sema.type_of_expr(&it) | ||
203 | }, | ||
204 | ast::Pat(it) => { | ||
205 | sema.type_of_pat(&it) | ||
206 | }, | ||
207 | _ => None, | ||
208 | } | ||
209 | }?; | ||
210 | |||
211 | res.extend(Some(rust_code_markup(&ty.display(db)))); | ||
212 | let range = sema.original_range(&node).range; | ||
213 | Some(RangeInfo::new(range, res)) | ||
214 | } | ||
215 | |||
216 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 217 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { |
217 | return tokens.max_by_key(priority); | 218 | return tokens.max_by_key(priority); |
218 | fn priority(n: &SyntaxToken) -> usize { | 219 | fn priority(n: &SyntaxToken) -> usize { |
@@ -405,7 +406,7 @@ mod tests { | |||
405 | }; | 406 | }; |
406 | } | 407 | } |
407 | "#, | 408 | "#, |
408 | &["Foo\nfield_a: u32"], | 409 | &["Foo\n```\n\n```rust\nfield_a: u32"], |
409 | ); | 410 | ); |
410 | 411 | ||
411 | // Hovering over the field in the definition | 412 | // Hovering over the field in the definition |
@@ -422,7 +423,7 @@ mod tests { | |||
422 | }; | 423 | }; |
423 | } | 424 | } |
424 | "#, | 425 | "#, |
425 | &["Foo\nfield_a: u32"], | 426 | &["Foo\n```\n\n```rust\nfield_a: u32"], |
426 | ); | 427 | ); |
427 | } | 428 | } |
428 | 429 | ||
@@ -475,7 +476,7 @@ fn main() { | |||
475 | ", | 476 | ", |
476 | ); | 477 | ); |
477 | let hover = analysis.hover(position).unwrap().unwrap(); | 478 | let hover = analysis.hover(position).unwrap().unwrap(); |
478 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\nSome")); | 479 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\n```\n\n```rust\nSome")); |
479 | 480 | ||
480 | let (analysis, position) = single_file_with_position( | 481 | let (analysis, position) = single_file_with_position( |
481 | " | 482 | " |
@@ -503,8 +504,12 @@ fn main() { | |||
503 | "#, | 504 | "#, |
504 | &[" | 505 | &[" |
505 | Option | 506 | Option |
507 | ``` | ||
508 | |||
509 | ```rust | ||
506 | None | 510 | None |
507 | ``` | 511 | ``` |
512 | ___ | ||
508 | 513 | ||
509 | The None variant | 514 | The None variant |
510 | " | 515 | " |
@@ -524,8 +529,12 @@ The None variant | |||
524 | "#, | 529 | "#, |
525 | &[" | 530 | &[" |
526 | Option | 531 | Option |
532 | ``` | ||
533 | |||
534 | ```rust | ||
527 | Some | 535 | Some |
528 | ``` | 536 | ``` |
537 | ___ | ||
529 | 538 | ||
530 | The Some variant | 539 | The Some variant |
531 | " | 540 | " |
@@ -606,7 +615,10 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
606 | ", | 615 | ", |
607 | ); | 616 | ); |
608 | let hover = analysis.hover(position).unwrap().unwrap(); | 617 | let hover = analysis.hover(position).unwrap().unwrap(); |
609 | assert_eq!(trim_markup_opt(hover.info.first()), Some("wrapper::Thing\nfn new() -> Thing")); | 618 | assert_eq!( |
619 | trim_markup_opt(hover.info.first()), | ||
620 | Some("wrapper::Thing\n```\n\n```rust\nfn new() -> Thing") | ||
621 | ); | ||
610 | } | 622 | } |
611 | 623 | ||
612 | #[test] | 624 | #[test] |
@@ -882,7 +894,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
882 | fo<|>o(); | 894 | fo<|>o(); |
883 | } | 895 | } |
884 | ", | 896 | ", |
885 | &["fn foo()\n```\n\n<- `\u{3000}` here"], | 897 | &["fn foo()\n```\n___\n\n<- `\u{3000}` here"], |
886 | ); | 898 | ); |
887 | } | 899 | } |
888 | 900 | ||
@@ -938,4 +950,106 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
938 | &["mod my"], | 950 | &["mod my"], |
939 | ); | 951 | ); |
940 | } | 952 | } |
953 | |||
954 | #[test] | ||
955 | fn test_hover_struct_doc_comment() { | ||
956 | check_hover_result( | ||
957 | r#" | ||
958 | //- /lib.rs | ||
959 | /// bar docs | ||
960 | struct Bar; | ||
961 | |||
962 | fn foo() { | ||
963 | let bar = Ba<|>r; | ||
964 | } | ||
965 | "#, | ||
966 | &["struct Bar\n```\n___\n\nbar docs"], | ||
967 | ); | ||
968 | } | ||
969 | |||
970 | #[test] | ||
971 | fn test_hover_struct_doc_attr() { | ||
972 | check_hover_result( | ||
973 | r#" | ||
974 | //- /lib.rs | ||
975 | #[doc = "bar docs"] | ||
976 | struct Bar; | ||
977 | |||
978 | fn foo() { | ||
979 | let bar = Ba<|>r; | ||
980 | } | ||
981 | "#, | ||
982 | &["struct Bar\n```\n___\n\nbar docs"], | ||
983 | ); | ||
984 | } | ||
985 | |||
986 | #[test] | ||
987 | fn test_hover_struct_doc_attr_multiple_and_mixed() { | ||
988 | check_hover_result( | ||
989 | r#" | ||
990 | //- /lib.rs | ||
991 | /// bar docs 0 | ||
992 | #[doc = "bar docs 1"] | ||
993 | #[doc = "bar docs 2"] | ||
994 | struct Bar; | ||
995 | |||
996 | fn foo() { | ||
997 | let bar = Ba<|>r; | ||
998 | } | ||
999 | "#, | ||
1000 | &["struct Bar\n```\n___\n\nbar docs 0\n\nbar docs 1\n\nbar docs 2"], | ||
1001 | ); | ||
1002 | } | ||
1003 | |||
1004 | #[test] | ||
1005 | fn test_hover_macro_generated_struct_fn_doc_comment() { | ||
1006 | check_hover_result( | ||
1007 | r#" | ||
1008 | //- /lib.rs | ||
1009 | macro_rules! bar { | ||
1010 | () => { | ||
1011 | struct Bar; | ||
1012 | impl Bar { | ||
1013 | /// Do the foo | ||
1014 | fn foo(&self) {} | ||
1015 | } | ||
1016 | } | ||
1017 | } | ||
1018 | |||
1019 | bar!(); | ||
1020 | |||
1021 | fn foo() { | ||
1022 | let bar = Bar; | ||
1023 | bar.fo<|>o(); | ||
1024 | } | ||
1025 | "#, | ||
1026 | &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\n Do the foo"], | ||
1027 | ); | ||
1028 | } | ||
1029 | |||
1030 | #[test] | ||
1031 | fn test_hover_macro_generated_struct_fn_doc_attr() { | ||
1032 | check_hover_result( | ||
1033 | r#" | ||
1034 | //- /lib.rs | ||
1035 | macro_rules! bar { | ||
1036 | () => { | ||
1037 | struct Bar; | ||
1038 | impl Bar { | ||
1039 | #[doc = "Do the foo"] | ||
1040 | fn foo(&self) {} | ||
1041 | } | ||
1042 | } | ||
1043 | } | ||
1044 | |||
1045 | bar!(); | ||
1046 | |||
1047 | fn foo() { | ||
1048 | let bar = Bar; | ||
1049 | bar.fo<|>o(); | ||
1050 | } | ||
1051 | "#, | ||
1052 | &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], | ||
1053 | ); | ||
1054 | } | ||
941 | } | 1055 | } |
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index b391f903a..75bd3c96b 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! This module defines multiple types of inlay hints and their visibility | ||
2 | |||
3 | use hir::{Adt, HirDisplay, Semantics, Type}; | 1 | use hir::{Adt, HirDisplay, Semantics, Type}; |
4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
5 | use ra_prof::profile; | 3 | use ra_prof::profile; |
@@ -39,6 +37,26 @@ pub struct InlayHint { | |||
39 | pub label: SmolStr, | 37 | pub label: SmolStr, |
40 | } | 38 | } |
41 | 39 | ||
40 | // Feature: Inlay Hints | ||
41 | // | ||
42 | // rust-analyzer shows additional information inline with the source code. | ||
43 | // Editors usually render this using read-only virtual text snippets interspersed with code. | ||
44 | // | ||
45 | // rust-analyzer shows hits for | ||
46 | // | ||
47 | // * types of local variables | ||
48 | // * names of function arguments | ||
49 | // * types of chained expressions | ||
50 | // | ||
51 | // **Note:** VS Code does not have native support for inlay hints https://github.com/microsoft/vscode/issues/16221[yet] and the hints are implemented using decorations. | ||
52 | // This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird: | ||
53 | // https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2]. | ||
54 | // | ||
55 | // |=== | ||
56 | // | Editor | Action Name | ||
57 | // | ||
58 | // | VS Code | **Rust Analyzer: Toggle inlay hints* | ||
59 | // |=== | ||
42 | pub(crate) fn inlay_hints( | 60 | pub(crate) fn inlay_hints( |
43 | db: &RootDatabase, | 61 | db: &RootDatabase, |
44 | file_id: FileId, | 62 | file_id: FileId, |
diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs index af1ade8a1..5036c1fb0 100644 --- a/crates/ra_ide/src/join_lines.rs +++ b/crates/ra_ide/src/join_lines.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use itertools::Itertools; | 1 | use itertools::Itertools; |
4 | use ra_fmt::{compute_ws, extract_trivial_expression}; | 2 | use ra_fmt::{compute_ws, extract_trivial_expression}; |
5 | use ra_syntax::{ | 3 | use ra_syntax::{ |
@@ -11,6 +9,15 @@ use ra_syntax::{ | |||
11 | }; | 9 | }; |
12 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 10 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
13 | 11 | ||
12 | // Feature: Join Lines | ||
13 | // | ||
14 | // Join selected lines into one, smartly fixing up whitespace, trailing commas, and braces. | ||
15 | // | ||
16 | // |=== | ||
17 | // | Editor | Action Name | ||
18 | // | ||
19 | // | VS Code | **Rust Analyzer: Join lines** | ||
20 | // |=== | ||
14 | pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { | 21 | pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { |
15 | let range = if range.is_empty() { | 22 | let range = if range.is_empty() { |
16 | let syntax = file.syntax(); | 23 | let syntax = file.syntax(); |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 5ac002d82..12d5716e8 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -23,6 +23,7 @@ mod completion; | |||
23 | mod runnables; | 23 | mod runnables; |
24 | mod goto_definition; | 24 | mod goto_definition; |
25 | mod goto_type_definition; | 25 | mod goto_type_definition; |
26 | mod goto_implementation; | ||
26 | mod extend_selection; | 27 | mod extend_selection; |
27 | mod hover; | 28 | mod hover; |
28 | mod call_hierarchy; | 29 | mod call_hierarchy; |
@@ -30,7 +31,6 @@ mod call_info; | |||
30 | mod syntax_highlighting; | 31 | mod syntax_highlighting; |
31 | mod parent_module; | 32 | mod parent_module; |
32 | mod references; | 33 | mod references; |
33 | mod impls; | ||
34 | mod diagnostics; | 34 | mod diagnostics; |
35 | mod syntax_tree; | 35 | mod syntax_tree; |
36 | mod folding_ranges; | 36 | mod folding_ranges; |
@@ -309,7 +309,8 @@ impl Analysis { | |||
309 | 309 | ||
310 | /// Returns an edit which should be applied when opening a new line, fixing | 310 | /// Returns an edit which should be applied when opening a new line, fixing |
311 | /// up minor stuff like continuing the comment. | 311 | /// up minor stuff like continuing the comment. |
312 | pub fn on_enter(&self, position: FilePosition) -> Cancelable<Option<SourceChange>> { | 312 | /// The edit will be a snippet (with `$0`). |
313 | pub fn on_enter(&self, position: FilePosition) -> Cancelable<Option<TextEdit>> { | ||
313 | self.with_db(|db| typing::on_enter(&db, position)) | 314 | self.with_db(|db| typing::on_enter(&db, position)) |
314 | } | 315 | } |
315 | 316 | ||
@@ -372,7 +373,7 @@ impl Analysis { | |||
372 | &self, | 373 | &self, |
373 | position: FilePosition, | 374 | position: FilePosition, |
374 | ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> { | 375 | ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> { |
375 | self.with_db(|db| impls::goto_implementation(db, position)) | 376 | self.with_db(|db| goto_implementation::goto_implementation(db, position)) |
376 | } | 377 | } |
377 | 378 | ||
378 | /// Returns the type definitions for the symbol at `position`. | 379 | /// Returns the type definitions for the symbol at `position`. |
diff --git a/crates/ra_ide/src/matching_brace.rs b/crates/ra_ide/src/matching_brace.rs index b85348706..407a9636d 100644 --- a/crates/ra_ide/src/matching_brace.rs +++ b/crates/ra_ide/src/matching_brace.rs | |||
@@ -1,7 +1,16 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextSize, T}; | 1 | use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextSize, T}; |
4 | 2 | ||
3 | // Feature: Matching Brace | ||
4 | // | ||
5 | // If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair, | ||
6 | // moves cursor to the matching brace. It uses the actual parser to determine | ||
7 | // braces, so it won't confuse generics with comparisons. | ||
8 | // | ||
9 | // |=== | ||
10 | // | Editor | Action Name | ||
11 | // | ||
12 | // | VS Code | **Rust Analyzer: Find matching brace** | ||
13 | // |=== | ||
5 | pub fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> { | 14 | pub fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> { |
6 | const BRACES: &[SyntaxKind] = | 15 | const BRACES: &[SyntaxKind] = |
7 | &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>]]; | 16 | &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>]]; |
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs index 2c13f206a..ad78d2d93 100644 --- a/crates/ra_ide/src/mock_analysis.rs +++ b/crates/ra_ide/src/mock_analysis.rs | |||
@@ -1,21 +1,81 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use std::str::FromStr; | ||
3 | use std::sync::Arc; | 4 | use std::sync::Arc; |
4 | 5 | ||
5 | use ra_cfg::CfgOptions; | 6 | use ra_cfg::CfgOptions; |
6 | use ra_db::{CrateName, Env, RelativePathBuf}; | 7 | use ra_db::{CrateName, Env, RelativePathBuf}; |
7 | use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER}; | 8 | use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER}; |
8 | 9 | ||
9 | use crate::{ | 10 | use crate::{ |
10 | Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition::Edition2018, FileId, FilePosition, | 11 | Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition, FileId, FilePosition, FileRange, |
11 | FileRange, SourceRootId, | 12 | SourceRootId, |
12 | }; | 13 | }; |
13 | 14 | ||
15 | #[derive(Debug)] | ||
16 | enum MockFileData { | ||
17 | Plain { path: String, content: String }, | ||
18 | Fixture(FixtureEntry), | ||
19 | } | ||
20 | |||
21 | impl MockFileData { | ||
22 | fn new(path: String, content: String) -> Self { | ||
23 | // `Self::Plain` causes a false warning: 'variant is never constructed: `Plain` ' | ||
24 | // see https://github.com/rust-lang/rust/issues/69018 | ||
25 | MockFileData::Plain { path, content } | ||
26 | } | ||
27 | |||
28 | fn path(&self) -> &str { | ||
29 | match self { | ||
30 | MockFileData::Plain { path, .. } => path.as_str(), | ||
31 | MockFileData::Fixture(f) => f.meta.path().as_str(), | ||
32 | } | ||
33 | } | ||
34 | |||
35 | fn content(&self) -> &str { | ||
36 | match self { | ||
37 | MockFileData::Plain { content, .. } => content, | ||
38 | MockFileData::Fixture(f) => f.text.as_str(), | ||
39 | } | ||
40 | } | ||
41 | |||
42 | fn cfg_options(&self) -> CfgOptions { | ||
43 | match self { | ||
44 | MockFileData::Fixture(f) => { | ||
45 | f.meta.cfg_options().map_or_else(Default::default, |o| o.clone()) | ||
46 | } | ||
47 | _ => CfgOptions::default(), | ||
48 | } | ||
49 | } | ||
50 | |||
51 | fn edition(&self) -> Edition { | ||
52 | match self { | ||
53 | MockFileData::Fixture(f) => { | ||
54 | f.meta.edition().map_or(Edition::Edition2018, |v| Edition::from_str(v).unwrap()) | ||
55 | } | ||
56 | _ => Edition::Edition2018, | ||
57 | } | ||
58 | } | ||
59 | |||
60 | fn env(&self) -> Env { | ||
61 | match self { | ||
62 | MockFileData::Fixture(f) => Env::from(f.meta.env()), | ||
63 | _ => Env::default(), | ||
64 | } | ||
65 | } | ||
66 | } | ||
67 | |||
68 | impl From<FixtureEntry> for MockFileData { | ||
69 | fn from(fixture: FixtureEntry) -> Self { | ||
70 | Self::Fixture(fixture) | ||
71 | } | ||
72 | } | ||
73 | |||
14 | /// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis | 74 | /// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis |
15 | /// from a set of in-memory files. | 75 | /// from a set of in-memory files. |
16 | #[derive(Debug, Default)] | 76 | #[derive(Debug, Default)] |
17 | pub struct MockAnalysis { | 77 | pub struct MockAnalysis { |
18 | files: Vec<(String, String)>, | 78 | files: Vec<MockFileData>, |
19 | } | 79 | } |
20 | 80 | ||
21 | impl MockAnalysis { | 81 | impl MockAnalysis { |
@@ -35,7 +95,7 @@ impl MockAnalysis { | |||
35 | pub fn with_files(fixture: &str) -> MockAnalysis { | 95 | pub fn with_files(fixture: &str) -> MockAnalysis { |
36 | let mut res = MockAnalysis::new(); | 96 | let mut res = MockAnalysis::new(); |
37 | for entry in parse_fixture(fixture) { | 97 | for entry in parse_fixture(fixture) { |
38 | res.add_file(&entry.meta, &entry.text); | 98 | res.add_file_fixture(entry); |
39 | } | 99 | } |
40 | res | 100 | res |
41 | } | 101 | } |
@@ -48,30 +108,44 @@ impl MockAnalysis { | |||
48 | for entry in parse_fixture(fixture) { | 108 | for entry in parse_fixture(fixture) { |
49 | if entry.text.contains(CURSOR_MARKER) { | 109 | if entry.text.contains(CURSOR_MARKER) { |
50 | assert!(position.is_none(), "only one marker (<|>) per fixture is allowed"); | 110 | assert!(position.is_none(), "only one marker (<|>) per fixture is allowed"); |
51 | position = Some(res.add_file_with_position(&entry.meta, &entry.text)); | 111 | position = Some(res.add_file_fixture_with_position(entry)); |
52 | } else { | 112 | } else { |
53 | res.add_file(&entry.meta, &entry.text); | 113 | res.add_file_fixture(entry); |
54 | } | 114 | } |
55 | } | 115 | } |
56 | let position = position.expect("expected a marker (<|>)"); | 116 | let position = position.expect("expected a marker (<|>)"); |
57 | (res, position) | 117 | (res, position) |
58 | } | 118 | } |
59 | 119 | ||
120 | pub fn add_file_fixture(&mut self, fixture: FixtureEntry) -> FileId { | ||
121 | let file_id = self.next_id(); | ||
122 | self.files.push(MockFileData::from(fixture)); | ||
123 | file_id | ||
124 | } | ||
125 | |||
126 | pub fn add_file_fixture_with_position(&mut self, mut fixture: FixtureEntry) -> FilePosition { | ||
127 | let (offset, text) = extract_offset(&fixture.text); | ||
128 | fixture.text = text; | ||
129 | let file_id = self.next_id(); | ||
130 | self.files.push(MockFileData::from(fixture)); | ||
131 | FilePosition { file_id, offset } | ||
132 | } | ||
133 | |||
60 | pub fn add_file(&mut self, path: &str, text: &str) -> FileId { | 134 | pub fn add_file(&mut self, path: &str, text: &str) -> FileId { |
61 | let file_id = FileId((self.files.len() + 1) as u32); | 135 | let file_id = self.next_id(); |
62 | self.files.push((path.to_string(), text.to_string())); | 136 | self.files.push(MockFileData::new(path.to_string(), text.to_string())); |
63 | file_id | 137 | file_id |
64 | } | 138 | } |
65 | pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition { | 139 | pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition { |
66 | let (offset, text) = extract_offset(text); | 140 | let (offset, text) = extract_offset(text); |
67 | let file_id = FileId((self.files.len() + 1) as u32); | 141 | let file_id = self.next_id(); |
68 | self.files.push((path.to_string(), text)); | 142 | self.files.push(MockFileData::new(path.to_string(), text)); |
69 | FilePosition { file_id, offset } | 143 | FilePosition { file_id, offset } |
70 | } | 144 | } |
71 | pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange { | 145 | pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange { |
72 | let (range, text) = extract_range(text); | 146 | let (range, text) = extract_range(text); |
73 | let file_id = FileId((self.files.len() + 1) as u32); | 147 | let file_id = self.next_id(); |
74 | self.files.push((path.to_string(), text)); | 148 | self.files.push(MockFileData::new(path.to_string(), text)); |
75 | FileRange { file_id, range } | 149 | FileRange { file_id, range } |
76 | } | 150 | } |
77 | pub fn id_of(&self, path: &str) -> FileId { | 151 | pub fn id_of(&self, path: &str) -> FileId { |
@@ -79,7 +153,7 @@ impl MockAnalysis { | |||
79 | .files | 153 | .files |
80 | .iter() | 154 | .iter() |
81 | .enumerate() | 155 | .enumerate() |
82 | .find(|(_, (p, _text))| path == p) | 156 | .find(|(_, data)| path == data.path()) |
83 | .expect("no file in this mock"); | 157 | .expect("no file in this mock"); |
84 | FileId(idx as u32 + 1) | 158 | FileId(idx as u32 + 1) |
85 | } | 159 | } |
@@ -90,18 +164,21 @@ impl MockAnalysis { | |||
90 | change.add_root(source_root, true); | 164 | change.add_root(source_root, true); |
91 | let mut crate_graph = CrateGraph::default(); | 165 | let mut crate_graph = CrateGraph::default(); |
92 | let mut root_crate = None; | 166 | let mut root_crate = None; |
93 | for (i, (path, contents)) in self.files.into_iter().enumerate() { | 167 | for (i, data) in self.files.into_iter().enumerate() { |
168 | let path = data.path(); | ||
94 | assert!(path.starts_with('/')); | 169 | assert!(path.starts_with('/')); |
95 | let path = RelativePathBuf::from_path(&path[1..]).unwrap(); | 170 | let path = RelativePathBuf::from_path(&path[1..]).unwrap(); |
171 | let cfg_options = data.cfg_options(); | ||
96 | let file_id = FileId(i as u32 + 1); | 172 | let file_id = FileId(i as u32 + 1); |
97 | let cfg_options = CfgOptions::default(); | 173 | let edition = data.edition(); |
174 | let env = data.env(); | ||
98 | if path == "/lib.rs" || path == "/main.rs" { | 175 | if path == "/lib.rs" || path == "/main.rs" { |
99 | root_crate = Some(crate_graph.add_crate_root( | 176 | root_crate = Some(crate_graph.add_crate_root( |
100 | file_id, | 177 | file_id, |
101 | Edition2018, | 178 | edition, |
102 | None, | 179 | None, |
103 | cfg_options, | 180 | cfg_options, |
104 | Env::default(), | 181 | env, |
105 | Default::default(), | 182 | Default::default(), |
106 | Default::default(), | 183 | Default::default(), |
107 | )); | 184 | )); |
@@ -109,10 +186,10 @@ impl MockAnalysis { | |||
109 | let crate_name = path.parent().unwrap().file_name().unwrap(); | 186 | let crate_name = path.parent().unwrap().file_name().unwrap(); |
110 | let other_crate = crate_graph.add_crate_root( | 187 | let other_crate = crate_graph.add_crate_root( |
111 | file_id, | 188 | file_id, |
112 | Edition2018, | 189 | edition, |
113 | Some(CrateName::new(crate_name).unwrap()), | 190 | Some(CrateName::new(crate_name).unwrap()), |
114 | cfg_options, | 191 | cfg_options, |
115 | Env::default(), | 192 | env, |
116 | Default::default(), | 193 | Default::default(), |
117 | Default::default(), | 194 | Default::default(), |
118 | ); | 195 | ); |
@@ -122,7 +199,7 @@ impl MockAnalysis { | |||
122 | .unwrap(); | 199 | .unwrap(); |
123 | } | 200 | } |
124 | } | 201 | } |
125 | change.add_file(source_root, file_id, path, Arc::new(contents)); | 202 | change.add_file(source_root, file_id, path, Arc::new(data.content().to_owned())); |
126 | } | 203 | } |
127 | change.set_crate_graph(crate_graph); | 204 | change.set_crate_graph(crate_graph); |
128 | host.apply_change(change); | 205 | host.apply_change(change); |
@@ -131,6 +208,10 @@ impl MockAnalysis { | |||
131 | pub fn analysis(self) -> Analysis { | 208 | pub fn analysis(self) -> Analysis { |
132 | self.analysis_host().analysis() | 209 | self.analysis_host().analysis() |
133 | } | 210 | } |
211 | |||
212 | fn next_id(&self) -> FileId { | ||
213 | FileId((self.files.len() + 1) as u32) | ||
214 | } | ||
134 | } | 215 | } |
135 | 216 | ||
136 | /// Creates analysis from a multi-file fixture, returns positions marked with <|>. | 217 | /// Creates analysis from a multi-file fixture, returns positions marked with <|>. |
diff --git a/crates/ra_ide/src/parent_module.rs b/crates/ra_ide/src/parent_module.rs index a083fb1eb..fa1535da5 100644 --- a/crates/ra_ide/src/parent_module.rs +++ b/crates/ra_ide/src/parent_module.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::Semantics; | 1 | use hir::Semantics; |
4 | use ra_db::{CrateId, FileId, FilePosition}; | 2 | use ra_db::{CrateId, FileId, FilePosition}; |
5 | use ra_ide_db::RootDatabase; | 3 | use ra_ide_db::RootDatabase; |
@@ -11,6 +9,16 @@ use test_utils::mark; | |||
11 | 9 | ||
12 | use crate::NavigationTarget; | 10 | use crate::NavigationTarget; |
13 | 11 | ||
12 | // Feature: Parent Module | ||
13 | // | ||
14 | // Navigates to the parent module of the current module. | ||
15 | // | ||
16 | // |=== | ||
17 | // | Editor | Action Name | ||
18 | // | ||
19 | // | VS Code | **Rust Analyzer: Locate parent module** | ||
20 | // |=== | ||
21 | |||
14 | /// This returns `Vec` because a module may be included from several places. We | 22 | /// This returns `Vec` because a module may be included from several places. We |
15 | /// don't handle this case yet though, so the Vec has length at most one. | 23 | /// don't handle this case yet though, so the Vec has length at most one. |
16 | pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { | 24 | pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { |
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs index 96444bf6a..bb40d2043 100644 --- a/crates/ra_ide/src/references.rs +++ b/crates/ra_ide/src/references.rs | |||
@@ -615,6 +615,33 @@ mod tests { | |||
615 | ); | 615 | ); |
616 | } | 616 | } |
617 | 617 | ||
618 | #[test] | ||
619 | fn test_find_all_refs_nested_module() { | ||
620 | let code = r#" | ||
621 | //- /lib.rs | ||
622 | mod foo { | ||
623 | mod bar; | ||
624 | } | ||
625 | |||
626 | fn f<|>() {} | ||
627 | |||
628 | //- /foo/bar.rs | ||
629 | use crate::f; | ||
630 | |||
631 | fn g() { | ||
632 | f(); | ||
633 | } | ||
634 | "#; | ||
635 | |||
636 | let (analysis, pos) = analysis_and_position(code); | ||
637 | let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); | ||
638 | check_result( | ||
639 | refs, | ||
640 | "f FN_DEF FileId(1) 25..34 28..29 Other", | ||
641 | &["FileId(2) 11..12 Other", "FileId(2) 27..28 StructLiteral"], | ||
642 | ); | ||
643 | } | ||
644 | |||
618 | fn get_all_refs(text: &str) -> ReferenceSearchResult { | 645 | fn get_all_refs(text: &str) -> ReferenceSearchResult { |
619 | let (analysis, position) = single_file_with_position(text); | 646 | let (analysis, position) = single_file_with_position(text); |
620 | analysis.find_all_refs(position, None).unwrap().unwrap() | 647 | analysis.find_all_refs(position, None).unwrap().unwrap() |
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index 131b8f307..f32ce0d22 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs | |||
@@ -1,21 +1,21 @@ | |||
1 | //! FIXME: write short doc here | 1 | use std::fmt; |
2 | 2 | ||
3 | use hir::{AsAssocItem, Semantics}; | 3 | use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; |
4 | use itertools::Itertools; | 4 | use itertools::Itertools; |
5 | use ra_cfg::CfgExpr; | ||
5 | use ra_ide_db::RootDatabase; | 6 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 7 | use ra_syntax::{ |
7 | ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner}, | 8 | ast::{self, AstNode, AttrsOwner, DocCommentsOwner, ModuleItemOwner, NameOwner}, |
8 | match_ast, SyntaxNode, TextRange, | 9 | match_ast, SyntaxNode, |
9 | }; | 10 | }; |
10 | 11 | ||
11 | use crate::FileId; | 12 | use crate::{display::ToNav, FileId, NavigationTarget}; |
12 | use ast::DocCommentsOwner; | ||
13 | use std::fmt::Display; | ||
14 | 13 | ||
15 | #[derive(Debug)] | 14 | #[derive(Debug)] |
16 | pub struct Runnable { | 15 | pub struct Runnable { |
17 | pub range: TextRange, | 16 | pub nav: NavigationTarget, |
18 | pub kind: RunnableKind, | 17 | pub kind: RunnableKind, |
18 | pub cfg_exprs: Vec<CfgExpr>, | ||
19 | } | 19 | } |
20 | 20 | ||
21 | #[derive(Debug)] | 21 | #[derive(Debug)] |
@@ -24,8 +24,8 @@ pub enum TestId { | |||
24 | Path(String), | 24 | Path(String), |
25 | } | 25 | } |
26 | 26 | ||
27 | impl Display for TestId { | 27 | impl fmt::Display for TestId { |
28 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | 28 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
29 | match self { | 29 | match self { |
30 | TestId::Name(name) => write!(f, "{}", name), | 30 | TestId::Name(name) => write!(f, "{}", name), |
31 | TestId::Path(path) => write!(f, "{}", path), | 31 | TestId::Path(path) => write!(f, "{}", path), |
@@ -42,32 +42,47 @@ pub enum RunnableKind { | |||
42 | Bin, | 42 | Bin, |
43 | } | 43 | } |
44 | 44 | ||
45 | // Feature: Run | ||
46 | // | ||
47 | // Shows a popup suggesting to run a test/benchmark/binary **at the current cursor | ||
48 | // location**. Super useful for repeatedly running just a single test. Do bind this | ||
49 | // to a shortcut! | ||
50 | // | ||
51 | // |=== | ||
52 | // | Editor | Action Name | ||
53 | // | ||
54 | // | VS Code | **Rust Analyzer: Run** | ||
55 | // |=== | ||
45 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { | 56 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { |
46 | let sema = Semantics::new(db); | 57 | let sema = Semantics::new(db); |
47 | let source_file = sema.parse(file_id); | 58 | let source_file = sema.parse(file_id); |
48 | source_file.syntax().descendants().filter_map(|i| runnable(&sema, i)).collect() | 59 | source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect() |
49 | } | 60 | } |
50 | 61 | ||
51 | fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { | 62 | fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> { |
52 | match_ast! { | 63 | match_ast! { |
53 | match item { | 64 | match item { |
54 | ast::FnDef(it) => runnable_fn(sema, it), | 65 | ast::FnDef(it) => runnable_fn(sema, it, file_id), |
55 | ast::Module(it) => runnable_mod(sema, it), | 66 | ast::Module(it) => runnable_mod(sema, it, file_id), |
56 | _ => None, | 67 | _ => None, |
57 | } | 68 | } |
58 | } | 69 | } |
59 | } | 70 | } |
60 | 71 | ||
61 | fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Runnable> { | 72 | fn runnable_fn( |
73 | sema: &Semantics<RootDatabase>, | ||
74 | fn_def: ast::FnDef, | ||
75 | file_id: FileId, | ||
76 | ) -> Option<Runnable> { | ||
62 | let name_string = fn_def.name()?.text().to_string(); | 77 | let name_string = fn_def.name()?.text().to_string(); |
63 | 78 | ||
64 | let kind = if name_string == "main" { | 79 | let kind = if name_string == "main" { |
65 | RunnableKind::Bin | 80 | RunnableKind::Bin |
66 | } else { | 81 | } else { |
67 | let test_id = if let Some(module) = sema.to_def(&fn_def).map(|def| def.module(sema.db)) { | 82 | let test_id = match sema.to_def(&fn_def).map(|def| def.module(sema.db)) { |
68 | let def = sema.to_def(&fn_def)?; | 83 | Some(module) => { |
69 | let impl_trait_name = | 84 | let def = sema.to_def(&fn_def)?; |
70 | def.as_assoc_item(sema.db).and_then(|assoc_item| { | 85 | let impl_trait_name = def.as_assoc_item(sema.db).and_then(|assoc_item| { |
71 | match assoc_item.container(sema.db) { | 86 | match assoc_item.container(sema.db) { |
72 | hir::AssocItemContainer::Trait(trait_item) => { | 87 | hir::AssocItemContainer::Trait(trait_item) => { |
73 | Some(trait_item.name(sema.db).to_string()) | 88 | Some(trait_item.name(sema.db).to_string()) |
@@ -79,25 +94,25 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run | |||
79 | } | 94 | } |
80 | }); | 95 | }); |
81 | 96 | ||
82 | let path_iter = module | 97 | let path_iter = module |
83 | .path_to_root(sema.db) | 98 | .path_to_root(sema.db) |
84 | .into_iter() | 99 | .into_iter() |
85 | .rev() | 100 | .rev() |
86 | .filter_map(|it| it.name(sema.db)) | 101 | .filter_map(|it| it.name(sema.db)) |
87 | .map(|name| name.to_string()); | 102 | .map(|name| name.to_string()); |
88 | 103 | ||
89 | let path = if let Some(impl_trait_name) = impl_trait_name { | 104 | let path = if let Some(impl_trait_name) = impl_trait_name { |
90 | path_iter | 105 | path_iter |
91 | .chain(std::iter::once(impl_trait_name)) | 106 | .chain(std::iter::once(impl_trait_name)) |
92 | .chain(std::iter::once(name_string)) | 107 | .chain(std::iter::once(name_string)) |
93 | .join("::") | 108 | .join("::") |
94 | } else { | 109 | } else { |
95 | path_iter.chain(std::iter::once(name_string)).join("::") | 110 | path_iter.chain(std::iter::once(name_string)).join("::") |
96 | }; | 111 | }; |
97 | 112 | ||
98 | TestId::Path(path) | 113 | TestId::Path(path) |
99 | } else { | 114 | } |
100 | TestId::Name(name_string) | 115 | None => TestId::Name(name_string), |
101 | }; | 116 | }; |
102 | 117 | ||
103 | if has_test_related_attribute(&fn_def) { | 118 | if has_test_related_attribute(&fn_def) { |
@@ -111,7 +126,13 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run | |||
111 | return None; | 126 | return None; |
112 | } | 127 | } |
113 | }; | 128 | }; |
114 | Some(Runnable { range: fn_def.syntax().text_range(), kind }) | 129 | |
130 | let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def)); | ||
131 | let cfg_exprs = | ||
132 | attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); | ||
133 | |||
134 | let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def)); | ||
135 | Some(Runnable { nav, kind, cfg_exprs }) | ||
115 | } | 136 | } |
116 | 137 | ||
117 | #[derive(Debug)] | 138 | #[derive(Debug)] |
@@ -147,7 +168,11 @@ fn has_doc_test(fn_def: &ast::FnDef) -> bool { | |||
147 | fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```")) | 168 | fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```")) |
148 | } | 169 | } |
149 | 170 | ||
150 | fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> { | 171 | fn runnable_mod( |
172 | sema: &Semantics<RootDatabase>, | ||
173 | module: ast::Module, | ||
174 | file_id: FileId, | ||
175 | ) -> Option<Runnable> { | ||
151 | let has_test_function = module | 176 | let has_test_function = module |
152 | .item_list()? | 177 | .item_list()? |
153 | .items() | 178 | .items() |
@@ -159,12 +184,21 @@ fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<R | |||
159 | if !has_test_function { | 184 | if !has_test_function { |
160 | return None; | 185 | return None; |
161 | } | 186 | } |
162 | let range = module.syntax().text_range(); | 187 | let module_def = sema.to_def(&module)?; |
163 | let module = sema.to_def(&module)?; | ||
164 | 188 | ||
165 | let path = | 189 | let path = module_def |
166 | module.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::"); | 190 | .path_to_root(sema.db) |
167 | Some(Runnable { range, kind: RunnableKind::TestMod { path } }) | 191 | .into_iter() |
192 | .rev() | ||
193 | .filter_map(|it| it.name(sema.db)) | ||
194 | .join("::"); | ||
195 | |||
196 | let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module)); | ||
197 | let cfg_exprs = | ||
198 | attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); | ||
199 | |||
200 | let nav = module_def.to_nav(sema.db); | ||
201 | Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs }) | ||
168 | } | 202 | } |
169 | 203 | ||
170 | #[cfg(test)] | 204 | #[cfg(test)] |
@@ -194,11 +228,38 @@ mod tests { | |||
194 | @r###" | 228 | @r###" |
195 | [ | 229 | [ |
196 | Runnable { | 230 | Runnable { |
197 | range: 1..21, | 231 | nav: NavigationTarget { |
232 | file_id: FileId( | ||
233 | 1, | ||
234 | ), | ||
235 | full_range: 1..21, | ||
236 | name: "main", | ||
237 | kind: FN_DEF, | ||
238 | focus_range: Some( | ||
239 | 12..16, | ||
240 | ), | ||
241 | container_name: None, | ||
242 | description: None, | ||
243 | docs: None, | ||
244 | }, | ||
198 | kind: Bin, | 245 | kind: Bin, |
246 | cfg_exprs: [], | ||
199 | }, | 247 | }, |
200 | Runnable { | 248 | Runnable { |
201 | range: 22..46, | 249 | nav: NavigationTarget { |
250 | file_id: FileId( | ||
251 | 1, | ||
252 | ), | ||
253 | full_range: 22..46, | ||
254 | name: "test_foo", | ||
255 | kind: FN_DEF, | ||
256 | focus_range: Some( | ||
257 | 33..41, | ||
258 | ), | ||
259 | container_name: None, | ||
260 | description: None, | ||
261 | docs: None, | ||
262 | }, | ||
202 | kind: Test { | 263 | kind: Test { |
203 | test_id: Path( | 264 | test_id: Path( |
204 | "test_foo", | 265 | "test_foo", |
@@ -207,9 +268,23 @@ mod tests { | |||
207 | ignore: false, | 268 | ignore: false, |
208 | }, | 269 | }, |
209 | }, | 270 | }, |
271 | cfg_exprs: [], | ||
210 | }, | 272 | }, |
211 | Runnable { | 273 | Runnable { |
212 | range: 47..81, | 274 | nav: NavigationTarget { |
275 | file_id: FileId( | ||
276 | 1, | ||
277 | ), | ||
278 | full_range: 47..81, | ||
279 | name: "test_foo", | ||
280 | kind: FN_DEF, | ||
281 | focus_range: Some( | ||
282 | 68..76, | ||
283 | ), | ||
284 | container_name: None, | ||
285 | description: None, | ||
286 | docs: None, | ||
287 | }, | ||
213 | kind: Test { | 288 | kind: Test { |
214 | test_id: Path( | 289 | test_id: Path( |
215 | "test_foo", | 290 | "test_foo", |
@@ -218,6 +293,7 @@ mod tests { | |||
218 | ignore: true, | 293 | ignore: true, |
219 | }, | 294 | }, |
220 | }, | 295 | }, |
296 | cfg_exprs: [], | ||
221 | }, | 297 | }, |
222 | ] | 298 | ] |
223 | "### | 299 | "### |
@@ -243,16 +319,44 @@ mod tests { | |||
243 | @r###" | 319 | @r###" |
244 | [ | 320 | [ |
245 | Runnable { | 321 | Runnable { |
246 | range: 1..21, | 322 | nav: NavigationTarget { |
323 | file_id: FileId( | ||
324 | 1, | ||
325 | ), | ||
326 | full_range: 1..21, | ||
327 | name: "main", | ||
328 | kind: FN_DEF, | ||
329 | focus_range: Some( | ||
330 | 12..16, | ||
331 | ), | ||
332 | container_name: None, | ||
333 | description: None, | ||
334 | docs: None, | ||
335 | }, | ||
247 | kind: Bin, | 336 | kind: Bin, |
337 | cfg_exprs: [], | ||
248 | }, | 338 | }, |
249 | Runnable { | 339 | Runnable { |
250 | range: 22..64, | 340 | nav: NavigationTarget { |
341 | file_id: FileId( | ||
342 | 1, | ||
343 | ), | ||
344 | full_range: 22..64, | ||
345 | name: "foo", | ||
346 | kind: FN_DEF, | ||
347 | focus_range: Some( | ||
348 | 56..59, | ||
349 | ), | ||
350 | container_name: None, | ||
351 | description: None, | ||
352 | docs: None, | ||
353 | }, | ||
251 | kind: DocTest { | 354 | kind: DocTest { |
252 | test_id: Path( | 355 | test_id: Path( |
253 | "foo", | 356 | "foo", |
254 | ), | 357 | ), |
255 | }, | 358 | }, |
359 | cfg_exprs: [], | ||
256 | }, | 360 | }, |
257 | ] | 361 | ] |
258 | "### | 362 | "### |
@@ -281,16 +385,44 @@ mod tests { | |||
281 | @r###" | 385 | @r###" |
282 | [ | 386 | [ |
283 | Runnable { | 387 | Runnable { |
284 | range: 1..21, | 388 | nav: NavigationTarget { |
389 | file_id: FileId( | ||
390 | 1, | ||
391 | ), | ||
392 | full_range: 1..21, | ||
393 | name: "main", | ||
394 | kind: FN_DEF, | ||
395 | focus_range: Some( | ||
396 | 12..16, | ||
397 | ), | ||
398 | container_name: None, | ||
399 | description: None, | ||
400 | docs: None, | ||
401 | }, | ||
285 | kind: Bin, | 402 | kind: Bin, |
403 | cfg_exprs: [], | ||
286 | }, | 404 | }, |
287 | Runnable { | 405 | Runnable { |
288 | range: 51..105, | 406 | nav: NavigationTarget { |
407 | file_id: FileId( | ||
408 | 1, | ||
409 | ), | ||
410 | full_range: 51..105, | ||
411 | name: "foo", | ||
412 | kind: FN_DEF, | ||
413 | focus_range: Some( | ||
414 | 97..100, | ||
415 | ), | ||
416 | container_name: None, | ||
417 | description: None, | ||
418 | docs: None, | ||
419 | }, | ||
289 | kind: DocTest { | 420 | kind: DocTest { |
290 | test_id: Path( | 421 | test_id: Path( |
291 | "Data::foo", | 422 | "Data::foo", |
292 | ), | 423 | ), |
293 | }, | 424 | }, |
425 | cfg_exprs: [], | ||
294 | }, | 426 | }, |
295 | ] | 427 | ] |
296 | "### | 428 | "### |
@@ -314,13 +446,40 @@ mod tests { | |||
314 | @r###" | 446 | @r###" |
315 | [ | 447 | [ |
316 | Runnable { | 448 | Runnable { |
317 | range: 1..59, | 449 | nav: NavigationTarget { |
450 | file_id: FileId( | ||
451 | 1, | ||
452 | ), | ||
453 | full_range: 1..59, | ||
454 | name: "test_mod", | ||
455 | kind: MODULE, | ||
456 | focus_range: Some( | ||
457 | 13..21, | ||
458 | ), | ||
459 | container_name: None, | ||
460 | description: None, | ||
461 | docs: None, | ||
462 | }, | ||
318 | kind: TestMod { | 463 | kind: TestMod { |
319 | path: "test_mod", | 464 | path: "test_mod", |
320 | }, | 465 | }, |
466 | cfg_exprs: [], | ||
321 | }, | 467 | }, |
322 | Runnable { | 468 | Runnable { |
323 | range: 28..57, | 469 | nav: NavigationTarget { |
470 | file_id: FileId( | ||
471 | 1, | ||
472 | ), | ||
473 | full_range: 28..57, | ||
474 | name: "test_foo1", | ||
475 | kind: FN_DEF, | ||
476 | focus_range: Some( | ||
477 | 43..52, | ||
478 | ), | ||
479 | container_name: None, | ||
480 | description: None, | ||
481 | docs: None, | ||
482 | }, | ||
324 | kind: Test { | 483 | kind: Test { |
325 | test_id: Path( | 484 | test_id: Path( |
326 | "test_mod::test_foo1", | 485 | "test_mod::test_foo1", |
@@ -329,6 +488,7 @@ mod tests { | |||
329 | ignore: false, | 488 | ignore: false, |
330 | }, | 489 | }, |
331 | }, | 490 | }, |
491 | cfg_exprs: [], | ||
332 | }, | 492 | }, |
333 | ] | 493 | ] |
334 | "### | 494 | "### |
@@ -354,13 +514,40 @@ mod tests { | |||
354 | @r###" | 514 | @r###" |
355 | [ | 515 | [ |
356 | Runnable { | 516 | Runnable { |
357 | range: 23..85, | 517 | nav: NavigationTarget { |
518 | file_id: FileId( | ||
519 | 1, | ||
520 | ), | ||
521 | full_range: 23..85, | ||
522 | name: "test_mod", | ||
523 | kind: MODULE, | ||
524 | focus_range: Some( | ||
525 | 27..35, | ||
526 | ), | ||
527 | container_name: None, | ||
528 | description: None, | ||
529 | docs: None, | ||
530 | }, | ||
358 | kind: TestMod { | 531 | kind: TestMod { |
359 | path: "foo::test_mod", | 532 | path: "foo::test_mod", |
360 | }, | 533 | }, |
534 | cfg_exprs: [], | ||
361 | }, | 535 | }, |
362 | Runnable { | 536 | Runnable { |
363 | range: 46..79, | 537 | nav: NavigationTarget { |
538 | file_id: FileId( | ||
539 | 1, | ||
540 | ), | ||
541 | full_range: 46..79, | ||
542 | name: "test_foo1", | ||
543 | kind: FN_DEF, | ||
544 | focus_range: Some( | ||
545 | 65..74, | ||
546 | ), | ||
547 | container_name: None, | ||
548 | description: None, | ||
549 | docs: None, | ||
550 | }, | ||
364 | kind: Test { | 551 | kind: Test { |
365 | test_id: Path( | 552 | test_id: Path( |
366 | "foo::test_mod::test_foo1", | 553 | "foo::test_mod::test_foo1", |
@@ -369,6 +556,7 @@ mod tests { | |||
369 | ignore: false, | 556 | ignore: false, |
370 | }, | 557 | }, |
371 | }, | 558 | }, |
559 | cfg_exprs: [], | ||
372 | }, | 560 | }, |
373 | ] | 561 | ] |
374 | "### | 562 | "### |
@@ -396,13 +584,40 @@ mod tests { | |||
396 | @r###" | 584 | @r###" |
397 | [ | 585 | [ |
398 | Runnable { | 586 | Runnable { |
399 | range: 41..115, | 587 | nav: NavigationTarget { |
588 | file_id: FileId( | ||
589 | 1, | ||
590 | ), | ||
591 | full_range: 41..115, | ||
592 | name: "test_mod", | ||
593 | kind: MODULE, | ||
594 | focus_range: Some( | ||
595 | 45..53, | ||
596 | ), | ||
597 | container_name: None, | ||
598 | description: None, | ||
599 | docs: None, | ||
600 | }, | ||
400 | kind: TestMod { | 601 | kind: TestMod { |
401 | path: "foo::bar::test_mod", | 602 | path: "foo::bar::test_mod", |
402 | }, | 603 | }, |
604 | cfg_exprs: [], | ||
403 | }, | 605 | }, |
404 | Runnable { | 606 | Runnable { |
405 | range: 68..105, | 607 | nav: NavigationTarget { |
608 | file_id: FileId( | ||
609 | 1, | ||
610 | ), | ||
611 | full_range: 68..105, | ||
612 | name: "test_foo1", | ||
613 | kind: FN_DEF, | ||
614 | focus_range: Some( | ||
615 | 91..100, | ||
616 | ), | ||
617 | container_name: None, | ||
618 | description: None, | ||
619 | docs: None, | ||
620 | }, | ||
406 | kind: Test { | 621 | kind: Test { |
407 | test_id: Path( | 622 | test_id: Path( |
408 | "foo::bar::test_mod::test_foo1", | 623 | "foo::bar::test_mod::test_foo1", |
@@ -411,6 +626,115 @@ mod tests { | |||
411 | ignore: false, | 626 | ignore: false, |
412 | }, | 627 | }, |
413 | }, | 628 | }, |
629 | cfg_exprs: [], | ||
630 | }, | ||
631 | ] | ||
632 | "### | ||
633 | ); | ||
634 | } | ||
635 | |||
636 | #[test] | ||
637 | fn test_runnables_with_feature() { | ||
638 | let (analysis, pos) = analysis_and_position( | ||
639 | r#" | ||
640 | //- /lib.rs crate:foo cfg:feature=foo | ||
641 | <|> //empty | ||
642 | #[test] | ||
643 | #[cfg(feature = "foo")] | ||
644 | fn test_foo1() {} | ||
645 | "#, | ||
646 | ); | ||
647 | let runnables = analysis.runnables(pos.file_id).unwrap(); | ||
648 | assert_debug_snapshot!(&runnables, | ||
649 | @r###" | ||
650 | [ | ||
651 | Runnable { | ||
652 | nav: NavigationTarget { | ||
653 | file_id: FileId( | ||
654 | 1, | ||
655 | ), | ||
656 | full_range: 1..58, | ||
657 | name: "test_foo1", | ||
658 | kind: FN_DEF, | ||
659 | focus_range: Some( | ||
660 | 44..53, | ||
661 | ), | ||
662 | container_name: None, | ||
663 | description: None, | ||
664 | docs: None, | ||
665 | }, | ||
666 | kind: Test { | ||
667 | test_id: Path( | ||
668 | "test_foo1", | ||
669 | ), | ||
670 | attr: TestAttr { | ||
671 | ignore: false, | ||
672 | }, | ||
673 | }, | ||
674 | cfg_exprs: [ | ||
675 | KeyValue { | ||
676 | key: "feature", | ||
677 | value: "foo", | ||
678 | }, | ||
679 | ], | ||
680 | }, | ||
681 | ] | ||
682 | "### | ||
683 | ); | ||
684 | } | ||
685 | |||
686 | #[test] | ||
687 | fn test_runnables_with_features() { | ||
688 | let (analysis, pos) = analysis_and_position( | ||
689 | r#" | ||
690 | //- /lib.rs crate:foo cfg:feature=foo,feature=bar | ||
691 | <|> //empty | ||
692 | #[test] | ||
693 | #[cfg(all(feature = "foo", feature = "bar"))] | ||
694 | fn test_foo1() {} | ||
695 | "#, | ||
696 | ); | ||
697 | let runnables = analysis.runnables(pos.file_id).unwrap(); | ||
698 | assert_debug_snapshot!(&runnables, | ||
699 | @r###" | ||
700 | [ | ||
701 | Runnable { | ||
702 | nav: NavigationTarget { | ||
703 | file_id: FileId( | ||
704 | 1, | ||
705 | ), | ||
706 | full_range: 1..80, | ||
707 | name: "test_foo1", | ||
708 | kind: FN_DEF, | ||
709 | focus_range: Some( | ||
710 | 66..75, | ||
711 | ), | ||
712 | container_name: None, | ||
713 | description: None, | ||
714 | docs: None, | ||
715 | }, | ||
716 | kind: Test { | ||
717 | test_id: Path( | ||
718 | "test_foo1", | ||
719 | ), | ||
720 | attr: TestAttr { | ||
721 | ignore: false, | ||
722 | }, | ||
723 | }, | ||
724 | cfg_exprs: [ | ||
725 | All( | ||
726 | [ | ||
727 | KeyValue { | ||
728 | key: "feature", | ||
729 | value: "foo", | ||
730 | }, | ||
731 | KeyValue { | ||
732 | key: "feature", | ||
733 | value: "bar", | ||
734 | }, | ||
735 | ], | ||
736 | ), | ||
737 | ], | ||
414 | }, | 738 | }, |
415 | ] | 739 | ] |
416 | "### | 740 | "### |
diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/src/snapshots/highlight_injection.html index ea026d7a0..fcdc98201 100644 --- a/crates/ra_ide/src/snapshots/highlight_injection.html +++ b/crates/ra_ide/src/snapshots/highlight_injection.html | |||
@@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
10 | .string_literal { color: #CC9393; } | 10 | .string_literal { color: #CC9393; } |
11 | .field { color: #94BFF3; } | 11 | .field { color: #94BFF3; } |
12 | .function { color: #93E0E3; } | 12 | .function { color: #93E0E3; } |
13 | .operator.unsafe { color: #E28C14; } | ||
13 | .parameter { color: #94BFF3; } | 14 | .parameter { color: #94BFF3; } |
14 | .text { color: #DCDCCC; } | 15 | .text { color: #DCDCCC; } |
15 | .type { color: #7CB8BB; } | 16 | .type { color: #7CB8BB; } |
@@ -17,6 +18,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
17 | .type_param { color: #DFAF8F; } | 18 | .type_param { color: #DFAF8F; } |
18 | .attribute { color: #94BFF3; } | 19 | .attribute { color: #94BFF3; } |
19 | .numeric_literal { color: #BFEBBF; } | 20 | .numeric_literal { color: #BFEBBF; } |
21 | .bool_literal { color: #BFE6EB; } | ||
20 | .macro { color: #94BFF3; } | 22 | .macro { color: #94BFF3; } |
21 | .module { color: #AFD8AF; } | 23 | .module { color: #AFD8AF; } |
22 | .variable { color: #DCDCCC; } | 24 | .variable { color: #DCDCCC; } |
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html index 752b487e8..e97192b61 100644 --- a/crates/ra_ide/src/snapshots/highlight_strings.html +++ b/crates/ra_ide/src/snapshots/highlight_strings.html | |||
@@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
10 | .string_literal { color: #CC9393; } | 10 | .string_literal { color: #CC9393; } |
11 | .field { color: #94BFF3; } | 11 | .field { color: #94BFF3; } |
12 | .function { color: #93E0E3; } | 12 | .function { color: #93E0E3; } |
13 | .operator.unsafe { color: #E28C14; } | ||
13 | .parameter { color: #94BFF3; } | 14 | .parameter { color: #94BFF3; } |
14 | .text { color: #DCDCCC; } | 15 | .text { color: #DCDCCC; } |
15 | .type { color: #7CB8BB; } | 16 | .type { color: #7CB8BB; } |
@@ -17,6 +18,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
17 | .type_param { color: #DFAF8F; } | 18 | .type_param { color: #DFAF8F; } |
18 | .attribute { color: #94BFF3; } | 19 | .attribute { color: #94BFF3; } |
19 | .numeric_literal { color: #BFEBBF; } | 20 | .numeric_literal { color: #BFEBBF; } |
21 | .bool_literal { color: #BFE6EB; } | ||
20 | .macro { color: #94BFF3; } | 22 | .macro { color: #94BFF3; } |
21 | .module { color: #AFD8AF; } | 23 | .module { color: #AFD8AF; } |
22 | .variable { color: #DCDCCC; } | 24 | .variable { color: #DCDCCC; } |
@@ -51,6 +53,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
51 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">argument</span><span class="format_specifier">}</span><span class="string_literal">"</span>, argument = <span class="string_literal">"test"</span>); <span class="comment">// => "test"</span> | 53 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">argument</span><span class="format_specifier">}</span><span class="string_literal">"</span>, argument = <span class="string_literal">"test"</span>); <span class="comment">// => "test"</span> |
52 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, name = <span class="numeric_literal">2</span>); <span class="comment">// => "2 1"</span> | 54 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, name = <span class="numeric_literal">2</span>); <span class="comment">// => "2 1"</span> |
53 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">a</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">c</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">b</span><span class="format_specifier">}</span><span class="string_literal">"</span>, a=<span class="string_literal">"a"</span>, b=<span class="char_literal">'b'</span>, c=<span class="numeric_literal">3</span>); <span class="comment">// => "a 3 b"</span> | 55 | <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">a</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">c</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">b</span><span class="format_specifier">}</span><span class="string_literal">"</span>, a=<span class="string_literal">"a"</span>, b=<span class="char_literal">'b'</span>, c=<span class="numeric_literal">3</span>); <span class="comment">// => "a 3 b"</span> |
56 | <span class="macro">println!</span>(<span class="string_literal">"{{</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">}}"</span>, <span class="numeric_literal">2</span>); <span class="comment">// => "{2}"</span> | ||
54 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>); | 57 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>); |
55 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>); | 58 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>); |
56 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>); | 59 | <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>); |
diff --git a/crates/ra_ide/src/snapshots/highlight_unsafe.html b/crates/ra_ide/src/snapshots/highlight_unsafe.html new file mode 100644 index 000000000..17ffc727c --- /dev/null +++ b/crates/ra_ide/src/snapshots/highlight_unsafe.html | |||
@@ -0,0 +1,48 @@ | |||
1 | |||
2 | <style> | ||
3 | body { margin: 0; } | ||
4 | pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; } | ||
5 | |||
6 | .lifetime { color: #DFAF8F; font-style: italic; } | ||
7 | .comment { color: #7F9F7F; } | ||
8 | .struct, .enum { color: #7CB8BB; } | ||
9 | .enum_variant { color: #BDE0F3; } | ||
10 | .string_literal { color: #CC9393; } | ||
11 | .field { color: #94BFF3; } | ||
12 | .function { color: #93E0E3; } | ||
13 | .operator.unsafe { color: #E28C14; } | ||
14 | .parameter { color: #94BFF3; } | ||
15 | .text { color: #DCDCCC; } | ||
16 | .type { color: #7CB8BB; } | ||
17 | .builtin_type { color: #8CD0D3; } | ||
18 | .type_param { color: #DFAF8F; } | ||
19 | .attribute { color: #94BFF3; } | ||
20 | .numeric_literal { color: #BFEBBF; } | ||
21 | .bool_literal { color: #BFE6EB; } | ||
22 | .macro { color: #94BFF3; } | ||
23 | .module { color: #AFD8AF; } | ||
24 | .variable { color: #DCDCCC; } | ||
25 | .format_specifier { color: #CC696B; } | ||
26 | .mutable { text-decoration: underline; } | ||
27 | |||
28 | .keyword { color: #F0DFAF; font-weight: bold; } | ||
29 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | ||
30 | .control { font-style: italic; } | ||
31 | </style> | ||
32 | <pre><code><span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span>() {} | ||
33 | |||
34 | <span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span>; | ||
35 | |||
36 | <span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> { | ||
37 | <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_method</span>(&<span class="self_keyword">self</span>) {} | ||
38 | } | ||
39 | |||
40 | <span class="keyword">fn</span> <span class="function declaration">main</span>() { | ||
41 | <span class="keyword">let</span> <span class="variable declaration">x</span> = &<span class="numeric_literal">5</span> <span class="keyword">as</span> *<span class="keyword">const</span> <span class="builtin_type">usize</span>; | ||
42 | <span class="keyword unsafe">unsafe</span> { | ||
43 | <span class="function unsafe">unsafe_fn</span>(); | ||
44 | <span class="struct">HasUnsafeFn</span>.<span class="function unsafe">unsafe_method</span>(); | ||
45 | <span class="keyword">let</span> <span class="variable declaration">y</span> = <span class="operator unsafe">*</span><span class="variable">x</span>; | ||
46 | <span class="keyword">let</span> <span class="variable declaration">z</span> = -<span class="variable">x</span>; | ||
47 | } | ||
48 | }</code></pre> \ No newline at end of file | ||
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html index 635fe5cf9..42c5f3e55 100644 --- a/crates/ra_ide/src/snapshots/highlighting.html +++ b/crates/ra_ide/src/snapshots/highlighting.html | |||
@@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
10 | .string_literal { color: #CC9393; } | 10 | .string_literal { color: #CC9393; } |
11 | .field { color: #94BFF3; } | 11 | .field { color: #94BFF3; } |
12 | .function { color: #93E0E3; } | 12 | .function { color: #93E0E3; } |
13 | .operator.unsafe { color: #E28C14; } | ||
13 | .parameter { color: #94BFF3; } | 14 | .parameter { color: #94BFF3; } |
14 | .text { color: #DCDCCC; } | 15 | .text { color: #DCDCCC; } |
15 | .type { color: #7CB8BB; } | 16 | .type { color: #7CB8BB; } |
@@ -17,6 +18,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
17 | .type_param { color: #DFAF8F; } | 18 | .type_param { color: #DFAF8F; } |
18 | .attribute { color: #94BFF3; } | 19 | .attribute { color: #94BFF3; } |
19 | .numeric_literal { color: #BFEBBF; } | 20 | .numeric_literal { color: #BFEBBF; } |
21 | .bool_literal { color: #BFE6EB; } | ||
20 | .macro { color: #94BFF3; } | 22 | .macro { color: #94BFF3; } |
21 | .module { color: #AFD8AF; } | 23 | .module { color: #AFD8AF; } |
22 | .variable { color: #DCDCCC; } | 24 | .variable { color: #DCDCCC; } |
@@ -27,19 +29,19 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
27 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 29 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
28 | .control { font-style: italic; } | 30 | .control { font-style: italic; } |
29 | </style> | 31 | </style> |
30 | <pre><code><span class="attribute">#[derive(Clone, Debug)]</span> | 32 | <pre><code><span class="attribute">#[</span><span class="function attribute">derive</span><span class="attribute">(Clone, Debug)]</span> |
31 | <span class="keyword">struct</span> <span class="struct declaration">Foo</span> { | 33 | <span class="keyword">struct</span> <span class="struct declaration">Foo</span> { |
32 | <span class="keyword">pub</span> <span class="field declaration">x</span>: <span class="builtin_type">i32</span>, | 34 | <span class="keyword">pub</span> <span class="field declaration">x</span>: <span class="builtin_type">i32</span>, |
33 | <span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>, | 35 | <span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>, |
34 | } | 36 | } |
35 | 37 | ||
36 | <span class="keyword">trait</span> <span class="trait declaration">Bar</span> { | 38 | <span class="keyword">trait</span> <span class="trait declaration">Bar</span> { |
37 | <span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="keyword">self</span>) -> <span class="builtin_type">i32</span>; | 39 | <span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="self_keyword">self</span>) -> <span class="builtin_type">i32</span>; |
38 | } | 40 | } |
39 | 41 | ||
40 | <span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> { | 42 | <span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> { |
41 | <span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="keyword">self</span>) -> <span class="builtin_type">i32</span> { | 43 | <span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="self_keyword">self</span>) -> <span class="builtin_type">i32</span> { |
42 | <span class="keyword">self</span>.<span class="field">x</span> | 44 | <span class="self_keyword">self</span>.<span class="field">x</span> |
43 | } | 45 | } |
44 | } | 46 | } |
45 | 47 | ||
@@ -64,7 +66,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
64 | <span class="macro">println!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>); | 66 | <span class="macro">println!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>); |
65 | 67 | ||
66 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">vec</span> = <span class="unresolved_reference">Vec</span>::<span class="unresolved_reference">new</span>(); | 68 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">vec</span> = <span class="unresolved_reference">Vec</span>::<span class="unresolved_reference">new</span>(); |
67 | <span class="keyword control">if</span> <span class="keyword">true</span> { | 69 | <span class="keyword control">if</span> <span class="bool_literal">true</span> { |
68 | <span class="keyword">let</span> <span class="variable declaration">x</span> = <span class="numeric_literal">92</span>; | 70 | <span class="keyword">let</span> <span class="variable declaration">x</span> = <span class="numeric_literal">92</span>; |
69 | <span class="variable mutable">vec</span>.<span class="unresolved_reference">push</span>(<span class="struct">Foo</span> { <span class="field">x</span>, <span class="field">y</span>: <span class="numeric_literal">1</span> }); | 71 | <span class="variable mutable">vec</span>.<span class="unresolved_reference">push</span>(<span class="struct">Foo</span> { <span class="field">x</span>, <span class="field">y</span>: <span class="numeric_literal">1</span> }); |
70 | } | 72 | } |
@@ -91,7 +93,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
91 | <span class="keyword">use</span> <span class="enum">Option</span>::*; | 93 | <span class="keyword">use</span> <span class="enum">Option</span>::*; |
92 | 94 | ||
93 | <span class="keyword">impl</span><<span class="type_param declaration">T</span>> <span class="enum">Option</span><<span class="type_param">T</span>> { | 95 | <span class="keyword">impl</span><<span class="type_param declaration">T</span>> <span class="enum">Option</span><<span class="type_param">T</span>> { |
94 | <span class="keyword">fn</span> <span class="function declaration">and</span><<span class="type_param declaration">U</span>>(<span class="keyword">self</span>, <span class="variable declaration">other</span>: <span class="enum">Option</span><<span class="type_param">U</span>>) -> <span class="enum">Option</span><(<span class="type_param">T</span>, <span class="type_param">U</span>)> { | 96 | <span class="keyword">fn</span> <span class="function declaration">and</span><<span class="type_param declaration">U</span>>(<span class="self_keyword">self</span>, <span class="variable declaration">other</span>: <span class="enum">Option</span><<span class="type_param">U</span>>) -> <span class="enum">Option</span><(<span class="type_param">T</span>, <span class="type_param">U</span>)> { |
95 | <span class="keyword control">match</span> <span class="variable">other</span> { | 97 | <span class="keyword control">match</span> <span class="variable">other</span> { |
96 | <span class="enum_variant">None</span> => <span class="macro">unimplemented!</span>(), | 98 | <span class="enum_variant">None</span> => <span class="macro">unimplemented!</span>(), |
97 | <span class="variable declaration">Nope</span> => <span class="variable">Nope</span>, | 99 | <span class="variable declaration">Nope</span> => <span class="variable">Nope</span>, |
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html index 11e1f3e44..2dd61d20d 100644 --- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html +++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html | |||
@@ -10,6 +10,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
10 | .string_literal { color: #CC9393; } | 10 | .string_literal { color: #CC9393; } |
11 | .field { color: #94BFF3; } | 11 | .field { color: #94BFF3; } |
12 | .function { color: #93E0E3; } | 12 | .function { color: #93E0E3; } |
13 | .operator.unsafe { color: #E28C14; } | ||
13 | .parameter { color: #94BFF3; } | 14 | .parameter { color: #94BFF3; } |
14 | .text { color: #DCDCCC; } | 15 | .text { color: #DCDCCC; } |
15 | .type { color: #7CB8BB; } | 16 | .type { color: #7CB8BB; } |
@@ -17,6 +18,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
17 | .type_param { color: #DFAF8F; } | 18 | .type_param { color: #DFAF8F; } |
18 | .attribute { color: #94BFF3; } | 19 | .attribute { color: #94BFF3; } |
19 | .numeric_literal { color: #BFEBBF; } | 20 | .numeric_literal { color: #BFEBBF; } |
21 | .bool_literal { color: #BFE6EB; } | ||
20 | .macro { color: #94BFF3; } | 22 | .macro { color: #94BFF3; } |
21 | .module { color: #AFD8AF; } | 23 | .module { color: #AFD8AF; } |
22 | .variable { color: #DCDCCC; } | 24 | .variable { color: #DCDCCC; } |
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index 1873d1d0d..93e9aee1d 100644 --- a/crates/ra_ide/src/ssr.rs +++ b/crates/ra_ide/src/ssr.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! structural search replace | ||
2 | |||
3 | use std::{collections::HashMap, iter::once, str::FromStr}; | 1 | use std::{collections::HashMap, iter::once, str::FromStr}; |
4 | 2 | ||
5 | use ra_db::{SourceDatabase, SourceDatabaseExt}; | 3 | use ra_db::{SourceDatabase, SourceDatabaseExt}; |
@@ -25,6 +23,28 @@ impl std::fmt::Display for SsrError { | |||
25 | 23 | ||
26 | impl std::error::Error for SsrError {} | 24 | impl std::error::Error for SsrError {} |
27 | 25 | ||
26 | // Feature: Structural Seach and Replace | ||
27 | // | ||
28 | // Search and replace with named wildcards that will match any expression. | ||
29 | // The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. | ||
30 | // A `$<name>:expr` placeholder in the search pattern will match any expression and `$<name>` will reference it in the replacement. | ||
31 | // Available via the command `rust-analyzer.ssr`. | ||
32 | // | ||
33 | // ```rust | ||
34 | // // Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)] | ||
35 | // | ||
36 | // // BEFORE | ||
37 | // String::from(foo(y + 5, z)) | ||
38 | // | ||
39 | // // AFTER | ||
40 | // String::from((y + 5).foo(z)) | ||
41 | // ``` | ||
42 | // | ||
43 | // |=== | ||
44 | // | Editor | Action Name | ||
45 | // | ||
46 | // | VS Code | **Rust Analyzer: Structural Search Replace** | ||
47 | // |=== | ||
28 | pub fn parse_search_replace( | 48 | pub fn parse_search_replace( |
29 | query: &str, | 49 | query: &str, |
30 | parse_only: bool, | 50 | parse_only: bool, |
@@ -196,10 +216,10 @@ fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches { | |||
196 | ) -> Option<Match> { | 216 | ) -> Option<Match> { |
197 | let match_ = check_opt_nodes(pattern.path(), code.path(), placeholders, match_)?; | 217 | let match_ = check_opt_nodes(pattern.path(), code.path(), placeholders, match_)?; |
198 | 218 | ||
199 | let mut pattern_fields = | 219 | let mut pattern_fields: Vec<RecordField> = |
200 | pattern.record_field_list().map(|x| x.fields().collect()).unwrap_or(vec![]); | 220 | pattern.record_field_list().map(|x| x.fields().collect()).unwrap_or_default(); |
201 | let mut code_fields = | 221 | let mut code_fields: Vec<RecordField> = |
202 | code.record_field_list().map(|x| x.fields().collect()).unwrap_or(vec![]); | 222 | code.record_field_list().map(|x| x.fields().collect()).unwrap_or_default(); |
203 | 223 | ||
204 | if pattern_fields.len() != code_fields.len() { | 224 | if pattern_fields.len() != code_fields.len() { |
205 | return None; | 225 | return None; |
diff --git a/crates/ra_ide/src/status.rs b/crates/ra_ide/src/status.rs index 30eb5c995..5b7992920 100644 --- a/crates/ra_ide/src/status.rs +++ b/crates/ra_ide/src/status.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use std::{fmt, iter::FromIterator, sync::Arc}; | 1 | use std::{fmt, iter::FromIterator, sync::Arc}; |
4 | 2 | ||
5 | use hir::MacroFile; | 3 | use hir::MacroFile; |
@@ -26,6 +24,15 @@ fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { | |||
26 | db.query(hir::db::ParseMacroQuery).entries::<SyntaxTreeStats>() | 24 | db.query(hir::db::ParseMacroQuery).entries::<SyntaxTreeStats>() |
27 | } | 25 | } |
28 | 26 | ||
27 | // Feature: Status | ||
28 | // | ||
29 | // Shows internal statistic about memory usage of rust-analyzer. | ||
30 | // | ||
31 | // |=== | ||
32 | // | Editor | Action Name | ||
33 | // | ||
34 | // | VS Code | **Rust Analyzer: Status** | ||
35 | // |=== | ||
29 | pub(crate) fn status(db: &RootDatabase) -> String { | 36 | pub(crate) fn status(db: &RootDatabase) -> String { |
30 | let files_stats = db.query(FileTextQuery).entries::<FilesStats>(); | 37 | let files_stats = db.query(FileTextQuery).entries::<FilesStats>(); |
31 | let syntax_tree_stats = syntax_tree_stats(db); | 38 | let syntax_tree_stats = syntax_tree_stats(db); |
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index be57eeb0a..19ecd54d6 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! Implements syntax highlighting. | ||
2 | |||
3 | mod tags; | 1 | mod tags; |
4 | mod html; | 2 | mod html; |
5 | #[cfg(test)] | 3 | #[cfg(test)] |
@@ -32,81 +30,15 @@ pub struct HighlightedRange { | |||
32 | pub binding_hash: Option<u64>, | 30 | pub binding_hash: Option<u64>, |
33 | } | 31 | } |
34 | 32 | ||
35 | #[derive(Debug)] | 33 | // Feature: Semantic Syntax Highlighting |
36 | struct HighlightedRangeStack { | 34 | // |
37 | stack: Vec<Vec<HighlightedRange>>, | 35 | // rust-analyzer highlights the code semantically. |
38 | } | 36 | // For example, `bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait. |
39 | 37 | // rust-analyzer does not specify colors directly, instead it assigns tag (like `struct`) and a set of modifiers (like `declaration`) to each token. | |
40 | /// We use a stack to implement the flattening logic for the highlighted | 38 | // It's up to the client to map those to specific colors. |
41 | /// syntax ranges. | 39 | // |
42 | impl HighlightedRangeStack { | 40 | // The general rule is that a reference to an entity gets colored the same way as the entity itself. |
43 | fn new() -> Self { | 41 | // We also give special modifier for `mut` and `&mut` local variables. |
44 | Self { stack: vec![Vec::new()] } | ||
45 | } | ||
46 | |||
47 | fn push(&mut self) { | ||
48 | self.stack.push(Vec::new()); | ||
49 | } | ||
50 | |||
51 | /// Flattens the highlighted ranges. | ||
52 | /// | ||
53 | /// For example `#[cfg(feature = "foo")]` contains the nested ranges: | ||
54 | /// 1) parent-range: Attribute [0, 23) | ||
55 | /// 2) child-range: String [16, 21) | ||
56 | /// | ||
57 | /// The following code implements the flattening, for our example this results to: | ||
58 | /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` | ||
59 | fn pop(&mut self) { | ||
60 | let children = self.stack.pop().unwrap(); | ||
61 | let prev = self.stack.last_mut().unwrap(); | ||
62 | let needs_flattening = !children.is_empty() | ||
63 | && !prev.is_empty() | ||
64 | && prev.last().unwrap().range.contains_range(children.first().unwrap().range); | ||
65 | if !needs_flattening { | ||
66 | prev.extend(children); | ||
67 | } else { | ||
68 | let mut parent = prev.pop().unwrap(); | ||
69 | for ele in children { | ||
70 | assert!(parent.range.contains_range(ele.range)); | ||
71 | let mut cloned = parent.clone(); | ||
72 | parent.range = TextRange::new(parent.range.start(), ele.range.start()); | ||
73 | cloned.range = TextRange::new(ele.range.end(), cloned.range.end()); | ||
74 | if !parent.range.is_empty() { | ||
75 | prev.push(parent); | ||
76 | } | ||
77 | prev.push(ele); | ||
78 | parent = cloned; | ||
79 | } | ||
80 | if !parent.range.is_empty() { | ||
81 | prev.push(parent); | ||
82 | } | ||
83 | } | ||
84 | } | ||
85 | |||
86 | fn add(&mut self, range: HighlightedRange) { | ||
87 | self.stack | ||
88 | .last_mut() | ||
89 | .expect("during DFS traversal, the stack must not be empty") | ||
90 | .push(range) | ||
91 | } | ||
92 | |||
93 | fn flattened(mut self) -> Vec<HighlightedRange> { | ||
94 | assert_eq!( | ||
95 | self.stack.len(), | ||
96 | 1, | ||
97 | "after DFS traversal, the stack should only contain a single element" | ||
98 | ); | ||
99 | let mut res = self.stack.pop().unwrap(); | ||
100 | res.sort_by_key(|range| range.range.start()); | ||
101 | // Check that ranges are sorted and disjoint | ||
102 | assert!(res | ||
103 | .iter() | ||
104 | .zip(res.iter().skip(1)) | ||
105 | .all(|(left, right)| left.range.end() <= right.range.start())); | ||
106 | res | ||
107 | } | ||
108 | } | ||
109 | |||
110 | pub(crate) fn highlight( | 42 | pub(crate) fn highlight( |
111 | db: &RootDatabase, | 43 | db: &RootDatabase, |
112 | file_id: FileId, | 44 | file_id: FileId, |
@@ -291,6 +223,81 @@ pub(crate) fn highlight( | |||
291 | stack.flattened() | 223 | stack.flattened() |
292 | } | 224 | } |
293 | 225 | ||
226 | #[derive(Debug)] | ||
227 | struct HighlightedRangeStack { | ||
228 | stack: Vec<Vec<HighlightedRange>>, | ||
229 | } | ||
230 | |||
231 | /// We use a stack to implement the flattening logic for the highlighted | ||
232 | /// syntax ranges. | ||
233 | impl HighlightedRangeStack { | ||
234 | fn new() -> Self { | ||
235 | Self { stack: vec![Vec::new()] } | ||
236 | } | ||
237 | |||
238 | fn push(&mut self) { | ||
239 | self.stack.push(Vec::new()); | ||
240 | } | ||
241 | |||
242 | /// Flattens the highlighted ranges. | ||
243 | /// | ||
244 | /// For example `#[cfg(feature = "foo")]` contains the nested ranges: | ||
245 | /// 1) parent-range: Attribute [0, 23) | ||
246 | /// 2) child-range: String [16, 21) | ||
247 | /// | ||
248 | /// The following code implements the flattening, for our example this results to: | ||
249 | /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` | ||
250 | fn pop(&mut self) { | ||
251 | let children = self.stack.pop().unwrap(); | ||
252 | let prev = self.stack.last_mut().unwrap(); | ||
253 | let needs_flattening = !children.is_empty() | ||
254 | && !prev.is_empty() | ||
255 | && prev.last().unwrap().range.contains_range(children.first().unwrap().range); | ||
256 | if !needs_flattening { | ||
257 | prev.extend(children); | ||
258 | } else { | ||
259 | let mut parent = prev.pop().unwrap(); | ||
260 | for ele in children { | ||
261 | assert!(parent.range.contains_range(ele.range)); | ||
262 | let mut cloned = parent.clone(); | ||
263 | parent.range = TextRange::new(parent.range.start(), ele.range.start()); | ||
264 | cloned.range = TextRange::new(ele.range.end(), cloned.range.end()); | ||
265 | if !parent.range.is_empty() { | ||
266 | prev.push(parent); | ||
267 | } | ||
268 | prev.push(ele); | ||
269 | parent = cloned; | ||
270 | } | ||
271 | if !parent.range.is_empty() { | ||
272 | prev.push(parent); | ||
273 | } | ||
274 | } | ||
275 | } | ||
276 | |||
277 | fn add(&mut self, range: HighlightedRange) { | ||
278 | self.stack | ||
279 | .last_mut() | ||
280 | .expect("during DFS traversal, the stack must not be empty") | ||
281 | .push(range) | ||
282 | } | ||
283 | |||
284 | fn flattened(mut self) -> Vec<HighlightedRange> { | ||
285 | assert_eq!( | ||
286 | self.stack.len(), | ||
287 | 1, | ||
288 | "after DFS traversal, the stack should only contain a single element" | ||
289 | ); | ||
290 | let mut res = self.stack.pop().unwrap(); | ||
291 | res.sort_by_key(|range| range.range.start()); | ||
292 | // Check that ranges are sorted and disjoint | ||
293 | assert!(res | ||
294 | .iter() | ||
295 | .zip(res.iter().skip(1)) | ||
296 | .all(|(left, right)| left.range.end() <= right.range.start())); | ||
297 | res | ||
298 | } | ||
299 | } | ||
300 | |||
294 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { | 301 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { |
295 | Some(match kind { | 302 | Some(match kind { |
296 | FormatSpecifier::Open | 303 | FormatSpecifier::Open |
@@ -361,7 +368,9 @@ fn highlight_element( | |||
361 | } | 368 | } |
362 | 369 | ||
363 | // Highlight references like the definitions they resolve to | 370 | // Highlight references like the definitions they resolve to |
364 | NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => return None, | 371 | NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => { |
372 | Highlight::from(HighlightTag::Function) | HighlightModifier::Attribute | ||
373 | } | ||
365 | NAME_REF => { | 374 | NAME_REF => { |
366 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); | 375 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); |
367 | match classify_name_ref(sema, &name_ref) { | 376 | match classify_name_ref(sema, &name_ref) { |
@@ -389,6 +398,7 @@ fn highlight_element( | |||
389 | INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), | 398 | INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), |
390 | BYTE => HighlightTag::ByteLiteral.into(), | 399 | BYTE => HighlightTag::ByteLiteral.into(), |
391 | CHAR => HighlightTag::CharLiteral.into(), | 400 | CHAR => HighlightTag::CharLiteral.into(), |
401 | QUESTION => Highlight::new(HighlightTag::Operator) | HighlightModifier::ControlFlow, | ||
392 | LIFETIME => { | 402 | LIFETIME => { |
393 | let h = Highlight::new(HighlightTag::Lifetime); | 403 | let h = Highlight::new(HighlightTag::Lifetime); |
394 | match element.parent().map(|it| it.kind()) { | 404 | match element.parent().map(|it| it.kind()) { |
@@ -396,6 +406,23 @@ fn highlight_element( | |||
396 | _ => h, | 406 | _ => h, |
397 | } | 407 | } |
398 | } | 408 | } |
409 | PREFIX_EXPR => { | ||
410 | let prefix_expr = element.into_node().and_then(ast::PrefixExpr::cast)?; | ||
411 | match prefix_expr.op_kind() { | ||
412 | Some(ast::PrefixOp::Deref) => {} | ||
413 | _ => return None, | ||
414 | } | ||
415 | |||
416 | let expr = prefix_expr.expr()?; | ||
417 | let ty = sema.type_of_expr(&expr)?; | ||
418 | if !ty.is_raw_ptr() { | ||
419 | return None; | ||
420 | } | ||
421 | |||
422 | let mut h = Highlight::new(HighlightTag::Operator); | ||
423 | h |= HighlightModifier::Unsafe; | ||
424 | h | ||
425 | } | ||
399 | 426 | ||
400 | k if k.is_keyword() => { | 427 | k if k.is_keyword() => { |
401 | let h = Highlight::new(HighlightTag::Keyword); | 428 | let h = Highlight::new(HighlightTag::Keyword); |
@@ -411,6 +438,8 @@ fn highlight_element( | |||
411 | | T![in] => h | HighlightModifier::ControlFlow, | 438 | | T![in] => h | HighlightModifier::ControlFlow, |
412 | T![for] if !is_child_of_impl(element) => h | HighlightModifier::ControlFlow, | 439 | T![for] if !is_child_of_impl(element) => h | HighlightModifier::ControlFlow, |
413 | T![unsafe] => h | HighlightModifier::Unsafe, | 440 | T![unsafe] => h | HighlightModifier::Unsafe, |
441 | T![true] | T![false] => HighlightTag::BoolLiteral.into(), | ||
442 | T![self] => HighlightTag::SelfKeyword.into(), | ||
414 | _ => h, | 443 | _ => h, |
415 | } | 444 | } |
416 | } | 445 | } |
@@ -446,7 +475,13 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { | |||
446 | Definition::Field(_) => HighlightTag::Field, | 475 | Definition::Field(_) => HighlightTag::Field, |
447 | Definition::ModuleDef(def) => match def { | 476 | Definition::ModuleDef(def) => match def { |
448 | hir::ModuleDef::Module(_) => HighlightTag::Module, | 477 | hir::ModuleDef::Module(_) => HighlightTag::Module, |
449 | hir::ModuleDef::Function(_) => HighlightTag::Function, | 478 | hir::ModuleDef::Function(func) => { |
479 | let mut h = HighlightTag::Function.into(); | ||
480 | if func.is_unsafe(db) { | ||
481 | h |= HighlightModifier::Unsafe; | ||
482 | } | ||
483 | return h; | ||
484 | } | ||
450 | hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, | 485 | hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, |
451 | hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, | 486 | hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, |
452 | hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, | 487 | hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, |
@@ -478,23 +513,31 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { | |||
478 | } | 513 | } |
479 | 514 | ||
480 | fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | 515 | fn highlight_name_by_syntax(name: ast::Name) -> Highlight { |
481 | let default = HighlightTag::Function.into(); | 516 | let default = HighlightTag::UnresolvedReference; |
482 | 517 | ||
483 | let parent = match name.syntax().parent() { | 518 | let parent = match name.syntax().parent() { |
484 | Some(it) => it, | 519 | Some(it) => it, |
485 | _ => return default, | 520 | _ => return default.into(), |
486 | }; | 521 | }; |
487 | 522 | ||
488 | match parent.kind() { | 523 | let tag = match parent.kind() { |
489 | STRUCT_DEF => HighlightTag::Struct.into(), | 524 | STRUCT_DEF => HighlightTag::Struct, |
490 | ENUM_DEF => HighlightTag::Enum.into(), | 525 | ENUM_DEF => HighlightTag::Enum, |
491 | UNION_DEF => HighlightTag::Union.into(), | 526 | UNION_DEF => HighlightTag::Union, |
492 | TRAIT_DEF => HighlightTag::Trait.into(), | 527 | TRAIT_DEF => HighlightTag::Trait, |
493 | TYPE_ALIAS_DEF => HighlightTag::TypeAlias.into(), | 528 | TYPE_ALIAS_DEF => HighlightTag::TypeAlias, |
494 | TYPE_PARAM => HighlightTag::TypeParam.into(), | 529 | TYPE_PARAM => HighlightTag::TypeParam, |
495 | RECORD_FIELD_DEF => HighlightTag::Field.into(), | 530 | RECORD_FIELD_DEF => HighlightTag::Field, |
531 | MODULE => HighlightTag::Module, | ||
532 | FN_DEF => HighlightTag::Function, | ||
533 | CONST_DEF => HighlightTag::Constant, | ||
534 | STATIC_DEF => HighlightTag::Static, | ||
535 | ENUM_VARIANT => HighlightTag::EnumVariant, | ||
536 | BIND_PAT => HighlightTag::Local, | ||
496 | _ => default, | 537 | _ => default, |
497 | } | 538 | }; |
539 | |||
540 | tag.into() | ||
498 | } | 541 | } |
499 | 542 | ||
500 | fn highlight_injection( | 543 | fn highlight_injection( |
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs index ff0eeeb52..7d946c98d 100644 --- a/crates/ra_ide/src/syntax_highlighting/html.rs +++ b/crates/ra_ide/src/syntax_highlighting/html.rs | |||
@@ -69,6 +69,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
69 | .string_literal { color: #CC9393; } | 69 | .string_literal { color: #CC9393; } |
70 | .field { color: #94BFF3; } | 70 | .field { color: #94BFF3; } |
71 | .function { color: #93E0E3; } | 71 | .function { color: #93E0E3; } |
72 | .operator.unsafe { color: #E28C14; } | ||
72 | .parameter { color: #94BFF3; } | 73 | .parameter { color: #94BFF3; } |
73 | .text { color: #DCDCCC; } | 74 | .text { color: #DCDCCC; } |
74 | .type { color: #7CB8BB; } | 75 | .type { color: #7CB8BB; } |
@@ -76,6 +77,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
76 | .type_param { color: #DFAF8F; } | 77 | .type_param { color: #DFAF8F; } |
77 | .attribute { color: #94BFF3; } | 78 | .attribute { color: #94BFF3; } |
78 | .numeric_literal { color: #BFEBBF; } | 79 | .numeric_literal { color: #BFEBBF; } |
80 | .bool_literal { color: #BFE6EB; } | ||
79 | .macro { color: #94BFF3; } | 81 | .macro { color: #94BFF3; } |
80 | .module { color: #AFD8AF; } | 82 | .module { color: #AFD8AF; } |
81 | .variable { color: #DCDCCC; } | 83 | .variable { color: #DCDCCC; } |
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs index be1a0f12b..94f466966 100644 --- a/crates/ra_ide/src/syntax_highlighting/tags.rs +++ b/crates/ra_ide/src/syntax_highlighting/tags.rs | |||
@@ -15,6 +15,7 @@ pub struct HighlightModifiers(u32); | |||
15 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] | 15 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] |
16 | pub enum HighlightTag { | 16 | pub enum HighlightTag { |
17 | Attribute, | 17 | Attribute, |
18 | BoolLiteral, | ||
18 | BuiltinType, | 19 | BuiltinType, |
19 | ByteLiteral, | 20 | ByteLiteral, |
20 | CharLiteral, | 21 | CharLiteral, |
@@ -23,12 +24,15 @@ pub enum HighlightTag { | |||
23 | Enum, | 24 | Enum, |
24 | EnumVariant, | 25 | EnumVariant, |
25 | Field, | 26 | Field, |
27 | FormatSpecifier, | ||
26 | Function, | 28 | Function, |
27 | Keyword, | 29 | Keyword, |
28 | Lifetime, | 30 | Lifetime, |
29 | Macro, | 31 | Macro, |
30 | Module, | 32 | Module, |
31 | NumericLiteral, | 33 | NumericLiteral, |
34 | Operator, | ||
35 | SelfKeyword, | ||
32 | SelfType, | 36 | SelfType, |
33 | Static, | 37 | Static, |
34 | StringLiteral, | 38 | StringLiteral, |
@@ -39,14 +43,15 @@ pub enum HighlightTag { | |||
39 | Union, | 43 | Union, |
40 | Local, | 44 | Local, |
41 | UnresolvedReference, | 45 | UnresolvedReference, |
42 | FormatSpecifier, | ||
43 | } | 46 | } |
44 | 47 | ||
45 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] | 48 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] |
46 | #[repr(u8)] | 49 | #[repr(u8)] |
47 | pub enum HighlightModifier { | 50 | pub enum HighlightModifier { |
51 | /// Used to differentiate individual elements within attributes. | ||
52 | Attribute = 0, | ||
48 | /// Used with keywords like `if` and `break`. | 53 | /// Used with keywords like `if` and `break`. |
49 | ControlFlow = 0, | 54 | ControlFlow, |
50 | /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is | 55 | /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is |
51 | /// not. | 56 | /// not. |
52 | Definition, | 57 | Definition, |
@@ -58,6 +63,7 @@ impl HighlightTag { | |||
58 | fn as_str(self) -> &'static str { | 63 | fn as_str(self) -> &'static str { |
59 | match self { | 64 | match self { |
60 | HighlightTag::Attribute => "attribute", | 65 | HighlightTag::Attribute => "attribute", |
66 | HighlightTag::BoolLiteral => "bool_literal", | ||
61 | HighlightTag::BuiltinType => "builtin_type", | 67 | HighlightTag::BuiltinType => "builtin_type", |
62 | HighlightTag::ByteLiteral => "byte_literal", | 68 | HighlightTag::ByteLiteral => "byte_literal", |
63 | HighlightTag::CharLiteral => "char_literal", | 69 | HighlightTag::CharLiteral => "char_literal", |
@@ -66,12 +72,15 @@ impl HighlightTag { | |||
66 | HighlightTag::Enum => "enum", | 72 | HighlightTag::Enum => "enum", |
67 | HighlightTag::EnumVariant => "enum_variant", | 73 | HighlightTag::EnumVariant => "enum_variant", |
68 | HighlightTag::Field => "field", | 74 | HighlightTag::Field => "field", |
75 | HighlightTag::FormatSpecifier => "format_specifier", | ||
69 | HighlightTag::Function => "function", | 76 | HighlightTag::Function => "function", |
70 | HighlightTag::Keyword => "keyword", | 77 | HighlightTag::Keyword => "keyword", |
71 | HighlightTag::Lifetime => "lifetime", | 78 | HighlightTag::Lifetime => "lifetime", |
72 | HighlightTag::Macro => "macro", | 79 | HighlightTag::Macro => "macro", |
73 | HighlightTag::Module => "module", | 80 | HighlightTag::Module => "module", |
74 | HighlightTag::NumericLiteral => "numeric_literal", | 81 | HighlightTag::NumericLiteral => "numeric_literal", |
82 | HighlightTag::Operator => "operator", | ||
83 | HighlightTag::SelfKeyword => "self_keyword", | ||
75 | HighlightTag::SelfType => "self_type", | 84 | HighlightTag::SelfType => "self_type", |
76 | HighlightTag::Static => "static", | 85 | HighlightTag::Static => "static", |
77 | HighlightTag::StringLiteral => "string_literal", | 86 | HighlightTag::StringLiteral => "string_literal", |
@@ -82,7 +91,6 @@ impl HighlightTag { | |||
82 | HighlightTag::Union => "union", | 91 | HighlightTag::Union => "union", |
83 | HighlightTag::Local => "variable", | 92 | HighlightTag::Local => "variable", |
84 | HighlightTag::UnresolvedReference => "unresolved_reference", | 93 | HighlightTag::UnresolvedReference => "unresolved_reference", |
85 | HighlightTag::FormatSpecifier => "format_specifier", | ||
86 | } | 94 | } |
87 | } | 95 | } |
88 | } | 96 | } |
@@ -95,6 +103,7 @@ impl fmt::Display for HighlightTag { | |||
95 | 103 | ||
96 | impl HighlightModifier { | 104 | impl HighlightModifier { |
97 | const ALL: &'static [HighlightModifier] = &[ | 105 | const ALL: &'static [HighlightModifier] = &[ |
106 | HighlightModifier::Attribute, | ||
98 | HighlightModifier::ControlFlow, | 107 | HighlightModifier::ControlFlow, |
99 | HighlightModifier::Definition, | 108 | HighlightModifier::Definition, |
100 | HighlightModifier::Mutable, | 109 | HighlightModifier::Mutable, |
@@ -103,6 +112,7 @@ impl HighlightModifier { | |||
103 | 112 | ||
104 | fn as_str(self) -> &'static str { | 113 | fn as_str(self) -> &'static str { |
105 | match self { | 114 | match self { |
115 | HighlightModifier::Attribute => "attribute", | ||
106 | HighlightModifier::ControlFlow => "control", | 116 | HighlightModifier::ControlFlow => "control", |
107 | HighlightModifier::Definition => "declaration", | 117 | HighlightModifier::Definition => "declaration", |
108 | HighlightModifier::Mutable => "mutable", | 118 | HighlightModifier::Mutable => "mutable", |
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index eb43a23da..36a1aa419 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs | |||
@@ -218,6 +218,7 @@ fn main() { | |||
218 | println!("{argument}", argument = "test"); // => "test" | 218 | println!("{argument}", argument = "test"); // => "test" |
219 | println!("{name} {}", 1, name = 2); // => "2 1" | 219 | println!("{name} {}", 1, name = 2); // => "2 1" |
220 | println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" | 220 | println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" |
221 | println!("{{{}}}", 2); // => "{2}" | ||
221 | println!("Hello {:5}!", "x"); | 222 | println!("Hello {:5}!", "x"); |
222 | println!("Hello {:1$}!", "x", 5); | 223 | println!("Hello {:1$}!", "x", 5); |
223 | println!("Hello {1:0$}!", 5, "x"); | 224 | println!("Hello {1:0$}!", 5, "x"); |
@@ -257,3 +258,34 @@ fn main() { | |||
257 | fs::write(dst_file, &actual_html).unwrap(); | 258 | fs::write(dst_file, &actual_html).unwrap(); |
258 | assert_eq_text!(expected_html, actual_html); | 259 | assert_eq_text!(expected_html, actual_html); |
259 | } | 260 | } |
261 | |||
262 | #[test] | ||
263 | fn test_unsafe_highlighting() { | ||
264 | let (analysis, file_id) = single_file( | ||
265 | r#" | ||
266 | unsafe fn unsafe_fn() {} | ||
267 | |||
268 | struct HasUnsafeFn; | ||
269 | |||
270 | impl HasUnsafeFn { | ||
271 | unsafe fn unsafe_method(&self) {} | ||
272 | } | ||
273 | |||
274 | fn main() { | ||
275 | let x = &5 as *const usize; | ||
276 | unsafe { | ||
277 | unsafe_fn(); | ||
278 | HasUnsafeFn.unsafe_method(); | ||
279 | let y = *x; | ||
280 | let z = -x; | ||
281 | } | ||
282 | } | ||
283 | "# | ||
284 | .trim(), | ||
285 | ); | ||
286 | let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_unsafe.html"); | ||
287 | let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); | ||
288 | let expected_html = &read_text(&dst_file); | ||
289 | fs::write(dst_file, &actual_html).unwrap(); | ||
290 | assert_eq_text!(expected_html, actual_html); | ||
291 | } | ||
diff --git a/crates/ra_ide/src/syntax_tree.rs b/crates/ra_ide/src/syntax_tree.rs index 86c70ff83..a341684fd 100644 --- a/crates/ra_ide/src/syntax_tree.rs +++ b/crates/ra_ide/src/syntax_tree.rs | |||
@@ -1,6 +1,4 @@ | |||
1 | //! FIXME: write short doc here | 1 | use ra_db::{FileId, SourceDatabase}; |
2 | |||
3 | use ra_db::SourceDatabase; | ||
4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
5 | use ra_syntax::{ | 3 | use ra_syntax::{ |
6 | algo, AstNode, NodeOrToken, SourceFile, | 4 | algo, AstNode, NodeOrToken, SourceFile, |
@@ -8,8 +6,16 @@ use ra_syntax::{ | |||
8 | SyntaxToken, TextRange, TextSize, | 6 | SyntaxToken, TextRange, TextSize, |
9 | }; | 7 | }; |
10 | 8 | ||
11 | pub use ra_db::FileId; | 9 | // Feature: Show Syntax Tree |
12 | 10 | // | |
11 | // Shows the parse tree of the current file. It exists mostly for debugging | ||
12 | // rust-analyzer itself. | ||
13 | // | ||
14 | // |=== | ||
15 | // | Editor | Action Name | ||
16 | // | ||
17 | // | VS Code | **Rust Analyzer: Show Syntax Tree** | ||
18 | // |=== | ||
13 | pub(crate) fn syntax_tree( | 19 | pub(crate) fn syntax_tree( |
14 | db: &RootDatabase, | 20 | db: &RootDatabase, |
15 | file_id: FileId, | 21 | file_id: FileId, |
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs index 39bb3b357..67e2c33a0 100644 --- a/crates/ra_ide/src/typing.rs +++ b/crates/ra_ide/src/typing.rs | |||
@@ -32,6 +32,13 @@ pub(crate) use on_enter::on_enter; | |||
32 | 32 | ||
33 | pub(crate) const TRIGGER_CHARS: &str = ".=>"; | 33 | pub(crate) const TRIGGER_CHARS: &str = ".=>"; |
34 | 34 | ||
35 | // Feature: On Typing Assists | ||
36 | // | ||
37 | // Some features trigger on typing certain characters: | ||
38 | // | ||
39 | // - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression | ||
40 | // - Enter inside comments automatically inserts `///` | ||
41 | // - typing `.` in a chain method call auto-indents | ||
35 | pub(crate) fn on_char_typed( | 42 | pub(crate) fn on_char_typed( |
36 | db: &RootDatabase, | 43 | db: &RootDatabase, |
37 | position: FilePosition, | 44 | position: FilePosition, |
diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs index e7d64b4f6..a40d8af9c 100644 --- a/crates/ra_ide/src/typing/on_enter.rs +++ b/crates/ra_ide/src/typing/on_enter.rs | |||
@@ -11,9 +11,7 @@ use ra_syntax::{ | |||
11 | }; | 11 | }; |
12 | use ra_text_edit::TextEdit; | 12 | use ra_text_edit::TextEdit; |
13 | 13 | ||
14 | use crate::{SourceChange, SourceFileEdit}; | 14 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> { |
15 | |||
16 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<SourceChange> { | ||
17 | let parse = db.parse(position.file_id); | 15 | let parse = db.parse(position.file_id); |
18 | let file = parse.tree(); | 16 | let file = parse.tree(); |
19 | let comment = file | 17 | let comment = file |
@@ -41,9 +39,7 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Sour | |||
41 | let inserted = format!("\n{}{} $0", indent, prefix); | 39 | let inserted = format!("\n{}{} $0", indent, prefix); |
42 | let edit = TextEdit::insert(position.offset, inserted); | 40 | let edit = TextEdit::insert(position.offset, inserted); |
43 | 41 | ||
44 | let mut res = SourceChange::from(SourceFileEdit { edit, file_id: position.file_id }); | 42 | Some(edit) |
45 | res.is_snippet = true; | ||
46 | Some(res) | ||
47 | } | 43 | } |
48 | 44 | ||
49 | fn followed_by_comment(comment: &ast::Comment) -> bool { | 45 | fn followed_by_comment(comment: &ast::Comment) -> bool { |
@@ -90,9 +86,8 @@ mod tests { | |||
90 | let (analysis, file_id) = single_file(&before); | 86 | let (analysis, file_id) = single_file(&before); |
91 | let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; | 87 | let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; |
92 | 88 | ||
93 | assert_eq!(result.source_file_edits.len(), 1); | ||
94 | let mut actual = before.to_string(); | 89 | let mut actual = before.to_string(); |
95 | result.source_file_edits[0].edit.apply(&mut actual); | 90 | result.apply(&mut actual); |
96 | Some(actual) | 91 | Some(actual) |
97 | } | 92 | } |
98 | 93 | ||