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/complete_attribute.rs6
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs59
-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.rs103
-rw-r--r--crates/ra_ide/src/display.rs7
-rw-r--r--crates/ra_ide/src/hover.rs21
-rw-r--r--crates/ra_ide/src/lib.rs21
-rw-r--r--crates/ra_ide/src/mock_analysis.rs123
-rw-r--r--crates/ra_ide/src/references/rename.rs11
-rw-r--r--crates/ra_ide/src/runnables.rs189
-rw-r--r--crates/ra_ide/src/snapshots/highlight_injection.html1
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html1
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html13
-rw-r--r--crates/ra_ide/src/snapshots/rainbow_highlighting.html1
-rw-r--r--crates/ra_ide/src/ssr.rs8
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs36
-rw-r--r--crates/ra_ide/src/syntax_highlighting/html.rs1
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tags.rs10
-rw-r--r--crates/ra_ide/src/typing.rs35
-rw-r--r--crates/ra_ide/src/typing/on_enter.rs14
24 files changed, 640 insertions, 219 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/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..02e660ca8 100644
--- a/crates/ra_ide/src/completion/complete_postfix.rs
+++ b/crates/ra_ide/src/completion/complete_postfix.rs
@@ -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 c2819bbf7..15dc50cf1 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -21,7 +21,7 @@ use ra_syntax::{
21}; 21};
22use ra_text_edit::{TextEdit, TextEditBuilder}; 22use ra_text_edit::{TextEdit, TextEditBuilder};
23 23
24use crate::{Diagnostic, FileId, FileSystemEdit, SourceChange, SourceFileEdit}; 24use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceChange, SourceFileEdit};
25 25
26#[derive(Debug, Copy, Clone)] 26#[derive(Debug, Copy, Clone)]
27pub enum Severity { 27pub enum Severity {
@@ -63,8 +63,8 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
63 .parent() 63 .parent()
64 .unwrap_or_else(|| RelativePath::new("")) 64 .unwrap_or_else(|| RelativePath::new(""))
65 .join(&d.candidate); 65 .join(&d.candidate);
66 let create_file = FileSystemEdit::CreateFile { source_root, path }; 66 let fix =
67 let fix = SourceChange::file_system_edit("Create module", create_file); 67 Fix::new("Create module", FileSystemEdit::CreateFile { source_root, path }.into());
68 res.borrow_mut().push(Diagnostic { 68 res.borrow_mut().push(Diagnostic {
69 range: sema.diagnostics_range(d).range, 69 range: sema.diagnostics_range(d).range,
70 message: d.message(), 70 message: d.message(),
@@ -88,14 +88,12 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
88 field_list = field_list.append_field(&field); 88 field_list = field_list.append_field(&field);
89 } 89 }
90 90
91 let mut builder = TextEditBuilder::default(); 91 let edit = {
92 algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); 92 let mut builder = TextEditBuilder::default();
93 93 algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder);
94 Some(SourceChange::source_file_edit_from( 94 builder.finish()
95 "Fill struct fields", 95 };
96 file_id, 96 Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into()))
97 builder.finish(),
98 ))
99 }; 97 };
100 98
101 res.borrow_mut().push(Diagnostic { 99 res.borrow_mut().push(Diagnostic {
@@ -117,7 +115,8 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
117 let node = d.ast(db); 115 let node = d.ast(db);
118 let replacement = format!("Ok({})", node.syntax()); 116 let replacement = format!("Ok({})", node.syntax());
119 let edit = TextEdit::replace(node.syntax().text_range(), replacement); 117 let edit = TextEdit::replace(node.syntax().text_range(), replacement);
120 let fix = SourceChange::source_file_edit_from("Wrap with ok", file_id, edit); 118 let source_change = SourceChange::source_file_edit_from(file_id, edit);
119 let fix = Fix::new("Wrap with ok", source_change);
121 res.borrow_mut().push(Diagnostic { 120 res.borrow_mut().push(Diagnostic {
122 range: sema.diagnostics_range(d).range, 121 range: sema.diagnostics_range(d).range,
123 message: d.message(), 122 message: d.message(),
@@ -154,9 +153,9 @@ fn check_unnecessary_braces_in_use_statement(
154 range, 153 range,
155 message: "Unnecessary braces in use statement".to_string(), 154 message: "Unnecessary braces in use statement".to_string(),
156 severity: Severity::WeakWarning, 155 severity: Severity::WeakWarning,
157 fix: Some(SourceChange::source_file_edit( 156 fix: Some(Fix::new(
158 "Remove unnecessary braces", 157 "Remove unnecessary braces",
159 SourceFileEdit { file_id, edit }, 158 SourceFileEdit { file_id, edit }.into(),
160 )), 159 )),
161 }); 160 });
162 } 161 }
@@ -198,9 +197,9 @@ fn check_struct_shorthand_initialization(
198 range: record_field.syntax().text_range(), 197 range: record_field.syntax().text_range(),
199 message: "Shorthand struct initialization".to_string(), 198 message: "Shorthand struct initialization".to_string(),
200 severity: Severity::WeakWarning, 199 severity: Severity::WeakWarning,
201 fix: Some(SourceChange::source_file_edit( 200 fix: Some(Fix::new(
202 "Use struct shorthand initialization", 201 "Use struct shorthand initialization",
203 SourceFileEdit { file_id, edit }, 202 SourceFileEdit { file_id, edit }.into(),
204 )), 203 )),
205 }); 204 });
206 } 205 }
@@ -240,7 +239,7 @@ mod tests {
240 let diagnostic = 239 let diagnostic =
241 diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); 240 diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before));
242 let mut fix = diagnostic.fix.unwrap(); 241 let mut fix = diagnostic.fix.unwrap();
243 let edit = fix.source_file_edits.pop().unwrap().edit; 242 let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
244 let actual = { 243 let actual = {
245 let mut actual = before.to_string(); 244 let mut actual = before.to_string();
246 edit.apply(&mut actual); 245 edit.apply(&mut actual);
@@ -258,7 +257,7 @@ mod tests {
258 let (analysis, file_position) = analysis_and_position(fixture); 257 let (analysis, file_position) = analysis_and_position(fixture);
259 let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap(); 258 let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap();
260 let mut fix = diagnostic.fix.unwrap(); 259 let mut fix = diagnostic.fix.unwrap();
261 let edit = fix.source_file_edits.pop().unwrap().edit; 260 let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
262 let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); 261 let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
263 let actual = { 262 let actual = {
264 let mut actual = target_file_contents.to_string(); 263 let mut actual = target_file_contents.to_string();
@@ -295,7 +294,7 @@ mod tests {
295 let (analysis, file_id) = single_file(before); 294 let (analysis, file_id) = single_file(before);
296 let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); 295 let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap();
297 let mut fix = diagnostic.fix.unwrap(); 296 let mut fix = diagnostic.fix.unwrap();
298 let edit = fix.source_file_edits.pop().unwrap().edit; 297 let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
299 let actual = { 298 let actual = {
300 let mut actual = before.to_string(); 299 let mut actual = before.to_string();
301 edit.apply(&mut actual); 300 edit.apply(&mut actual);
@@ -616,22 +615,24 @@ mod tests {
616 Diagnostic { 615 Diagnostic {
617 message: "unresolved module", 616 message: "unresolved module",
618 range: 0..8, 617 range: 0..8,
618 severity: Error,
619 fix: Some( 619 fix: Some(
620 SourceChange { 620 Fix {
621 label: "Create module", 621 label: "Create module",
622 source_file_edits: [], 622 source_change: SourceChange {
623 file_system_edits: [ 623 source_file_edits: [],
624 CreateFile { 624 file_system_edits: [
625 source_root: SourceRootId( 625 CreateFile {
626 0, 626 source_root: SourceRootId(
627 ), 627 0,
628 path: "foo.rs", 628 ),
629 }, 629 path: "foo.rs",
630 ], 630 },
631 is_snippet: false, 631 ],
632 is_snippet: false,
633 },
632 }, 634 },
633 ), 635 ),
634 severity: Error,
635 }, 636 },
636 ] 637 ]
637 "###); 638 "###);
@@ -663,31 +664,33 @@ mod tests {
663 assert_debug_snapshot!(diagnostics, @r###" 664 assert_debug_snapshot!(diagnostics, @r###"
664 [ 665 [
665 Diagnostic { 666 Diagnostic {
666 message: "Missing structure fields:\n- b", 667 message: "Missing structure fields:\n- b\n",
667 range: 224..233, 668 range: 224..233,
669 severity: Error,
668 fix: Some( 670 fix: Some(
669 SourceChange { 671 Fix {
670 label: "Fill struct fields", 672 label: "Fill struct fields",
671 source_file_edits: [ 673 source_change: SourceChange {
672 SourceFileEdit { 674 source_file_edits: [
673 file_id: FileId( 675 SourceFileEdit {
674 1, 676 file_id: FileId(
675 ), 677 1,
676 edit: TextEdit { 678 ),
677 indels: [ 679 edit: TextEdit {
678 Indel { 680 indels: [
679 insert: "{a:42, b: ()}", 681 Indel {
680 delete: 3..9, 682 insert: "{a:42, b: ()}",
681 }, 683 delete: 3..9,
682 ], 684 },
685 ],
686 },
683 }, 687 },
684 }, 688 ],
685 ], 689 file_system_edits: [],
686 file_system_edits: [], 690 is_snippet: false,
687 is_snippet: false, 691 },
688 }, 692 },
689 ), 693 ),
690 severity: Error,
691 }, 694 },
692 ] 695 ]
693 "###); 696 "###);
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/hover.rs b/crates/ra_ide/src/hover.rs
index befa977c7..3e721dcca 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -405,7 +405,7 @@ mod tests {
405 }; 405 };
406 } 406 }
407 "#, 407 "#,
408 &["Foo\nfield_a: u32"], 408 &["Foo\n```\n\n```rust\nfield_a: u32"],
409 ); 409 );
410 410
411 // Hovering over the field in the definition 411 // Hovering over the field in the definition
@@ -422,7 +422,7 @@ mod tests {
422 }; 422 };
423 } 423 }
424 "#, 424 "#,
425 &["Foo\nfield_a: u32"], 425 &["Foo\n```\n\n```rust\nfield_a: u32"],
426 ); 426 );
427 } 427 }
428 428
@@ -475,7 +475,7 @@ fn main() {
475 ", 475 ",
476 ); 476 );
477 let hover = analysis.hover(position).unwrap().unwrap(); 477 let hover = analysis.hover(position).unwrap().unwrap();
478 assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\nSome")); 478 assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\n```\n\n```rust\nSome"));
479 479
480 let (analysis, position) = single_file_with_position( 480 let (analysis, position) = single_file_with_position(
481 " 481 "
@@ -503,8 +503,12 @@ fn main() {
503 "#, 503 "#,
504 &[" 504 &["
505Option 505Option
506```
507
508```rust
506None 509None
507``` 510```
511___
508 512
509The None variant 513The None variant
510 " 514 "
@@ -524,8 +528,12 @@ The None variant
524 "#, 528 "#,
525 &[" 529 &["
526Option 530Option
531```
532
533```rust
527Some 534Some
528``` 535```
536___
529 537
530The Some variant 538The Some variant
531 " 539 "
@@ -606,7 +614,10 @@ fn func(foo: i32) { if true { <|>foo; }; }
606 ", 614 ",
607 ); 615 );
608 let hover = analysis.hover(position).unwrap().unwrap(); 616 let hover = analysis.hover(position).unwrap().unwrap();
609 assert_eq!(trim_markup_opt(hover.info.first()), Some("wrapper::Thing\nfn new() -> Thing")); 617 assert_eq!(
618 trim_markup_opt(hover.info.first()),
619 Some("wrapper::Thing\n```\n\n```rust\nfn new() -> Thing")
620 );
610 } 621 }
611 622
612 #[test] 623 #[test]
@@ -882,7 +893,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
882 fo<|>o(); 893 fo<|>o();
883 } 894 }
884 ", 895 ",
885 &["fn foo()\n```\n\n<- `\u{3000}` here"], 896 &["fn foo()\n```\n___\n\n<- `\u{3000}` here"],
886 ); 897 );
887 } 898 }
888 899
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 97ff67ee8..d983cd910 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -97,8 +97,22 @@ pub type Cancelable<T> = Result<T, Canceled>;
97pub struct Diagnostic { 97pub struct Diagnostic {
98 pub message: String, 98 pub message: String,
99 pub range: TextRange, 99 pub range: TextRange,
100 pub fix: Option<SourceChange>,
101 pub severity: Severity, 100 pub severity: Severity,
101 pub fix: Option<Fix>,
102}
103
104#[derive(Debug)]
105pub struct Fix {
106 pub label: String,
107 pub source_change: SourceChange,
108}
109
110impl Fix {
111 pub fn new(label: impl Into<String>, source_change: SourceChange) -> Self {
112 let label = label.into();
113 assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.'));
114 Self { label, source_change }
115 }
102} 116}
103 117
104/// Info associated with a text range. 118/// Info associated with a text range.
@@ -295,7 +309,8 @@ impl Analysis {
295 309
296 /// 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
297 /// up minor stuff like continuing the comment. 311 /// up minor stuff like continuing the comment.
298 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>> {
299 self.with_db(|db| typing::on_enter(&db, position)) 314 self.with_db(|db| typing::on_enter(&db, position))
300 } 315 }
301 316
@@ -493,7 +508,7 @@ impl Analysis {
493 ) -> Cancelable<Result<SourceChange, SsrError>> { 508 ) -> Cancelable<Result<SourceChange, SsrError>> {
494 self.with_db(|db| { 509 self.with_db(|db| {
495 let edits = ssr::parse_search_replace(query, parse_only, db)?; 510 let edits = ssr::parse_search_replace(query, parse_only, db)?;
496 Ok(SourceChange::source_file_edits("Structural Search Replace", edits)) 511 Ok(SourceChange::source_file_edits(edits))
497 }) 512 })
498 } 513 }
499 514
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/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index fd2163dad..28c6349b1 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -128,7 +128,7 @@ fn rename_mod(
128 source_file_edits.extend(ref_edits); 128 source_file_edits.extend(ref_edits);
129 } 129 }
130 130
131 Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits)) 131 Some(SourceChange::from_edits(source_file_edits, file_system_edits))
132} 132}
133 133
134fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { 134fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> {
@@ -171,7 +171,7 @@ fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo
171 ), 171 ),
172 }); 172 });
173 173
174 Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits))) 174 Some(RangeInfo::new(range, SourceChange::source_file_edits(edits)))
175} 175}
176 176
177fn text_edit_from_self_param( 177fn text_edit_from_self_param(
@@ -234,7 +234,7 @@ fn rename_self_to_param(
234 let range = ast::SelfParam::cast(self_token.parent()) 234 let range = ast::SelfParam::cast(self_token.parent())
235 .map_or(self_token.text_range(), |p| p.syntax().text_range()); 235 .map_or(self_token.text_range(), |p| p.syntax().text_range());
236 236
237 Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits))) 237 Some(RangeInfo::new(range, SourceChange::source_file_edits(edits)))
238} 238}
239 239
240fn rename_reference( 240fn rename_reference(
@@ -253,7 +253,7 @@ fn rename_reference(
253 return None; 253 return None;
254 } 254 }
255 255
256 Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edit))) 256 Some(RangeInfo::new(range, SourceChange::source_file_edits(edit)))
257} 257}
258 258
259#[cfg(test)] 259#[cfg(test)]
@@ -642,7 +642,6 @@ mod tests {
642 RangeInfo { 642 RangeInfo {
643 range: 4..7, 643 range: 4..7,
644 info: SourceChange { 644 info: SourceChange {
645 label: "Rename",
646 source_file_edits: [ 645 source_file_edits: [
647 SourceFileEdit { 646 SourceFileEdit {
648 file_id: FileId( 647 file_id: FileId(
@@ -694,7 +693,6 @@ mod tests {
694 RangeInfo { 693 RangeInfo {
695 range: 4..7, 694 range: 4..7,
696 info: SourceChange { 695 info: SourceChange {
697 label: "Rename",
698 source_file_edits: [ 696 source_file_edits: [
699 SourceFileEdit { 697 SourceFileEdit {
700 file_id: FileId( 698 file_id: FileId(
@@ -777,7 +775,6 @@ mod tests {
777 RangeInfo { 775 RangeInfo {
778 range: 8..11, 776 range: 8..11,
779 info: SourceChange { 777 info: SourceChange {
780 label: "Rename",
781 source_file_edits: [ 778 source_file_edits: [
782 SourceFileEdit { 779 SourceFileEdit {
783 file_id: FileId( 780 file_id: FileId(
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index 131b8f307..6e7e47199 100644
--- a/crates/ra_ide/src/runnables.rs
+++ b/crates/ra_ide/src/runnables.rs
@@ -1,6 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use hir::{AsAssocItem, Semantics}; 3use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics};
4use itertools::Itertools; 4use itertools::Itertools;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{
@@ -10,12 +10,14 @@ use ra_syntax::{
10 10
11use crate::FileId; 11use crate::FileId;
12use ast::DocCommentsOwner; 12use ast::DocCommentsOwner;
13use ra_cfg::CfgExpr;
13use std::fmt::Display; 14use std::fmt::Display;
14 15
15#[derive(Debug)] 16#[derive(Debug)]
16pub struct Runnable { 17pub struct Runnable {
17 pub range: TextRange, 18 pub range: TextRange,
18 pub kind: RunnableKind, 19 pub kind: RunnableKind,
20 pub cfg_exprs: Vec<CfgExpr>,
19} 21}
20 22
21#[derive(Debug)] 23#[derive(Debug)]
@@ -45,29 +47,33 @@ pub enum RunnableKind {
45pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 47pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
46 let sema = Semantics::new(db); 48 let sema = Semantics::new(db);
47 let source_file = sema.parse(file_id); 49 let source_file = sema.parse(file_id);
48 source_file.syntax().descendants().filter_map(|i| runnable(&sema, i)).collect() 50 source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect()
49} 51}
50 52
51fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { 53fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> {
52 match_ast! { 54 match_ast! {
53 match item { 55 match item {
54 ast::FnDef(it) => runnable_fn(sema, it), 56 ast::FnDef(it) => runnable_fn(sema, it, file_id),
55 ast::Module(it) => runnable_mod(sema, it), 57 ast::Module(it) => runnable_mod(sema, it, file_id),
56 _ => None, 58 _ => None,
57 } 59 }
58 } 60 }
59} 61}
60 62
61fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Runnable> { 63fn runnable_fn(
64 sema: &Semantics<RootDatabase>,
65 fn_def: ast::FnDef,
66 file_id: FileId,
67) -> Option<Runnable> {
62 let name_string = fn_def.name()?.text().to_string(); 68 let name_string = fn_def.name()?.text().to_string();
63 69
64 let kind = if name_string == "main" { 70 let kind = if name_string == "main" {
65 RunnableKind::Bin 71 RunnableKind::Bin
66 } else { 72 } else {
67 let test_id = if let Some(module) = sema.to_def(&fn_def).map(|def| def.module(sema.db)) { 73 let test_id = match sema.to_def(&fn_def).map(|def| def.module(sema.db)) {
68 let def = sema.to_def(&fn_def)?; 74 Some(module) => {
69 let impl_trait_name = 75 let def = sema.to_def(&fn_def)?;
70 def.as_assoc_item(sema.db).and_then(|assoc_item| { 76 let impl_trait_name = def.as_assoc_item(sema.db).and_then(|assoc_item| {
71 match assoc_item.container(sema.db) { 77 match assoc_item.container(sema.db) {
72 hir::AssocItemContainer::Trait(trait_item) => { 78 hir::AssocItemContainer::Trait(trait_item) => {
73 Some(trait_item.name(sema.db).to_string()) 79 Some(trait_item.name(sema.db).to_string())
@@ -79,25 +85,25 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
79 } 85 }
80 }); 86 });
81 87
82 let path_iter = module 88 let path_iter = module
83 .path_to_root(sema.db) 89 .path_to_root(sema.db)
84 .into_iter() 90 .into_iter()
85 .rev() 91 .rev()
86 .filter_map(|it| it.name(sema.db)) 92 .filter_map(|it| it.name(sema.db))
87 .map(|name| name.to_string()); 93 .map(|name| name.to_string());
88 94
89 let path = if let Some(impl_trait_name) = impl_trait_name { 95 let path = if let Some(impl_trait_name) = impl_trait_name {
90 path_iter 96 path_iter
91 .chain(std::iter::once(impl_trait_name)) 97 .chain(std::iter::once(impl_trait_name))
92 .chain(std::iter::once(name_string)) 98 .chain(std::iter::once(name_string))
93 .join("::") 99 .join("::")
94 } else { 100 } else {
95 path_iter.chain(std::iter::once(name_string)).join("::") 101 path_iter.chain(std::iter::once(name_string)).join("::")
96 }; 102 };
97 103
98 TestId::Path(path) 104 TestId::Path(path)
99 } else { 105 }
100 TestId::Name(name_string) 106 None => TestId::Name(name_string),
101 }; 107 };
102 108
103 if has_test_related_attribute(&fn_def) { 109 if has_test_related_attribute(&fn_def) {
@@ -111,7 +117,12 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
111 return None; 117 return None;
112 } 118 }
113 }; 119 };
114 Some(Runnable { range: fn_def.syntax().text_range(), kind }) 120
121 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def));
122 let cfg_exprs =
123 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
124
125 Some(Runnable { range: fn_def.syntax().text_range(), kind, cfg_exprs })
115} 126}
116 127
117#[derive(Debug)] 128#[derive(Debug)]
@@ -147,7 +158,11 @@ fn has_doc_test(fn_def: &ast::FnDef) -> bool {
147 fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```")) 158 fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```"))
148} 159}
149 160
150fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> { 161fn runnable_mod(
162 sema: &Semantics<RootDatabase>,
163 module: ast::Module,
164 file_id: FileId,
165) -> Option<Runnable> {
151 let has_test_function = module 166 let has_test_function = module
152 .item_list()? 167 .item_list()?
153 .items() 168 .items()
@@ -160,11 +175,20 @@ fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<R
160 return None; 175 return None;
161 } 176 }
162 let range = module.syntax().text_range(); 177 let range = module.syntax().text_range();
163 let module = sema.to_def(&module)?; 178 let module_def = sema.to_def(&module)?;
164 179
165 let path = 180 let path = module_def
166 module.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::"); 181 .path_to_root(sema.db)
167 Some(Runnable { range, kind: RunnableKind::TestMod { path } }) 182 .into_iter()
183 .rev()
184 .filter_map(|it| it.name(sema.db))
185 .join("::");
186
187 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module));
188 let cfg_exprs =
189 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
190
191 Some(Runnable { range, kind: RunnableKind::TestMod { path }, cfg_exprs })
168} 192}
169 193
170#[cfg(test)] 194#[cfg(test)]
@@ -196,6 +220,7 @@ mod tests {
196 Runnable { 220 Runnable {
197 range: 1..21, 221 range: 1..21,
198 kind: Bin, 222 kind: Bin,
223 cfg_exprs: [],
199 }, 224 },
200 Runnable { 225 Runnable {
201 range: 22..46, 226 range: 22..46,
@@ -207,6 +232,7 @@ mod tests {
207 ignore: false, 232 ignore: false,
208 }, 233 },
209 }, 234 },
235 cfg_exprs: [],
210 }, 236 },
211 Runnable { 237 Runnable {
212 range: 47..81, 238 range: 47..81,
@@ -218,6 +244,7 @@ mod tests {
218 ignore: true, 244 ignore: true,
219 }, 245 },
220 }, 246 },
247 cfg_exprs: [],
221 }, 248 },
222 ] 249 ]
223 "### 250 "###
@@ -245,6 +272,7 @@ mod tests {
245 Runnable { 272 Runnable {
246 range: 1..21, 273 range: 1..21,
247 kind: Bin, 274 kind: Bin,
275 cfg_exprs: [],
248 }, 276 },
249 Runnable { 277 Runnable {
250 range: 22..64, 278 range: 22..64,
@@ -253,6 +281,7 @@ mod tests {
253 "foo", 281 "foo",
254 ), 282 ),
255 }, 283 },
284 cfg_exprs: [],
256 }, 285 },
257 ] 286 ]
258 "### 287 "###
@@ -283,6 +312,7 @@ mod tests {
283 Runnable { 312 Runnable {
284 range: 1..21, 313 range: 1..21,
285 kind: Bin, 314 kind: Bin,
315 cfg_exprs: [],
286 }, 316 },
287 Runnable { 317 Runnable {
288 range: 51..105, 318 range: 51..105,
@@ -291,6 +321,7 @@ mod tests {
291 "Data::foo", 321 "Data::foo",
292 ), 322 ),
293 }, 323 },
324 cfg_exprs: [],
294 }, 325 },
295 ] 326 ]
296 "### 327 "###
@@ -318,6 +349,7 @@ mod tests {
318 kind: TestMod { 349 kind: TestMod {
319 path: "test_mod", 350 path: "test_mod",
320 }, 351 },
352 cfg_exprs: [],
321 }, 353 },
322 Runnable { 354 Runnable {
323 range: 28..57, 355 range: 28..57,
@@ -329,6 +361,7 @@ mod tests {
329 ignore: false, 361 ignore: false,
330 }, 362 },
331 }, 363 },
364 cfg_exprs: [],
332 }, 365 },
333 ] 366 ]
334 "### 367 "###
@@ -358,6 +391,7 @@ mod tests {
358 kind: TestMod { 391 kind: TestMod {
359 path: "foo::test_mod", 392 path: "foo::test_mod",
360 }, 393 },
394 cfg_exprs: [],
361 }, 395 },
362 Runnable { 396 Runnable {
363 range: 46..79, 397 range: 46..79,
@@ -369,6 +403,7 @@ mod tests {
369 ignore: false, 403 ignore: false,
370 }, 404 },
371 }, 405 },
406 cfg_exprs: [],
372 }, 407 },
373 ] 408 ]
374 "### 409 "###
@@ -400,6 +435,7 @@ mod tests {
400 kind: TestMod { 435 kind: TestMod {
401 path: "foo::bar::test_mod", 436 path: "foo::bar::test_mod",
402 }, 437 },
438 cfg_exprs: [],
403 }, 439 },
404 Runnable { 440 Runnable {
405 range: 68..105, 441 range: 68..105,
@@ -411,6 +447,89 @@ mod tests {
411 ignore: false, 447 ignore: false,
412 }, 448 },
413 }, 449 },
450 cfg_exprs: [],
451 },
452 ]
453 "###
454 );
455 }
456
457 #[test]
458 fn test_runnables_with_feature() {
459 let (analysis, pos) = analysis_and_position(
460 r#"
461 //- /lib.rs crate:foo cfg:feature=foo
462 <|> //empty
463 #[test]
464 #[cfg(feature = "foo")]
465 fn test_foo1() {}
466 "#,
467 );
468 let runnables = analysis.runnables(pos.file_id).unwrap();
469 assert_debug_snapshot!(&runnables,
470 @r###"
471 [
472 Runnable {
473 range: 1..58,
474 kind: Test {
475 test_id: Path(
476 "test_foo1",
477 ),
478 attr: TestAttr {
479 ignore: false,
480 },
481 },
482 cfg_exprs: [
483 KeyValue {
484 key: "feature",
485 value: "foo",
486 },
487 ],
488 },
489 ]
490 "###
491 );
492 }
493
494 #[test]
495 fn test_runnables_with_features() {
496 let (analysis, pos) = analysis_and_position(
497 r#"
498 //- /lib.rs crate:foo cfg:feature=foo,feature=bar
499 <|> //empty
500 #[test]
501 #[cfg(all(feature = "foo", feature = "bar"))]
502 fn test_foo1() {}
503 "#,
504 );
505 let runnables = analysis.runnables(pos.file_id).unwrap();
506 assert_debug_snapshot!(&runnables,
507 @r###"
508 [
509 Runnable {
510 range: 1..80,
511 kind: Test {
512 test_id: Path(
513 "test_foo1",
514 ),
515 attr: TestAttr {
516 ignore: false,
517 },
518 },
519 cfg_exprs: [
520 All(
521 [
522 KeyValue {
523 key: "feature",
524 value: "foo",
525 },
526 KeyValue {
527 key: "feature",
528 value: "bar",
529 },
530 ],
531 ),
532 ],
414 }, 533 },
415 ] 534 ]
416 "### 535 "###
diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/src/snapshots/highlight_injection.html
index ea026d7a0..68fc589bc 100644
--- a/crates/ra_ide/src/snapshots/highlight_injection.html
+++ b/crates/ra_ide/src/snapshots/highlight_injection.html
@@ -17,6 +17,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
17.type_param { color: #DFAF8F; } 17.type_param { color: #DFAF8F; }
18.attribute { color: #94BFF3; } 18.attribute { color: #94BFF3; }
19.numeric_literal { color: #BFEBBF; } 19.numeric_literal { color: #BFEBBF; }
20.bool_literal { color: #BFE6EB; }
20.macro { color: #94BFF3; } 21.macro { color: #94BFF3; }
21.module { color: #AFD8AF; } 22.module { color: #AFD8AF; }
22.variable { color: #DCDCCC; } 23.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..326744361 100644
--- a/crates/ra_ide/src/snapshots/highlight_strings.html
+++ b/crates/ra_ide/src/snapshots/highlight_strings.html
@@ -17,6 +17,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
17.type_param { color: #DFAF8F; } 17.type_param { color: #DFAF8F; }
18.attribute { color: #94BFF3; } 18.attribute { color: #94BFF3; }
19.numeric_literal { color: #BFEBBF; } 19.numeric_literal { color: #BFEBBF; }
20.bool_literal { color: #BFE6EB; }
20.macro { color: #94BFF3; } 21.macro { color: #94BFF3; }
21.module { color: #AFD8AF; } 22.module { color: #AFD8AF; }
22.variable { color: #DCDCCC; } 23.variable { color: #DCDCCC; }
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html
index 635fe5cf9..352e35095 100644
--- a/crates/ra_ide/src/snapshots/highlighting.html
+++ b/crates/ra_ide/src/snapshots/highlighting.html
@@ -17,6 +17,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
17.type_param { color: #DFAF8F; } 17.type_param { color: #DFAF8F; }
18.attribute { color: #94BFF3; } 18.attribute { color: #94BFF3; }
19.numeric_literal { color: #BFEBBF; } 19.numeric_literal { color: #BFEBBF; }
20.bool_literal { color: #BFE6EB; }
20.macro { color: #94BFF3; } 21.macro { color: #94BFF3; }
21.module { color: #AFD8AF; } 22.module { color: #AFD8AF; }
22.variable { color: #DCDCCC; } 23.variable { color: #DCDCCC; }
@@ -27,19 +28,19 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
27.keyword.unsafe { color: #BC8383; font-weight: bold; } 28.keyword.unsafe { color: #BC8383; font-weight: bold; }
28.control { font-style: italic; } 29.control { font-style: italic; }
29</style> 30</style>
30<pre><code><span class="attribute">#[derive(Clone, Debug)]</span> 31<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> { 32<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>, 33 <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>, 34 <span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>,
34} 35}
35 36
36<span class="keyword">trait</span> <span class="trait declaration">Bar</span> { 37<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>; 38 <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} 39}
39 40
40<span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> { 41<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> { 42 <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> 43 <span class="self_keyword">self</span>.<span class="field">x</span>
43 } 44 }
44} 45}
45 46
@@ -64,7 +65,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>); 65 <span class="macro">println!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>);
65 66
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>(); 67 <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> { 68 <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>; 69 <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> }); 70 <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 } 71 }
@@ -91,7 +92,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
91<span class="keyword">use</span> <span class="enum">Option</span>::*; 92<span class="keyword">use</span> <span class="enum">Option</span>::*;
92 93
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; { 94<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; { 95 <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> { 96 <span class="keyword control">match</span> <span class="variable">other</span> {
96 <span class="enum_variant">None</span> =&gt; <span class="macro">unimplemented!</span>(), 97 <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>, 98 <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..2a0294f71 100644
--- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html
+++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
@@ -17,6 +17,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
17.type_param { color: #DFAF8F; } 17.type_param { color: #DFAF8F; }
18.attribute { color: #94BFF3; } 18.attribute { color: #94BFF3; }
19.numeric_literal { color: #BFEBBF; } 19.numeric_literal { color: #BFEBBF; }
20.bool_literal { color: #BFE6EB; }
20.macro { color: #94BFF3; } 21.macro { color: #94BFF3; }
21.module { color: #AFD8AF; } 22.module { color: #AFD8AF; }
22.variable { color: #DCDCCC; } 23.variable { color: #DCDCCC; }
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 1873d1d0d..130d3b4c3 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -196,10 +196,10 @@ fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches {
196 ) -> Option<Match> { 196 ) -> Option<Match> {
197 let match_ = check_opt_nodes(pattern.path(), code.path(), placeholders, match_)?; 197 let match_ = check_opt_nodes(pattern.path(), code.path(), placeholders, match_)?;
198 198
199 let mut pattern_fields = 199 let mut pattern_fields: Vec<RecordField> =
200 pattern.record_field_list().map(|x| x.fields().collect()).unwrap_or(vec![]); 200 pattern.record_field_list().map(|x| x.fields().collect()).unwrap_or_default();
201 let mut code_fields = 201 let mut code_fields: Vec<RecordField> =
202 code.record_field_list().map(|x| x.fields().collect()).unwrap_or(vec![]); 202 code.record_field_list().map(|x| x.fields().collect()).unwrap_or_default();
203 203
204 if pattern_fields.len() != code_fields.len() { 204 if pattern_fields.len() != code_fields.len() {
205 return None; 205 return None;
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index be57eeb0a..8a995d779 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -361,7 +361,9 @@ fn highlight_element(
361 } 361 }
362 362
363 // Highlight references like the definitions they resolve to 363 // Highlight references like the definitions they resolve to
364 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => return None, 364 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => {
365 Highlight::from(HighlightTag::Function) | HighlightModifier::Attribute
366 }
365 NAME_REF => { 367 NAME_REF => {
366 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); 368 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
367 match classify_name_ref(sema, &name_ref) { 369 match classify_name_ref(sema, &name_ref) {
@@ -411,6 +413,8 @@ fn highlight_element(
411 | T![in] => h | HighlightModifier::ControlFlow, 413 | T![in] => h | HighlightModifier::ControlFlow,
412 T![for] if !is_child_of_impl(element) => h | HighlightModifier::ControlFlow, 414 T![for] if !is_child_of_impl(element) => h | HighlightModifier::ControlFlow,
413 T![unsafe] => h | HighlightModifier::Unsafe, 415 T![unsafe] => h | HighlightModifier::Unsafe,
416 T![true] | T![false] => HighlightTag::BoolLiteral.into(),
417 T![self] => HighlightTag::SelfKeyword.into(),
414 _ => h, 418 _ => h,
415 } 419 }
416 } 420 }
@@ -478,23 +482,31 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
478} 482}
479 483
480fn highlight_name_by_syntax(name: ast::Name) -> Highlight { 484fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
481 let default = HighlightTag::Function.into(); 485 let default = HighlightTag::UnresolvedReference;
482 486
483 let parent = match name.syntax().parent() { 487 let parent = match name.syntax().parent() {
484 Some(it) => it, 488 Some(it) => it,
485 _ => return default, 489 _ => return default.into(),
486 }; 490 };
487 491
488 match parent.kind() { 492 let tag = match parent.kind() {
489 STRUCT_DEF => HighlightTag::Struct.into(), 493 STRUCT_DEF => HighlightTag::Struct,
490 ENUM_DEF => HighlightTag::Enum.into(), 494 ENUM_DEF => HighlightTag::Enum,
491 UNION_DEF => HighlightTag::Union.into(), 495 UNION_DEF => HighlightTag::Union,
492 TRAIT_DEF => HighlightTag::Trait.into(), 496 TRAIT_DEF => HighlightTag::Trait,
493 TYPE_ALIAS_DEF => HighlightTag::TypeAlias.into(), 497 TYPE_ALIAS_DEF => HighlightTag::TypeAlias,
494 TYPE_PARAM => HighlightTag::TypeParam.into(), 498 TYPE_PARAM => HighlightTag::TypeParam,
495 RECORD_FIELD_DEF => HighlightTag::Field.into(), 499 RECORD_FIELD_DEF => HighlightTag::Field,
500 MODULE => HighlightTag::Module,
501 FN_DEF => HighlightTag::Function,
502 CONST_DEF => HighlightTag::Constant,
503 STATIC_DEF => HighlightTag::Static,
504 ENUM_VARIANT => HighlightTag::EnumVariant,
505 BIND_PAT => HighlightTag::Local,
496 _ => default, 506 _ => default,
497 } 507 };
508
509 tag.into()
498} 510}
499 511
500fn highlight_injection( 512fn highlight_injection(
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs
index ff0eeeb52..edfe61f39 100644
--- a/crates/ra_ide/src/syntax_highlighting/html.rs
+++ b/crates/ra_ide/src/syntax_highlighting/html.rs
@@ -76,6 +76,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
76.type_param { color: #DFAF8F; } 76.type_param { color: #DFAF8F; }
77.attribute { color: #94BFF3; } 77.attribute { color: #94BFF3; }
78.numeric_literal { color: #BFEBBF; } 78.numeric_literal { color: #BFEBBF; }
79.bool_literal { color: #BFE6EB; }
79.macro { color: #94BFF3; } 80.macro { color: #94BFF3; }
80.module { color: #AFD8AF; } 81.module { color: #AFD8AF; }
81.variable { color: #DCDCCC; } 82.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..46c718c91 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,
@@ -29,6 +30,7 @@ pub enum HighlightTag {
29 Macro, 30 Macro,
30 Module, 31 Module,
31 NumericLiteral, 32 NumericLiteral,
33 SelfKeyword,
32 SelfType, 34 SelfType,
33 Static, 35 Static,
34 StringLiteral, 36 StringLiteral,
@@ -45,8 +47,10 @@ pub enum HighlightTag {
45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 47#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
46#[repr(u8)] 48#[repr(u8)]
47pub enum HighlightModifier { 49pub enum HighlightModifier {
50 /// Used to differentiate individual elements within attributes.
51 Attribute = 0,
48 /// Used with keywords like `if` and `break`. 52 /// Used with keywords like `if` and `break`.
49 ControlFlow = 0, 53 ControlFlow,
50 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is 54 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is
51 /// not. 55 /// not.
52 Definition, 56 Definition,
@@ -58,6 +62,7 @@ impl HighlightTag {
58 fn as_str(self) -> &'static str { 62 fn as_str(self) -> &'static str {
59 match self { 63 match self {
60 HighlightTag::Attribute => "attribute", 64 HighlightTag::Attribute => "attribute",
65 HighlightTag::BoolLiteral => "bool_literal",
61 HighlightTag::BuiltinType => "builtin_type", 66 HighlightTag::BuiltinType => "builtin_type",
62 HighlightTag::ByteLiteral => "byte_literal", 67 HighlightTag::ByteLiteral => "byte_literal",
63 HighlightTag::CharLiteral => "char_literal", 68 HighlightTag::CharLiteral => "char_literal",
@@ -72,6 +77,7 @@ impl HighlightTag {
72 HighlightTag::Macro => "macro", 77 HighlightTag::Macro => "macro",
73 HighlightTag::Module => "module", 78 HighlightTag::Module => "module",
74 HighlightTag::NumericLiteral => "numeric_literal", 79 HighlightTag::NumericLiteral => "numeric_literal",
80 HighlightTag::SelfKeyword => "self_keyword",
75 HighlightTag::SelfType => "self_type", 81 HighlightTag::SelfType => "self_type",
76 HighlightTag::Static => "static", 82 HighlightTag::Static => "static",
77 HighlightTag::StringLiteral => "string_literal", 83 HighlightTag::StringLiteral => "string_literal",
@@ -95,6 +101,7 @@ impl fmt::Display for HighlightTag {
95 101
96impl HighlightModifier { 102impl HighlightModifier {
97 const ALL: &'static [HighlightModifier] = &[ 103 const ALL: &'static [HighlightModifier] = &[
104 HighlightModifier::Attribute,
98 HighlightModifier::ControlFlow, 105 HighlightModifier::ControlFlow,
99 HighlightModifier::Definition, 106 HighlightModifier::Definition,
100 HighlightModifier::Mutable, 107 HighlightModifier::Mutable,
@@ -103,6 +110,7 @@ impl HighlightModifier {
103 110
104 fn as_str(self) -> &'static str { 111 fn as_str(self) -> &'static str {
105 match self { 112 match self {
113 HighlightModifier::Attribute => "attribute",
106 HighlightModifier::ControlFlow => "control", 114 HighlightModifier::ControlFlow => "control",
107 HighlightModifier::Definition => "declaration", 115 HighlightModifier::Definition => "declaration",
108 HighlightModifier::Mutable => "mutable", 116 HighlightModifier::Mutable => "mutable",
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs
index cd48cad93..39bb3b357 100644
--- a/crates/ra_ide/src/typing.rs
+++ b/crates/ra_ide/src/typing.rs
@@ -17,7 +17,7 @@ mod on_enter;
17 17
18use ra_db::{FilePosition, SourceDatabase}; 18use ra_db::{FilePosition, SourceDatabase};
19use ra_fmt::leading_indent; 19use ra_fmt::leading_indent;
20use ra_ide_db::{source_change::SingleFileChange, RootDatabase}; 20use ra_ide_db::RootDatabase;
21use ra_syntax::{ 21use ra_syntax::{
22 algo::find_node_at_offset, 22 algo::find_node_at_offset,
23 ast::{self, AstToken}, 23 ast::{self, AstToken},
@@ -40,15 +40,11 @@ pub(crate) fn on_char_typed(
40 assert!(TRIGGER_CHARS.contains(char_typed)); 40 assert!(TRIGGER_CHARS.contains(char_typed));
41 let file = &db.parse(position.file_id).tree(); 41 let file = &db.parse(position.file_id).tree();
42 assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); 42 assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed));
43 let single_file_change = on_char_typed_inner(file, position.offset, char_typed)?; 43 let text_edit = on_char_typed_inner(file, position.offset, char_typed)?;
44 Some(single_file_change.into_source_change(position.file_id)) 44 Some(SourceChange::source_file_edit_from(position.file_id, text_edit))
45} 45}
46 46
47fn on_char_typed_inner( 47fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> {
48 file: &SourceFile,
49 offset: TextSize,
50 char_typed: char,
51) -> Option<SingleFileChange> {
52 assert!(TRIGGER_CHARS.contains(char_typed)); 48 assert!(TRIGGER_CHARS.contains(char_typed));
53 match char_typed { 49 match char_typed {
54 '.' => on_dot_typed(file, offset), 50 '.' => on_dot_typed(file, offset),
@@ -61,7 +57,7 @@ fn on_char_typed_inner(
61/// Returns an edit which should be applied after `=` was typed. Primarily, 57/// Returns an edit which should be applied after `=` was typed. Primarily,
62/// this works when adding `let =`. 58/// this works when adding `let =`.
63// FIXME: use a snippet completion instead of this hack here. 59// FIXME: use a snippet completion instead of this hack here.
64fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { 60fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
65 assert_eq!(file.syntax().text().char_at(offset), Some('=')); 61 assert_eq!(file.syntax().text().char_at(offset), Some('='));
66 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; 62 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
67 if let_stmt.semicolon_token().is_some() { 63 if let_stmt.semicolon_token().is_some() {
@@ -79,14 +75,11 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange>
79 return None; 75 return None;
80 } 76 }
81 let offset = let_stmt.syntax().text_range().end(); 77 let offset = let_stmt.syntax().text_range().end();
82 Some(SingleFileChange { 78 Some(TextEdit::insert(offset, ";".to_string()))
83 label: "add semicolon".to_string(),
84 edit: TextEdit::insert(offset, ";".to_string()),
85 })
86} 79}
87 80
88/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. 81/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
89fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { 82fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
90 assert_eq!(file.syntax().text().char_at(offset), Some('.')); 83 assert_eq!(file.syntax().text().char_at(offset), Some('.'));
91 let whitespace = 84 let whitespace =
92 file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; 85 file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?;
@@ -107,14 +100,11 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange>
107 return None; 100 return None;
108 } 101 }
109 102
110 Some(SingleFileChange { 103 Some(TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent))
111 label: "reindent dot".to_string(),
112 edit: TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent),
113 })
114} 104}
115 105
116/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` 106/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
117fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { 107fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
118 let file_text = file.syntax().text(); 108 let file_text = file.syntax().text();
119 assert_eq!(file_text.char_at(offset), Some('>')); 109 assert_eq!(file_text.char_at(offset), Some('>'));
120 let after_arrow = offset + TextSize::of('>'); 110 let after_arrow = offset + TextSize::of('>');
@@ -125,10 +115,7 @@ fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChang
125 return None; 115 return None;
126 } 116 }
127 117
128 Some(SingleFileChange { 118 Some(TextEdit::insert(after_arrow, " ".to_string()))
129 label: "add space after return type".to_string(),
130 edit: TextEdit::insert(after_arrow, " ".to_string()),
131 })
132} 119}
133 120
134#[cfg(test)] 121#[cfg(test)]
@@ -144,7 +131,7 @@ mod tests {
144 edit.apply(&mut before); 131 edit.apply(&mut before);
145 let parse = SourceFile::parse(&before); 132 let parse = SourceFile::parse(&before);
146 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| { 133 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {
147 it.edit.apply(&mut before); 134 it.apply(&mut before);
148 before.to_string() 135 before.to_string()
149 }) 136 })
150 } 137 }
diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs
index 85be14ad3..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,12 +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::source_file_edit( 42 Some(edit)
45 "On enter",
46 SourceFileEdit { edit, file_id: position.file_id },
47 );
48 res.is_snippet = true;
49 Some(res)
50} 43}
51 44
52fn followed_by_comment(comment: &ast::Comment) -> bool { 45fn followed_by_comment(comment: &ast::Comment) -> bool {
@@ -93,9 +86,8 @@ mod tests {
93 let (analysis, file_id) = single_file(&before); 86 let (analysis, file_id) = single_file(&before);
94 let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; 87 let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?;
95 88
96 assert_eq!(result.source_file_edits.len(), 1);
97 let mut actual = before.to_string(); 89 let mut actual = before.to_string();
98 result.source_file_edits[0].edit.apply(&mut actual); 90 result.apply(&mut actual);
99 Some(actual) 91 Some(actual)
100 } 92 }
101 93