aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/src/call_hierarchy.rs29
-rw-r--r--crates/ra_ide/src/completion.rs125
-rw-r--r--crates/ra_ide/src/completion/complete_attribute.rs6
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs65
-rw-r--r--crates/ra_ide/src/completion/complete_trait_impl.rs61
-rw-r--r--crates/ra_ide/src/completion/complete_unqualified_path.rs36
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs13
-rw-r--r--crates/ra_ide/src/completion/presentation.rs60
-rw-r--r--crates/ra_ide/src/diagnostics.rs2
-rw-r--r--crates/ra_ide/src/display.rs7
-rw-r--r--crates/ra_ide/src/display/function_signature.rs13
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs66
-rw-r--r--crates/ra_ide/src/display/structure.rs19
-rw-r--r--crates/ra_ide/src/expand_macro.rs11
-rw-r--r--crates/ra_ide/src/extend_selection.rs12
-rw-r--r--crates/ra_ide/src/goto_definition.rs11
-rw-r--r--crates/ra_ide/src/goto_implementation.rs (renamed from crates/ra_ide/src/impls.rs)11
-rw-r--r--crates/ra_ide/src/goto_type_definition.rs11
-rw-r--r--crates/ra_ide/src/hover.rs264
-rw-r--r--crates/ra_ide/src/inlay_hints.rs22
-rw-r--r--crates/ra_ide/src/join_lines.rs11
-rw-r--r--crates/ra_ide/src/lib.rs7
-rw-r--r--crates/ra_ide/src/matching_brace.rs13
-rw-r--r--crates/ra_ide/src/mock_analysis.rs123
-rw-r--r--crates/ra_ide/src/parent_module.rs12
-rw-r--r--crates/ra_ide/src/references.rs27
-rw-r--r--crates/ra_ide/src/runnables.rs440
-rw-r--r--crates/ra_ide/src/snapshots/highlight_injection.html2
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html3
-rw-r--r--crates/ra_ide/src/snapshots/highlight_unsafe.html48
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html14
-rw-r--r--crates/ra_ide/src/snapshots/rainbow_highlighting.html2
-rw-r--r--crates/ra_ide/src/ssr.rs32
-rw-r--r--crates/ra_ide/src/status.rs11
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs223
-rw-r--r--crates/ra_ide/src/syntax_highlighting/html.rs2
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tags.rs16
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs32
-rw-r--r--crates/ra_ide/src/syntax_tree.rs16
-rw-r--r--crates/ra_ide/src/typing.rs7
-rw-r--r--crates/ra_ide/src/typing/on_enter.rs11
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
3mod completion_config; 1mod completion_config;
4mod completion_item; 2mod completion_item;
5mod completion_context; 3mod 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)]
130mod 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 2use ra_assists::utils::TryEnum;
3use ra_syntax::{ 3use ra_syntax::{
4 ast::{self, AstNode}, 4 ast::{self, AstNode},
5 TextRange, TextSize, 5 TextRange, TextSize,
6}; 6};
7use ra_text_edit::TextEdit; 7use ra_text_edit::TextEdit;
8 8
9use super::completion_config::SnippetCap;
10use crate::{ 9use 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};
17use ra_assists::utils::TryEnum; 16
17use super::completion_config::SnippetCap;
18 18
19pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { 19pub(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
189fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String { 199fn 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::{
49pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { 49pub(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::{
12use ra_text_edit::Indel; 12use ra_text_edit::Indel;
13 13
14use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; 14use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition};
15use 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::{
10use hir::{Docs, Documentation, HasSource, HirDisplay}; 10use hir::{Docs, Documentation, HasSource, HirDisplay};
11use ra_ide_db::RootDatabase; 11use ra_ide_db::RootDatabase;
12use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; 12use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
13use stdx::SepBy; 13use stdx::{split1, SepBy};
14 14
15use crate::display::{generic_parameters, where_predicates}; 15use 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
3use crate::TextRange;
4
5use ra_syntax::{ 1use 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// |===
21pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> { 30pub 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
3use hir::Semantics; 1use hir::Semantics;
4use ra_ide_db::RootDatabase; 2use ra_ide_db::RootDatabase;
5use ra_syntax::{ 3use 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// |===
17pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { 24pub(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
3use std::iter::successors; 1use std::iter::successors;
4 2
5use hir::Semantics; 3use hir::Semantics;
@@ -14,6 +12,16 @@ use ra_syntax::{
14 12
15use crate::FileRange; 13use 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// |===
17pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { 25pub(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
3use hir::Semantics; 1use hir::Semantics;
4use ra_ide_db::{ 2use 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// |===
20pub(crate) fn goto_definition( 27pub(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
3use hir::{Crate, ImplDef, Semantics}; 1use hir::{Crate, ImplDef, Semantics};
4use ra_ide_db::RootDatabase; 2use ra_ide_db::RootDatabase;
5use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; 3use ra_syntax::{algo::find_node_at_offset, ast, AstNode};
6 4
7use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; 5use 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// |===
9pub(crate) fn goto_implementation( 16pub(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
3use ra_ide_db::RootDatabase; 1use ra_ide_db::RootDatabase;
4use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; 2use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
5 3
6use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; 4use 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// |===
8pub(crate) fn goto_type_definition( 15pub(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 1use std::iter::once;
2//! source code items (e.g. function call, struct field, variable symbol...)
3 2
4use hir::{ 3use 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};
7use itertools::Itertools;
8use ra_db::SourceDatabase; 8use ra_db::SourceDatabase;
9use ra_ide_db::{ 9use 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};
13use ra_syntax::{ 13use 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
20use crate::{ 15use 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};
24use itertools::Itertools;
25use 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.
62pub(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
65fn hover_text( 115fn 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
163pub(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
216fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { 217fn 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 &["
505Option 506Option
507```
508
509```rust
506None 510None
507``` 511```
512___
508 513
509The None variant 514The None variant
510 " 515 "
@@ -524,8 +529,12 @@ The None variant
524 "#, 529 "#,
525 &[" 530 &["
526Option 531Option
532```
533
534```rust
527Some 535Some
528``` 536```
537___
529 538
530The Some variant 539The 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
3use hir::{Adt, HirDisplay, Semantics, Type}; 1use hir::{Adt, HirDisplay, Semantics, Type};
4use ra_ide_db::RootDatabase; 2use ra_ide_db::RootDatabase;
5use ra_prof::profile; 3use 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// |===
42pub(crate) fn inlay_hints( 60pub(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
3use itertools::Itertools; 1use itertools::Itertools;
4use ra_fmt::{compute_ws, extract_trivial_expression}; 2use ra_fmt::{compute_ws, extract_trivial_expression};
5use ra_syntax::{ 3use ra_syntax::{
@@ -11,6 +9,15 @@ use ra_syntax::{
11}; 9};
12use ra_text_edit::{TextEdit, TextEditBuilder}; 10use 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// |===
14pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { 21pub 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;
23mod runnables; 23mod runnables;
24mod goto_definition; 24mod goto_definition;
25mod goto_type_definition; 25mod goto_type_definition;
26mod goto_implementation;
26mod extend_selection; 27mod extend_selection;
27mod hover; 28mod hover;
28mod call_hierarchy; 29mod call_hierarchy;
@@ -30,7 +31,6 @@ mod call_info;
30mod syntax_highlighting; 31mod syntax_highlighting;
31mod parent_module; 32mod parent_module;
32mod references; 33mod references;
33mod impls;
34mod diagnostics; 34mod diagnostics;
35mod syntax_tree; 35mod syntax_tree;
36mod folding_ranges; 36mod 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
3use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextSize, T}; 1use 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// |===
5pub fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> { 14pub 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
3use std::str::FromStr;
3use std::sync::Arc; 4use std::sync::Arc;
4 5
5use ra_cfg::CfgOptions; 6use ra_cfg::CfgOptions;
6use ra_db::{CrateName, Env, RelativePathBuf}; 7use ra_db::{CrateName, Env, RelativePathBuf};
7use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER}; 8use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER};
8 9
9use crate::{ 10use 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)]
16enum MockFileData {
17 Plain { path: String, content: String },
18 Fixture(FixtureEntry),
19}
20
21impl 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
68impl 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)]
17pub struct MockAnalysis { 77pub struct MockAnalysis {
18 files: Vec<(String, String)>, 78 files: Vec<MockFileData>,
19} 79}
20 80
21impl MockAnalysis { 81impl 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
3use hir::Semantics; 1use hir::Semantics;
4use ra_db::{CrateId, FileId, FilePosition}; 2use ra_db::{CrateId, FileId, FilePosition};
5use ra_ide_db::RootDatabase; 3use ra_ide_db::RootDatabase;
@@ -11,6 +9,16 @@ use test_utils::mark;
11 9
12use crate::NavigationTarget; 10use 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.
16pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { 24pub(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 1use std::fmt;
2 2
3use hir::{AsAssocItem, Semantics}; 3use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics};
4use itertools::Itertools; 4use itertools::Itertools;
5use ra_cfg::CfgExpr;
5use ra_ide_db::RootDatabase; 6use ra_ide_db::RootDatabase;
6use ra_syntax::{ 7use 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
11use crate::FileId; 12use crate::{display::ToNav, FileId, NavigationTarget};
12use ast::DocCommentsOwner;
13use std::fmt::Display;
14 13
15#[derive(Debug)] 14#[derive(Debug)]
16pub struct Runnable { 15pub 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
27impl Display for TestId { 27impl 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// |===
45pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 56pub(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
51fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { 62fn 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
61fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Runnable> { 72fn 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
150fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> { 171fn 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">// =&gt; "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">// =&gt; "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">// =&gt; "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">// =&gt; "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">// =&gt; "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">// =&gt; "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">// =&gt; "{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>
3body { margin: 0; }
4pre { 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>) -&gt; <span class="builtin_type">i32</span>; 39 <span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="self_keyword">self</span>) -&gt; <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>) -&gt; <span class="builtin_type">i32</span> { 43 <span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="self_keyword">self</span>) -&gt; <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>&lt;<span class="type_param declaration">T</span>&gt; <span class="enum">Option</span>&lt;<span class="type_param">T</span>&gt; { 95<span class="keyword">impl</span>&lt;<span class="type_param declaration">T</span>&gt; <span class="enum">Option</span>&lt;<span class="type_param">T</span>&gt; {
94 <span class="keyword">fn</span> <span class="function declaration">and</span>&lt;<span class="type_param declaration">U</span>&gt;(<span class="keyword">self</span>, <span class="variable declaration">other</span>: <span class="enum">Option</span>&lt;<span class="type_param">U</span>&gt;) -&gt; <span class="enum">Option</span>&lt;(<span class="type_param">T</span>, <span class="type_param">U</span>)&gt; { 96 <span class="keyword">fn</span> <span class="function declaration">and</span>&lt;<span class="type_param declaration">U</span>&gt;(<span class="self_keyword">self</span>, <span class="variable declaration">other</span>: <span class="enum">Option</span>&lt;<span class="type_param">U</span>&gt;) -&gt; <span class="enum">Option</span>&lt;(<span class="type_param">T</span>, <span class="type_param">U</span>)&gt; {
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> =&gt; <span class="macro">unimplemented!</span>(), 98 <span class="enum_variant">None</span> =&gt; <span class="macro">unimplemented!</span>(),
97 <span class="variable declaration">Nope</span> =&gt; <span class="variable">Nope</span>, 99 <span class="variable declaration">Nope</span> =&gt; <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
3use std::{collections::HashMap, iter::once, str::FromStr}; 1use std::{collections::HashMap, iter::once, str::FromStr};
4 2
5use ra_db::{SourceDatabase, SourceDatabaseExt}; 3use ra_db::{SourceDatabase, SourceDatabaseExt};
@@ -25,6 +23,28 @@ impl std::fmt::Display for SsrError {
25 23
26impl std::error::Error for SsrError {} 24impl 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// |===
28pub fn parse_search_replace( 48pub 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
3use std::{fmt, iter::FromIterator, sync::Arc}; 1use std::{fmt, iter::FromIterator, sync::Arc};
4 2
5use hir::MacroFile; 3use 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// |===
29pub(crate) fn status(db: &RootDatabase) -> String { 36pub(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
3mod tags; 1mod tags;
4mod html; 2mod 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
36struct 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//
42impl 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
110pub(crate) fn highlight( 42pub(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)]
227struct HighlightedRangeStack {
228 stack: Vec<Vec<HighlightedRange>>,
229}
230
231/// We use a stack to implement the flattening logic for the highlighted
232/// syntax ranges.
233impl 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
294fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { 301fn 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
480fn highlight_name_by_syntax(name: ast::Name) -> Highlight { 515fn 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
500fn highlight_injection( 543fn 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)]
16pub enum HighlightTag { 16pub 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)]
47pub enum HighlightModifier { 50pub 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
96impl HighlightModifier { 104impl 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]
263fn test_unsafe_highlighting() {
264 let (analysis, file_id) = single_file(
265 r#"
266unsafe fn unsafe_fn() {}
267
268struct HasUnsafeFn;
269
270impl HasUnsafeFn {
271 unsafe fn unsafe_method(&self) {}
272}
273
274fn 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 1use ra_db::{FileId, SourceDatabase};
2
3use ra_db::SourceDatabase;
4use ra_ide_db::RootDatabase; 2use ra_ide_db::RootDatabase;
5use ra_syntax::{ 3use 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
11pub 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// |===
13pub(crate) fn syntax_tree( 19pub(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
33pub(crate) const TRIGGER_CHARS: &str = ".=>"; 33pub(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
35pub(crate) fn on_char_typed( 42pub(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};
12use ra_text_edit::TextEdit; 12use ra_text_edit::TextEdit;
13 13
14use crate::{SourceChange, SourceFileEdit}; 14pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> {
15
16pub(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
49fn followed_by_comment(comment: &ast::Comment) -> bool { 45fn 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