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_info.rs8
-rw-r--r--crates/ra_ide/src/completion.rs2
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs256
-rw-r--r--crates/ra_ide/src/completion/complete_qualified_path.rs38
-rw-r--r--crates/ra_ide/src/completion/complete_snippet.rs26
-rw-r--r--crates/ra_ide/src/completion/complete_unqualified_path.rs8
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs2
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs4
-rw-r--r--crates/ra_ide/src/completion/presentation.rs82
-rw-r--r--crates/ra_ide/src/completion/test_utils.rs2
-rw-r--r--crates/ra_ide/src/diagnostics.rs101
-rw-r--r--crates/ra_ide/src/display.rs6
-rw-r--r--crates/ra_ide/src/goto_definition.rs10
-rw-r--r--crates/ra_ide/src/hover.rs34
-rw-r--r--crates/ra_ide/src/join_lines.rs24
-rw-r--r--crates/ra_ide/src/lib.rs51
-rw-r--r--crates/ra_ide/src/marks.rs16
-rw-r--r--crates/ra_ide/src/parent_module.rs8
-rw-r--r--crates/ra_ide/src/references.rs28
-rw-r--r--crates/ra_ide/src/references/rename.rs240
-rw-r--r--crates/ra_ide/src/runnables.rs70
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html16
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs16
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tags.rs6
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs14
-rw-r--r--crates/ra_ide/src/test_utils.rs25
-rw-r--r--crates/ra_ide/src/typing.rs54
-rw-r--r--crates/ra_ide/src/typing/on_enter.rs26
28 files changed, 898 insertions, 275 deletions
diff --git a/crates/ra_ide/src/call_info.rs b/crates/ra_ide/src/call_info.rs
index 780a03c13..aa039e6fc 100644
--- a/crates/ra_ide/src/call_info.rs
+++ b/crates/ra_ide/src/call_info.rs
@@ -5,7 +5,7 @@ use ra_syntax::{
5 ast::{self, ArgListOwner}, 5 ast::{self, ArgListOwner},
6 match_ast, AstNode, SyntaxNode, SyntaxToken, 6 match_ast, AstNode, SyntaxNode, SyntaxToken,
7}; 7};
8use test_utils::tested_by; 8use test_utils::mark;
9 9
10use crate::{CallInfo, FilePosition, FunctionSignature}; 10use crate::{CallInfo, FilePosition, FunctionSignature};
11 11
@@ -84,7 +84,7 @@ fn call_info_for_token(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Op
84 84
85 let arg_list_range = arg_list.syntax().text_range(); 85 let arg_list_range = arg_list.syntax().text_range();
86 if !arg_list_range.contains_inclusive(token.text_range().start()) { 86 if !arg_list_range.contains_inclusive(token.text_range().start()) {
87 tested_by!(call_info_bad_offset); 87 mark::hit!(call_info_bad_offset);
88 return None; 88 return None;
89 } 89 }
90 90
@@ -213,7 +213,7 @@ impl CallInfo {
213 213
214#[cfg(test)] 214#[cfg(test)]
215mod tests { 215mod tests {
216 use test_utils::covers; 216 use test_utils::mark;
217 217
218 use crate::mock_analysis::single_file_with_position; 218 use crate::mock_analysis::single_file_with_position;
219 219
@@ -529,7 +529,7 @@ By default this method stops actor's `Context`."#
529 529
530 #[test] 530 #[test]
531 fn call_info_bad_offset() { 531 fn call_info_bad_offset() {
532 covers!(call_info_bad_offset); 532 mark::check!(call_info_bad_offset);
533 let (analysis, position) = single_file_with_position( 533 let (analysis, position) = single_file_with_position(
534 r#"fn foo(x: u32, y: u32) -> u32 {x + y} 534 r#"fn foo(x: u32, y: u32) -> u32 {x + y}
535 fn bar() { foo <|> (3, ); }"#, 535 fn bar() { foo <|> (3, ); }"#,
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs
index 8bdc43b1a..191300704 100644
--- a/crates/ra_ide/src/completion.rs
+++ b/crates/ra_ide/src/completion.rs
@@ -59,8 +59,8 @@ pub use crate::completion::{
59/// with ordering of completions (currently this is done by the client). 59/// with ordering of completions (currently this is done by the client).
60pub(crate) fn completions( 60pub(crate) fn completions(
61 db: &RootDatabase, 61 db: &RootDatabase,
62 position: FilePosition,
63 config: &CompletionConfig, 62 config: &CompletionConfig,
63 position: FilePosition,
64) -> Option<Completions> { 64) -> Option<Completions> {
65 let ctx = CompletionContext::new(db, position, config)?; 65 let ctx = CompletionContext::new(db, position, config)?;
66 66
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs
index 6a0f0c72e..f2a52a407 100644
--- a/crates/ra_ide/src/completion/complete_postfix.rs
+++ b/crates/ra_ide/src/completion/complete_postfix.rs
@@ -14,6 +14,7 @@ use crate::{
14 }, 14 },
15 CompletionItem, 15 CompletionItem,
16}; 16};
17use ra_assists::utils::TryEnum;
17 18
18pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { 19pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
19 if !ctx.config.enable_postfix_completions { 20 if !ctx.config.enable_postfix_completions {
@@ -37,8 +38,53 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
37 Some(it) => it, 38 Some(it) => it,
38 None => return, 39 None => return,
39 }; 40 };
41 let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty);
42 if let Some(try_enum) = &try_enum {
43 match try_enum {
44 TryEnum::Result => {
45 postfix_snippet(
46 ctx,
47 cap,
48 &dot_receiver,
49 "ifl",
50 "if let Ok {}",
51 &format!("if let Ok($1) = {} {{\n $0\n}}", receiver_text),
52 )
53 .add_to(acc);
40 54
41 if receiver_ty.is_bool() || receiver_ty.is_unknown() { 55 postfix_snippet(
56 ctx,
57 cap,
58 &dot_receiver,
59 "while",
60 "while let Ok {}",
61 &format!("while let Ok($1) = {} {{\n $0\n}}", receiver_text),
62 )
63 .add_to(acc);
64 }
65 TryEnum::Option => {
66 postfix_snippet(
67 ctx,
68 cap,
69 &dot_receiver,
70 "ifl",
71 "if let Some {}",
72 &format!("if let Some($1) = {} {{\n $0\n}}", receiver_text),
73 )
74 .add_to(acc);
75
76 postfix_snippet(
77 ctx,
78 cap,
79 &dot_receiver,
80 "while",
81 "while let Some {}",
82 &format!("while let Some($1) = {} {{\n $0\n}}", receiver_text),
83 )
84 .add_to(acc);
85 }
86 }
87 } else if receiver_ty.is_bool() || receiver_ty.is_unknown() {
42 postfix_snippet( 88 postfix_snippet(
43 ctx, 89 ctx,
44 cap, 90 cap,
@@ -58,7 +104,6 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
58 ) 104 )
59 .add_to(acc); 105 .add_to(acc);
60 } 106 }
61
62 // !&&&42 is a compiler error, ergo process it before considering the references 107 // !&&&42 is a compiler error, ergo process it before considering the references
63 postfix_snippet(ctx, cap, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)) 108 postfix_snippet(ctx, cap, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text))
64 .add_to(acc); 109 .add_to(acc);
@@ -80,16 +125,45 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
80 let dot_receiver = include_references(dot_receiver); 125 let dot_receiver = include_references(dot_receiver);
81 let receiver_text = 126 let receiver_text =
82 get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); 127 get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal);
83 128 match try_enum {
84 postfix_snippet( 129 Some(try_enum) => {
85 ctx, 130 match try_enum {
86 cap, 131 TryEnum::Result => {
87 &dot_receiver, 132 postfix_snippet(
88 "match", 133 ctx,
89 "match expr {}", 134 cap,
90 &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text), 135 &dot_receiver,
91 ) 136 "match",
92 .add_to(acc); 137 "match expr {}",
138 &format!("match {} {{\n Ok(${{1:_}}) => {{$2\\}},\n Err(${{3:_}}) => {{$0\\}},\n}}", receiver_text),
139 )
140 .add_to(acc);
141 }
142 TryEnum::Option => {
143 postfix_snippet(
144 ctx,
145 cap,
146 &dot_receiver,
147 "match",
148 "match expr {}",
149 &format!("match {} {{\n Some(${{1:_}}) => {{$2\\}},\n None => {{$0\\}},\n}}", receiver_text),
150 )
151 .add_to(acc);
152 }
153 }
154 }
155 None => {
156 postfix_snippet(
157 ctx,
158 cap,
159 &dot_receiver,
160 "match",
161 "match expr {}",
162 &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text),
163 )
164 .add_to(acc);
165 }
166 }
93 167
94 postfix_snippet( 168 postfix_snippet(
95 ctx, 169 ctx,
@@ -236,6 +310,164 @@ mod tests {
236 } 310 }
237 311
238 #[test] 312 #[test]
313 fn postfix_completion_works_for_option() {
314 assert_debug_snapshot!(
315 do_postfix_completion(
316 r#"
317 enum Option<T> {
318 Some(T),
319 None,
320 }
321
322 fn main() {
323 let bar = Option::Some(true);
324 bar.<|>
325 }
326 "#,
327 ),
328 @r###"
329 [
330 CompletionItem {
331 label: "box",
332 source_range: 210..210,
333 delete: 206..210,
334 insert: "Box::new(bar)",
335 detail: "Box::new(expr)",
336 },
337 CompletionItem {
338 label: "dbg",
339 source_range: 210..210,
340 delete: 206..210,
341 insert: "dbg!(bar)",
342 detail: "dbg!(expr)",
343 },
344 CompletionItem {
345 label: "ifl",
346 source_range: 210..210,
347 delete: 206..210,
348 insert: "if let Some($1) = bar {\n $0\n}",
349 detail: "if let Some {}",
350 },
351 CompletionItem {
352 label: "match",
353 source_range: 210..210,
354 delete: 206..210,
355 insert: "match bar {\n Some(${1:_}) => {$2\\},\n None => {$0\\},\n}",
356 detail: "match expr {}",
357 },
358 CompletionItem {
359 label: "not",
360 source_range: 210..210,
361 delete: 206..210,
362 insert: "!bar",
363 detail: "!expr",
364 },
365 CompletionItem {
366 label: "ref",
367 source_range: 210..210,
368 delete: 206..210,
369 insert: "&bar",
370 detail: "&expr",
371 },
372 CompletionItem {
373 label: "refm",
374 source_range: 210..210,
375 delete: 206..210,
376 insert: "&mut bar",
377 detail: "&mut expr",
378 },
379 CompletionItem {
380 label: "while",
381 source_range: 210..210,
382 delete: 206..210,
383 insert: "while let Some($1) = bar {\n $0\n}",
384 detail: "while let Some {}",
385 },
386 ]
387 "###
388 );
389 }
390
391 #[test]
392 fn postfix_completion_works_for_result() {
393 assert_debug_snapshot!(
394 do_postfix_completion(
395 r#"
396 enum Result<T, E> {
397 Ok(T),
398 Err(E),
399 }
400
401 fn main() {
402 let bar = Result::Ok(true);
403 bar.<|>
404 }
405 "#,
406 ),
407 @r###"
408 [
409 CompletionItem {
410 label: "box",
411 source_range: 211..211,
412 delete: 207..211,
413 insert: "Box::new(bar)",
414 detail: "Box::new(expr)",
415 },
416 CompletionItem {
417 label: "dbg",
418 source_range: 211..211,
419 delete: 207..211,
420 insert: "dbg!(bar)",
421 detail: "dbg!(expr)",
422 },
423 CompletionItem {
424 label: "ifl",
425 source_range: 211..211,
426 delete: 207..211,
427 insert: "if let Ok($1) = bar {\n $0\n}",
428 detail: "if let Ok {}",
429 },
430 CompletionItem {
431 label: "match",
432 source_range: 211..211,
433 delete: 207..211,
434 insert: "match bar {\n Ok(${1:_}) => {$2\\},\n Err(${3:_}) => {$0\\},\n}",
435 detail: "match expr {}",
436 },
437 CompletionItem {
438 label: "not",
439 source_range: 211..211,
440 delete: 207..211,
441 insert: "!bar",
442 detail: "!expr",
443 },
444 CompletionItem {
445 label: "ref",
446 source_range: 211..211,
447 delete: 207..211,
448 insert: "&bar",
449 detail: "&expr",
450 },
451 CompletionItem {
452 label: "refm",
453 source_range: 211..211,
454 delete: 207..211,
455 insert: "&mut bar",
456 detail: "&mut expr",
457 },
458 CompletionItem {
459 label: "while",
460 source_range: 211..211,
461 delete: 207..211,
462 insert: "while let Ok($1) = bar {\n $0\n}",
463 detail: "while let Ok {}",
464 },
465 ]
466 "###
467 );
468 }
469
470 #[test]
239 fn some_postfix_completions_ignored() { 471 fn some_postfix_completions_ignored() {
240 assert_debug_snapshot!( 472 assert_debug_snapshot!(
241 do_postfix_completion( 473 do_postfix_completion(
diff --git a/crates/ra_ide/src/completion/complete_qualified_path.rs b/crates/ra_ide/src/completion/complete_qualified_path.rs
index 7fcd22525..02ac0166b 100644
--- a/crates/ra_ide/src/completion/complete_qualified_path.rs
+++ b/crates/ra_ide/src/completion/complete_qualified_path.rs
@@ -3,7 +3,7 @@
3use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; 3use hir::{Adt, HasVisibility, PathResolution, ScopeDef};
4use ra_syntax::AstNode; 4use ra_syntax::AstNode;
5use rustc_hash::FxHashSet; 5use rustc_hash::FxHashSet;
6use test_utils::tested_by; 6use test_utils::mark;
7 7
8use crate::completion::{CompletionContext, Completions}; 8use crate::completion::{CompletionContext, Completions};
9 9
@@ -20,7 +20,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
20 let scope = ctx.scope(); 20 let scope = ctx.scope();
21 let context_module = scope.module(); 21 let context_module = scope.module();
22 22
23 let res = match scope.resolve_hir_path(&path) { 23 let res = match scope.resolve_hir_path_qualifier(&path) {
24 Some(res) => res, 24 Some(res) => res,
25 None => return, 25 None => return,
26 }; 26 };
@@ -40,7 +40,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
40 if let Some(name_ref) = ctx.name_ref_syntax.as_ref() { 40 if let Some(name_ref) = ctx.name_ref_syntax.as_ref() {
41 if name_ref.syntax().text() == name.to_string().as_str() { 41 if name_ref.syntax().text() == name.to_string().as_str() {
42 // for `use self::foo<|>`, don't suggest `foo` as a completion 42 // for `use self::foo<|>`, don't suggest `foo` as a completion
43 tested_by!(dont_complete_current_use); 43 mark::hit!(dont_complete_current_use);
44 continue; 44 continue;
45 } 45 }
46 } 46 }
@@ -147,7 +147,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
147 147
148#[cfg(test)] 148#[cfg(test)]
149mod tests { 149mod tests {
150 use test_utils::covers; 150 use test_utils::mark;
151 151
152 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; 152 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
153 use insta::assert_debug_snapshot; 153 use insta::assert_debug_snapshot;
@@ -158,7 +158,7 @@ mod tests {
158 158
159 #[test] 159 #[test]
160 fn dont_complete_current_use() { 160 fn dont_complete_current_use() {
161 covers!(dont_complete_current_use); 161 mark::check!(dont_complete_current_use);
162 let completions = do_completion(r"use self::foo<|>;", CompletionKind::Reference); 162 let completions = do_completion(r"use self::foo<|>;", CompletionKind::Reference);
163 assert!(completions.is_empty()); 163 assert!(completions.is_empty());
164 } 164 }
@@ -226,6 +226,34 @@ mod tests {
226 } 226 }
227 227
228 #[test] 228 #[test]
229 fn completes_mod_with_same_name_as_function() {
230 assert_debug_snapshot!(
231 do_reference_completion(
232 r"
233 use self::my::<|>;
234
235 mod my {
236 pub struct Bar;
237 }
238
239 fn my() {}
240 "
241 ),
242 @r###"
243 [
244 CompletionItem {
245 label: "Bar",
246 source_range: 31..31,
247 delete: 31..31,
248 insert: "Bar",
249 kind: Struct,
250 },
251 ]
252 "###
253 );
254 }
255
256 #[test]
229 fn path_visibility() { 257 fn path_visibility() {
230 assert_debug_snapshot!( 258 assert_debug_snapshot!(
231 do_reference_completion( 259 do_reference_completion(
diff --git a/crates/ra_ide/src/completion/complete_snippet.rs b/crates/ra_ide/src/completion/complete_snippet.rs
index a3f5d1b6a..0568d9ccf 100644
--- a/crates/ra_ide/src/completion/complete_snippet.rs
+++ b/crates/ra_ide/src/completion/complete_snippet.rs
@@ -36,6 +36,24 @@ pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionConte
36 snippet( 36 snippet(
37 ctx, 37 ctx,
38 cap, 38 cap,
39 "Test module",
40 "\
41#[cfg(test)]
42mod tests {
43 use super::*;
44
45 #[test]
46 fn ${1:test_name}() {
47 $0
48 }
49}",
50 )
51 .lookup_by("tmod")
52 .add_to(acc);
53
54 snippet(
55 ctx,
56 cap,
39 "Test function", 57 "Test function",
40 "\ 58 "\
41#[test] 59#[test]
@@ -118,6 +136,14 @@ mod tests {
118 lookup: "tfn", 136 lookup: "tfn",
119 }, 137 },
120 CompletionItem { 138 CompletionItem {
139 label: "Test module",
140 source_range: 78..78,
141 delete: 78..78,
142 insert: "#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn ${1:test_name}() {\n $0\n }\n}",
143 kind: Snippet,
144 lookup: "tmod",
145 },
146 CompletionItem {
121 label: "macro_rules", 147 label: "macro_rules",
122 source_range: 78..78, 148 source_range: 78..78,
123 delete: 78..78, 149 delete: 78..78,
diff --git a/crates/ra_ide/src/completion/complete_unqualified_path.rs b/crates/ra_ide/src/completion/complete_unqualified_path.rs
index bd40af1cb..db791660a 100644
--- a/crates/ra_ide/src/completion/complete_unqualified_path.rs
+++ b/crates/ra_ide/src/completion/complete_unqualified_path.rs
@@ -1,7 +1,7 @@
1//! Completion of names from the current scope, e.g. locals and imported items. 1//! Completion of names from the current scope, e.g. locals and imported items.
2 2
3use hir::ScopeDef; 3use hir::ScopeDef;
4use test_utils::tested_by; 4use test_utils::mark;
5 5
6use crate::completion::{CompletionContext, Completions}; 6use crate::completion::{CompletionContext, Completions};
7use hir::{Adt, ModuleDef, Type}; 7use hir::{Adt, ModuleDef, Type};
@@ -30,7 +30,7 @@ pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
30 if ctx.use_item_syntax.is_some() { 30 if ctx.use_item_syntax.is_some() {
31 if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) { 31 if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) {
32 if name_ref.syntax().text() == name.to_string().as_str() { 32 if name_ref.syntax().text() == name.to_string().as_str() {
33 tested_by!(self_fulfilling_completion); 33 mark::hit!(self_fulfilling_completion);
34 return; 34 return;
35 } 35 }
36 } 36 }
@@ -66,7 +66,7 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T
66#[cfg(test)] 66#[cfg(test)]
67mod tests { 67mod tests {
68 use insta::assert_debug_snapshot; 68 use insta::assert_debug_snapshot;
69 use test_utils::covers; 69 use test_utils::mark;
70 70
71 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; 71 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
72 72
@@ -76,7 +76,7 @@ mod tests {
76 76
77 #[test] 77 #[test]
78 fn self_fulfilling_completion() { 78 fn self_fulfilling_completion() {
79 covers!(self_fulfilling_completion); 79 mark::check!(self_fulfilling_completion);
80 assert_debug_snapshot!( 80 assert_debug_snapshot!(
81 do_reference_completion( 81 do_reference_completion(
82 r#" 82 r#"
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
index b6b9627de..da336973c 100644
--- a/crates/ra_ide/src/completion/completion_context.rs
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -34,7 +34,7 @@ pub(crate) struct CompletionContext<'a> {
34 pub(super) record_pat_syntax: Option<ast::RecordPat>, 34 pub(super) record_pat_syntax: Option<ast::RecordPat>,
35 pub(super) record_field_syntax: Option<ast::RecordField>, 35 pub(super) record_field_syntax: Option<ast::RecordField>,
36 pub(super) impl_def: Option<ast::ImplDef>, 36 pub(super) impl_def: Option<ast::ImplDef>,
37 /// FIXME: `ActiveParameter` is string-based, which is very wrong 37 /// FIXME: `ActiveParameter` is string-based, which is very very wrong
38 pub(super) active_parameter: Option<ActiveParameter>, 38 pub(super) active_parameter: Option<ActiveParameter>,
39 pub(super) is_param: bool, 39 pub(super) is_param: bool,
40 /// If a name-binding or reference to a const in a pattern. 40 /// If a name-binding or reference to a const in a pattern.
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs
index 6021f7279..cfb7c1e38 100644
--- a/crates/ra_ide/src/completion/completion_item.rs
+++ b/crates/ra_ide/src/completion/completion_item.rs
@@ -63,8 +63,8 @@ impl fmt::Debug for CompletionItem {
63 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 63 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64 let mut s = f.debug_struct("CompletionItem"); 64 let mut s = f.debug_struct("CompletionItem");
65 s.field("label", &self.label()).field("source_range", &self.source_range()); 65 s.field("label", &self.label()).field("source_range", &self.source_range());
66 if self.text_edit().as_indels().len() == 1 { 66 if self.text_edit().len() == 1 {
67 let atom = &self.text_edit().as_indels()[0]; 67 let atom = &self.text_edit().iter().next().unwrap();
68 s.field("delete", &atom.delete); 68 s.field("delete", &atom.delete);
69 s.field("insert", &atom.insert); 69 s.field("insert", &atom.insert);
70 } else { 70 } else {
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs
index 2edb130cf..440ffa31d 100644
--- a/crates/ra_ide/src/completion/presentation.rs
+++ b/crates/ra_ide/src/completion/presentation.rs
@@ -3,7 +3,7 @@
3use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; 3use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type};
4use ra_syntax::ast::NameOwner; 4use ra_syntax::ast::NameOwner;
5use stdx::SepBy; 5use stdx::SepBy;
6use test_utils::tested_by; 6use test_utils::mark;
7 7
8use crate::{ 8use crate::{
9 completion::{ 9 completion::{
@@ -17,12 +17,11 @@ use crate::{
17impl Completions { 17impl Completions {
18 pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) { 18 pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) {
19 let is_deprecated = is_deprecated(field, ctx.db); 19 let is_deprecated = is_deprecated(field, ctx.db);
20 let ty = ty.display(ctx.db).to_string();
21 let name = field.name(ctx.db); 20 let name = field.name(ctx.db);
22 let mut completion_item = 21 let mut completion_item =
23 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string()) 22 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string())
24 .kind(CompletionItemKind::Field) 23 .kind(CompletionItemKind::Field)
25 .detail(ty.clone()) 24 .detail(ty.display(ctx.db).to_string())
26 .set_documentation(field.docs(ctx.db)) 25 .set_documentation(field.docs(ctx.db))
27 .set_deprecated(is_deprecated); 26 .set_deprecated(is_deprecated);
28 27
@@ -107,6 +106,12 @@ impl Completions {
107 } 106 }
108 }; 107 };
109 108
109 if let ScopeDef::Local(local) = resolution {
110 if let Some(score) = compute_score(ctx, &local.ty(ctx.db), &local_name) {
111 completion_item = completion_item.set_score(score);
112 }
113 }
114
110 // Add `<>` for generic types 115 // Add `<>` for generic types
111 if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis { 116 if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis {
112 if let Some(cap) = ctx.config.snippet_cap { 117 if let Some(cap) = ctx.config.snippet_cap {
@@ -116,7 +121,7 @@ impl Completions {
116 _ => false, 121 _ => false,
117 }; 122 };
118 if has_non_default_type_params { 123 if has_non_default_type_params {
119 tested_by!(inserts_angle_brackets_for_generics); 124 mark::hit!(inserts_angle_brackets_for_generics);
120 completion_item = completion_item 125 completion_item = completion_item
121 .lookup_by(local_name.clone()) 126 .lookup_by(local_name.clone())
122 .label(format!("{}<…>", local_name)) 127 .label(format!("{}<…>", local_name))
@@ -171,7 +176,7 @@ impl Completions {
171 } 176 }
172 None if needs_bang => builder.insert_text(format!("{}!", name)), 177 None if needs_bang => builder.insert_text(format!("{}!", name)),
173 _ => { 178 _ => {
174 tested_by!(dont_insert_macro_call_parens_unncessary); 179 mark::hit!(dont_insert_macro_call_parens_unncessary);
175 builder.insert_text(name) 180 builder.insert_text(name)
176 } 181 }
177 }; 182 };
@@ -319,19 +324,20 @@ impl Completions {
319 324
320pub(crate) fn compute_score( 325pub(crate) fn compute_score(
321 ctx: &CompletionContext, 326 ctx: &CompletionContext,
322 // FIXME: this definitely should be a `Type` 327 ty: &Type,
323 ty: &str,
324 name: &str, 328 name: &str,
325) -> Option<CompletionScore> { 329) -> Option<CompletionScore> {
330 // FIXME: this should not fall back to string equality.
331 let ty = &ty.display(ctx.db).to_string();
326 let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax { 332 let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax {
327 tested_by!(test_struct_field_completion_in_record_lit); 333 mark::hit!(test_struct_field_completion_in_record_lit);
328 let (struct_field, _local) = ctx.sema.resolve_record_field(record_field)?; 334 let (struct_field, _local) = ctx.sema.resolve_record_field(record_field)?;
329 ( 335 (
330 struct_field.name(ctx.db).to_string(), 336 struct_field.name(ctx.db).to_string(),
331 struct_field.signature_ty(ctx.db).display(ctx.db).to_string(), 337 struct_field.signature_ty(ctx.db).display(ctx.db).to_string(),
332 ) 338 )
333 } else if let Some(active_parameter) = &ctx.active_parameter { 339 } else if let Some(active_parameter) = &ctx.active_parameter {
334 tested_by!(test_struct_field_completion_in_func_call); 340 mark::hit!(test_struct_field_completion_in_func_call);
335 (active_parameter.name.clone(), active_parameter.ty.clone()) 341 (active_parameter.name.clone(), active_parameter.ty.clone())
336 } else { 342 } else {
337 return None; 343 return None;
@@ -392,7 +398,7 @@ impl Builder {
392 None => return self, 398 None => return self,
393 }; 399 };
394 // If not an import, add parenthesis automatically. 400 // If not an import, add parenthesis automatically.
395 tested_by!(inserts_parens_for_function_calls); 401 mark::hit!(inserts_parens_for_function_calls);
396 402
397 let (snippet, label) = if params.is_empty() { 403 let (snippet, label) = if params.is_empty() {
398 (format!("{}()$0", name), format!("{}()", name)) 404 (format!("{}()$0", name), format!("{}()", name))
@@ -451,7 +457,7 @@ fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static s
451#[cfg(test)] 457#[cfg(test)]
452mod tests { 458mod tests {
453 use insta::assert_debug_snapshot; 459 use insta::assert_debug_snapshot;
454 use test_utils::covers; 460 use test_utils::mark;
455 461
456 use crate::completion::{ 462 use crate::completion::{
457 test_utils::{do_completion, do_completion_with_options}, 463 test_utils::{do_completion, do_completion_with_options},
@@ -601,7 +607,7 @@ mod tests {
601 607
602 #[test] 608 #[test]
603 fn inserts_parens_for_function_calls() { 609 fn inserts_parens_for_function_calls() {
604 covers!(inserts_parens_for_function_calls); 610 mark::check!(inserts_parens_for_function_calls);
605 assert_debug_snapshot!( 611 assert_debug_snapshot!(
606 do_reference_completion( 612 do_reference_completion(
607 r" 613 r"
@@ -986,7 +992,7 @@ mod tests {
986 992
987 #[test] 993 #[test]
988 fn inserts_angle_brackets_for_generics() { 994 fn inserts_angle_brackets_for_generics() {
989 covers!(inserts_angle_brackets_for_generics); 995 mark::check!(inserts_angle_brackets_for_generics);
990 assert_debug_snapshot!( 996 assert_debug_snapshot!(
991 do_reference_completion( 997 do_reference_completion(
992 r" 998 r"
@@ -1109,7 +1115,7 @@ mod tests {
1109 1115
1110 #[test] 1116 #[test]
1111 fn dont_insert_macro_call_parens_unncessary() { 1117 fn dont_insert_macro_call_parens_unncessary() {
1112 covers!(dont_insert_macro_call_parens_unncessary); 1118 mark::check!(dont_insert_macro_call_parens_unncessary);
1113 assert_debug_snapshot!( 1119 assert_debug_snapshot!(
1114 do_reference_completion( 1120 do_reference_completion(
1115 r" 1121 r"
@@ -1175,7 +1181,7 @@ mod tests {
1175 1181
1176 #[test] 1182 #[test]
1177 fn test_struct_field_completion_in_func_call() { 1183 fn test_struct_field_completion_in_func_call() {
1178 covers!(test_struct_field_completion_in_func_call); 1184 mark::check!(test_struct_field_completion_in_func_call);
1179 assert_debug_snapshot!( 1185 assert_debug_snapshot!(
1180 do_reference_completion( 1186 do_reference_completion(
1181 r" 1187 r"
@@ -1265,7 +1271,7 @@ mod tests {
1265 1271
1266 #[test] 1272 #[test]
1267 fn test_struct_field_completion_in_record_lit() { 1273 fn test_struct_field_completion_in_record_lit() {
1268 covers!(test_struct_field_completion_in_record_lit); 1274 mark::check!(test_struct_field_completion_in_record_lit);
1269 assert_debug_snapshot!( 1275 assert_debug_snapshot!(
1270 do_reference_completion( 1276 do_reference_completion(
1271 r" 1277 r"
@@ -1405,4 +1411,48 @@ mod tests {
1405 "### 1411 "###
1406 ); 1412 );
1407 } 1413 }
1414
1415 #[test]
1416 fn prioritize_exact_ref_match() {
1417 assert_debug_snapshot!(
1418 do_reference_completion(
1419 r"
1420 struct WorldSnapshot { _f: () };
1421 fn go(world: &WorldSnapshot) {
1422 go(w<|>)
1423 }
1424 ",
1425 ),
1426 @r###"
1427 [
1428 CompletionItem {
1429 label: "WorldSnapshot",
1430 source_range: 132..133,
1431 delete: 132..133,
1432 insert: "WorldSnapshot",
1433 kind: Struct,
1434 },
1435 CompletionItem {
1436 label: "go(…)",
1437 source_range: 132..133,
1438 delete: 132..133,
1439 insert: "go(${1:world})$0",
1440 kind: Function,
1441 lookup: "go",
1442 detail: "fn go(world: &WorldSnapshot)",
1443 trigger_call_info: true,
1444 },
1445 CompletionItem {
1446 label: "world",
1447 source_range: 132..133,
1448 delete: 132..133,
1449 insert: "world",
1450 kind: Binding,
1451 detail: "&WorldSnapshot",
1452 score: TypeAndNameMatch,
1453 },
1454 ]
1455 "###
1456 );
1457 }
1408} 1458}
diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs
index eb90b5279..bf22452a2 100644
--- a/crates/ra_ide/src/completion/test_utils.rs
+++ b/crates/ra_ide/src/completion/test_utils.rs
@@ -20,7 +20,7 @@ pub(crate) fn do_completion_with_options(
20 } else { 20 } else {
21 single_file_with_position(code) 21 single_file_with_position(code)
22 }; 22 };
23 let completions = analysis.completions(position, options).unwrap().unwrap(); 23 let completions = analysis.completions(options, position).unwrap().unwrap();
24 let completion_items: Vec<CompletionItem> = completions.into(); 24 let completion_items: Vec<CompletionItem> = completions.into();
25 let mut kind_completions: Vec<CompletionItem> = 25 let mut kind_completions: Vec<CompletionItem> =
26 completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); 26 completion_items.into_iter().filter(|c| c.completion_kind == kind).collect();
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index 87a0b80f1..3d83c0f71 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 cursor_position: None, 631 ],
632 is_snippet: false,
633 },
632 }, 634 },
633 ), 635 ),
634 severity: Error,
635 }, 636 },
636 ] 637 ]
637 "###); 638 "###);
@@ -665,29 +666,31 @@ mod tests {
665 Diagnostic { 666 Diagnostic {
666 message: "Missing structure fields:\n- b", 667 message: "Missing structure fields:\n- b",
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 cursor_position: None, 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..8bb312156 100644
--- a/crates/ra_ide/src/display.rs
+++ b/crates/ra_ide/src/display.rs
@@ -79,14 +79,14 @@ 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, "{}\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\n{}", doc); 92 format_to!(buf, "\n\n{}", doc);
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs
index 150895abb..90e85d419 100644
--- a/crates/ra_ide/src/goto_definition.rs
+++ b/crates/ra_ide/src/goto_definition.rs
@@ -93,7 +93,7 @@ pub(crate) fn reference_definition(
93 93
94#[cfg(test)] 94#[cfg(test)]
95mod tests { 95mod tests {
96 use test_utils::{assert_eq_text, covers}; 96 use test_utils::assert_eq_text;
97 97
98 use crate::mock_analysis::analysis_and_position; 98 use crate::mock_analysis::analysis_and_position;
99 99
@@ -208,7 +208,6 @@ mod tests {
208 208
209 #[test] 209 #[test]
210 fn goto_def_for_macros() { 210 fn goto_def_for_macros() {
211 covers!(ra_ide_db::goto_def_for_macros);
212 check_goto( 211 check_goto(
213 " 212 "
214 //- /lib.rs 213 //- /lib.rs
@@ -225,7 +224,6 @@ mod tests {
225 224
226 #[test] 225 #[test]
227 fn goto_def_for_macros_from_other_crates() { 226 fn goto_def_for_macros_from_other_crates() {
228 covers!(ra_ide_db::goto_def_for_macros);
229 check_goto( 227 check_goto(
230 " 228 "
231 //- /lib.rs 229 //- /lib.rs
@@ -245,7 +243,6 @@ mod tests {
245 243
246 #[test] 244 #[test]
247 fn goto_def_for_use_alias() { 245 fn goto_def_for_use_alias() {
248 covers!(ra_ide_db::goto_def_for_use_alias);
249 check_goto( 246 check_goto(
250 " 247 "
251 //- /lib.rs 248 //- /lib.rs
@@ -370,7 +367,6 @@ mod tests {
370 367
371 #[test] 368 #[test]
372 fn goto_def_for_methods() { 369 fn goto_def_for_methods() {
373 covers!(ra_ide_db::goto_def_for_methods);
374 check_goto( 370 check_goto(
375 " 371 "
376 //- /lib.rs 372 //- /lib.rs
@@ -390,7 +386,6 @@ mod tests {
390 386
391 #[test] 387 #[test]
392 fn goto_def_for_fields() { 388 fn goto_def_for_fields() {
393 covers!(ra_ide_db::goto_def_for_fields);
394 check_goto( 389 check_goto(
395 r" 390 r"
396 //- /lib.rs 391 //- /lib.rs
@@ -409,7 +404,6 @@ mod tests {
409 404
410 #[test] 405 #[test]
411 fn goto_def_for_record_fields() { 406 fn goto_def_for_record_fields() {
412 covers!(ra_ide_db::goto_def_for_record_fields);
413 check_goto( 407 check_goto(
414 r" 408 r"
415 //- /lib.rs 409 //- /lib.rs
@@ -430,7 +424,6 @@ mod tests {
430 424
431 #[test] 425 #[test]
432 fn goto_def_for_record_pat_fields() { 426 fn goto_def_for_record_pat_fields() {
433 covers!(ra_ide_db::goto_def_for_record_field_pats);
434 check_goto( 427 check_goto(
435 r" 428 r"
436 //- /lib.rs 429 //- /lib.rs
@@ -873,7 +866,6 @@ mod tests {
873 866
874 #[test] 867 #[test]
875 fn goto_def_for_field_init_shorthand() { 868 fn goto_def_for_field_init_shorthand() {
876 covers!(ra_ide_db::goto_def_for_field_init_shorthand);
877 check_goto( 869 check_goto(
878 " 870 "
879 //- /lib.rs 871 //- /lib.rs
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index 06d4f1c63..1f4f6b848 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,6 +503,9 @@ fn main() {
503 "#, 503 "#,
504 &[" 504 &["
505Option 505Option
506___
507
508```rust
506None 509None
507``` 510```
508 511
@@ -524,6 +527,9 @@ The None variant
524 "#, 527 "#,
525 &[" 528 &["
526Option 529Option
530___
531
532```rust
527Some 533Some
528``` 534```
529 535
@@ -606,7 +612,10 @@ fn func(foo: i32) { if true { <|>foo; }; }
606 ", 612 ",
607 ); 613 );
608 let hover = analysis.hover(position).unwrap().unwrap(); 614 let hover = analysis.hover(position).unwrap().unwrap();
609 assert_eq!(trim_markup_opt(hover.info.first()), Some("wrapper::Thing\nfn new() -> Thing")); 615 assert_eq!(
616 trim_markup_opt(hover.info.first()),
617 Some("wrapper::Thing\n___\n\n```rust\nfn new() -> Thing")
618 );
610 } 619 }
611 620
612 #[test] 621 #[test]
@@ -921,4 +930,21 @@ fn func(foo: i32) { if true { <|>foo; }; }
921 &["unsafe trait foo"], 930 &["unsafe trait foo"],
922 ); 931 );
923 } 932 }
933
934 #[test]
935 fn test_hover_mod_with_same_name_as_function() {
936 check_hover_result(
937 "
938 //- /lib.rs
939 use self::m<|>y::Bar;
940
941 mod my {
942 pub struct Bar;
943 }
944
945 fn my() {}
946 ",
947 &["mod my"],
948 );
949 }
924} 950}
diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs
index d3af780c4..af1ade8a1 100644
--- a/crates/ra_ide/src/join_lines.rs
+++ b/crates/ra_ide/src/join_lines.rs
@@ -166,16 +166,28 @@ fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool {
166 166
167#[cfg(test)] 167#[cfg(test)]
168mod tests { 168mod tests {
169 use crate::test_utils::{assert_eq_text, check_action, extract_range}; 169 use ra_syntax::SourceFile;
170 use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range};
170 171
171 use super::*; 172 use super::*;
172 173
173 fn check_join_lines(before: &str, after: &str) { 174 fn check_join_lines(before: &str, after: &str) {
174 check_action(before, after, |file, offset| { 175 let (before_cursor_pos, before) = extract_offset(before);
175 let range = TextRange::empty(offset); 176 let file = SourceFile::parse(&before).ok().unwrap();
176 let res = join_lines(file, range); 177
177 Some(res) 178 let range = TextRange::empty(before_cursor_pos);
178 }) 179 let result = join_lines(&file, range);
180
181 let actual = {
182 let mut actual = before.to_string();
183 result.apply(&mut actual);
184 actual
185 };
186 let actual_cursor_pos = result
187 .apply_to_offset(before_cursor_pos)
188 .expect("cursor position is affected by the edit");
189 let actual = add_cursor(&actual, actual_cursor_pos);
190 assert_eq_text!(after, &actual);
179 } 191 }
180 192
181 #[test] 193 #[test]
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 915199bd8..5ac002d82 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -42,11 +42,6 @@ mod inlay_hints;
42mod expand_macro; 42mod expand_macro;
43mod ssr; 43mod ssr;
44 44
45#[cfg(test)]
46mod marks;
47#[cfg(test)]
48mod test_utils;
49
50use std::sync::Arc; 45use std::sync::Arc;
51 46
52use ra_cfg::CfgOptions; 47use ra_cfg::CfgOptions;
@@ -82,19 +77,19 @@ pub use crate::{
82}; 77};
83 78
84pub use hir::Documentation; 79pub use hir::Documentation;
85pub use ra_assists::AssistId; 80pub use ra_assists::{AssistConfig, AssistId};
86pub use ra_db::{ 81pub use ra_db::{
87 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, 82 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId,
88}; 83};
89pub use ra_ide_db::{ 84pub use ra_ide_db::{
90 change::{AnalysisChange, LibraryData}, 85 change::{AnalysisChange, LibraryData},
91 line_index::{LineCol, LineIndex}, 86 line_index::{LineCol, LineIndex},
92 line_index_utils::translate_offset_with_edit,
93 search::SearchScope, 87 search::SearchScope,
94 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 88 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
95 symbol_index::Query, 89 symbol_index::Query,
96 RootDatabase, 90 RootDatabase,
97}; 91};
92pub use ra_text_edit::{Indel, TextEdit};
98 93
99pub type Cancelable<T> = Result<T, Canceled>; 94pub type Cancelable<T> = Result<T, Canceled>;
100 95
@@ -102,8 +97,22 @@ pub type Cancelable<T> = Result<T, Canceled>;
102pub struct Diagnostic { 97pub struct Diagnostic {
103 pub message: String, 98 pub message: String,
104 pub range: TextRange, 99 pub range: TextRange,
105 pub fix: Option<SourceChange>,
106 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 }
107} 116}
108 117
109/// Info associated with a text range. 118/// Info associated with a text range.
@@ -176,14 +185,10 @@ impl AnalysisHost {
176 pub fn request_cancellation(&mut self) { 185 pub fn request_cancellation(&mut self) {
177 self.db.request_cancellation(); 186 self.db.request_cancellation();
178 } 187 }
179 pub fn raw_database( 188 pub fn raw_database(&self) -> &RootDatabase {
180 &self,
181 ) -> &(impl hir::db::HirDatabase + salsa::Database + ra_db::SourceDatabaseExt) {
182 &self.db 189 &self.db
183 } 190 }
184 pub fn raw_database_mut( 191 pub fn raw_database_mut(&mut self) -> &mut RootDatabase {
185 &mut self,
186 ) -> &mut (impl hir::db::HirDatabase + salsa::Database + ra_db::SourceDatabaseExt) {
187 &mut self.db 192 &mut self.db
188 } 193 }
189} 194}
@@ -295,14 +300,10 @@ impl Analysis {
295 300
296 /// Returns an edit to remove all newlines in the range, cleaning up minor 301 /// Returns an edit to remove all newlines in the range, cleaning up minor
297 /// stuff like trailing commas. 302 /// stuff like trailing commas.
298 pub fn join_lines(&self, frange: FileRange) -> Cancelable<SourceChange> { 303 pub fn join_lines(&self, frange: FileRange) -> Cancelable<TextEdit> {
299 self.with_db(|db| { 304 self.with_db(|db| {
300 let parse = db.parse(frange.file_id); 305 let parse = db.parse(frange.file_id);
301 let file_edit = SourceFileEdit { 306 join_lines::join_lines(&parse.tree(), frange.range)
302 file_id: frange.file_id,
303 edit: join_lines::join_lines(&parse.tree(), frange.range),
304 };
305 SourceChange::source_file_edit("Join lines", file_edit)
306 }) 307 })
307 } 308 }
308 309
@@ -462,17 +463,17 @@ impl Analysis {
462 /// Computes completions at the given position. 463 /// Computes completions at the given position.
463 pub fn completions( 464 pub fn completions(
464 &self, 465 &self,
465 position: FilePosition,
466 config: &CompletionConfig, 466 config: &CompletionConfig,
467 position: FilePosition,
467 ) -> Cancelable<Option<Vec<CompletionItem>>> { 468 ) -> Cancelable<Option<Vec<CompletionItem>>> {
468 self.with_db(|db| completion::completions(db, position, config).map(Into::into)) 469 self.with_db(|db| completion::completions(db, config, position).map(Into::into))
469 } 470 }
470 471
471 /// Computes assists (aka code actions aka intentions) for the given 472 /// Computes assists (aka code actions aka intentions) for the given
472 /// position. 473 /// position.
473 pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> { 474 pub fn assists(&self, config: &AssistConfig, frange: FileRange) -> Cancelable<Vec<Assist>> {
474 self.with_db(|db| { 475 self.with_db(|db| {
475 ra_assists::Assist::resolved(db, frange) 476 ra_assists::Assist::resolved(db, config, frange)
476 .into_iter() 477 .into_iter()
477 .map(|assist| Assist { 478 .map(|assist| Assist {
478 id: assist.assist.id, 479 id: assist.assist.id,
@@ -506,7 +507,7 @@ impl Analysis {
506 ) -> Cancelable<Result<SourceChange, SsrError>> { 507 ) -> Cancelable<Result<SourceChange, SsrError>> {
507 self.with_db(|db| { 508 self.with_db(|db| {
508 let edits = ssr::parse_search_replace(query, parse_only, db)?; 509 let edits = ssr::parse_search_replace(query, parse_only, db)?;
509 Ok(SourceChange::source_file_edits("Structural Search Replace", edits)) 510 Ok(SourceChange::source_file_edits(edits))
510 }) 511 })
511 } 512 }
512 513
diff --git a/crates/ra_ide/src/marks.rs b/crates/ra_ide/src/marks.rs
deleted file mode 100644
index 51ca4dde3..000000000
--- a/crates/ra_ide/src/marks.rs
+++ /dev/null
@@ -1,16 +0,0 @@
1//! See test_utils/src/marks.rs
2
3test_utils::marks!(
4 inserts_angle_brackets_for_generics
5 inserts_parens_for_function_calls
6 call_info_bad_offset
7 dont_complete_current_use
8 test_resolve_parent_module_on_module_decl
9 search_filters_by_range
10 dont_insert_macro_call_parens_unncessary
11 self_fulfilling_completion
12 test_struct_field_completion_in_func_call
13 test_struct_field_completion_in_record_lit
14 test_rename_struct_field_for_shorthand
15 test_rename_local_for_field_shorthand
16);
diff --git a/crates/ra_ide/src/parent_module.rs b/crates/ra_ide/src/parent_module.rs
index aaf4460df..a083fb1eb 100644
--- a/crates/ra_ide/src/parent_module.rs
+++ b/crates/ra_ide/src/parent_module.rs
@@ -7,7 +7,7 @@ use ra_syntax::{
7 algo::find_node_at_offset, 7 algo::find_node_at_offset,
8 ast::{self, AstNode}, 8 ast::{self, AstNode},
9}; 9};
10use test_utils::tested_by; 10use test_utils::mark;
11 11
12use crate::NavigationTarget; 12use crate::NavigationTarget;
13 13
@@ -25,7 +25,7 @@ pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<Na
25 .item_list() 25 .item_list()
26 .map_or(false, |it| it.syntax().text_range().contains_inclusive(position.offset)) 26 .map_or(false, |it| it.syntax().text_range().contains_inclusive(position.offset))
27 { 27 {
28 tested_by!(test_resolve_parent_module_on_module_decl); 28 mark::hit!(test_resolve_parent_module_on_module_decl);
29 module = m.syntax().ancestors().skip(1).find_map(ast::Module::cast); 29 module = m.syntax().ancestors().skip(1).find_map(ast::Module::cast);
30 } 30 }
31 } 31 }
@@ -57,7 +57,7 @@ pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> {
57mod tests { 57mod tests {
58 use ra_cfg::CfgOptions; 58 use ra_cfg::CfgOptions;
59 use ra_db::Env; 59 use ra_db::Env;
60 use test_utils::covers; 60 use test_utils::mark;
61 61
62 use crate::{ 62 use crate::{
63 mock_analysis::{analysis_and_position, MockAnalysis}, 63 mock_analysis::{analysis_and_position, MockAnalysis},
@@ -81,7 +81,7 @@ mod tests {
81 81
82 #[test] 82 #[test]
83 fn test_resolve_parent_module_on_module_decl() { 83 fn test_resolve_parent_module_on_module_decl() {
84 covers!(test_resolve_parent_module_on_module_decl); 84 mark::check!(test_resolve_parent_module_on_module_decl);
85 let (analysis, pos) = analysis_and_position( 85 let (analysis, pos) = analysis_and_position(
86 " 86 "
87 //- /lib.rs 87 //- /lib.rs
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs
index 555ccf295..96444bf6a 100644
--- a/crates/ra_ide/src/references.rs
+++ b/crates/ra_ide/src/references.rs
@@ -190,8 +190,6 @@ fn get_struct_def_name_for_struct_literal_search(
190 190
191#[cfg(test)] 191#[cfg(test)]
192mod tests { 192mod tests {
193 use test_utils::covers;
194
195 use crate::{ 193 use crate::{
196 mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis}, 194 mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis},
197 Declaration, Reference, ReferenceSearchResult, SearchScope, 195 Declaration, Reference, ReferenceSearchResult, SearchScope,
@@ -301,7 +299,6 @@ mod tests {
301 299
302 #[test] 300 #[test]
303 fn search_filters_by_range() { 301 fn search_filters_by_range() {
304 covers!(ra_ide_db::search_filters_by_range);
305 let code = r#" 302 let code = r#"
306 fn foo() { 303 fn foo() {
307 let spam<|> = 92; 304 let spam<|> = 92;
@@ -593,6 +590,31 @@ mod tests {
593 check_result(refs, "i BIND_PAT FileId(1) 36..37 Other", &["FileId(1) 51..52 Other Write"]); 590 check_result(refs, "i BIND_PAT FileId(1) 36..37 Other", &["FileId(1) 51..52 Other Write"]);
594 } 591 }
595 592
593 #[test]
594 fn test_find_struct_function_refs_outside_module() {
595 let code = r#"
596 mod foo {
597 pub struct Foo;
598
599 impl Foo {
600 pub fn new<|>() -> Foo {
601 Foo
602 }
603 }
604 }
605
606 fn main() {
607 let _f = foo::Foo::new();
608 }"#;
609
610 let refs = get_all_refs(code);
611 check_result(
612 refs,
613 "new FN_DEF FileId(1) 87..150 94..97 Other",
614 &["FileId(1) 227..230 StructLiteral"],
615 );
616 }
617
596 fn get_all_refs(text: &str) -> ReferenceSearchResult { 618 fn get_all_refs(text: &str) -> ReferenceSearchResult {
597 let (analysis, position) = single_file_with_position(text); 619 let (analysis, position) = single_file_with_position(text);
598 analysis.find_all_refs(position, None).unwrap().unwrap() 620 analysis.find_all_refs(position, None).unwrap().unwrap()
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index 2cbb82c1a..28c6349b1 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -4,14 +4,16 @@ use hir::{ModuleSource, Semantics};
4use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt}; 4use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt};
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{
7 algo::find_node_at_offset, ast, lex_single_valid_syntax_kind, AstNode, SyntaxKind, SyntaxNode, 7 algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind,
8 AstNode, SyntaxKind, SyntaxNode, SyntaxToken,
8}; 9};
9use ra_text_edit::TextEdit; 10use ra_text_edit::TextEdit;
10use test_utils::tested_by; 11use std::convert::TryInto;
12use test_utils::mark;
11 13
12use crate::{ 14use crate::{
13 references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, 15 references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind,
14 SourceChange, SourceFileEdit, TextRange, 16 SourceChange, SourceFileEdit, TextRange, TextSize,
15}; 17};
16 18
17pub(crate) fn rename( 19pub(crate) fn rename(
@@ -21,17 +23,21 @@ pub(crate) fn rename(
21) -> Option<RangeInfo<SourceChange>> { 23) -> Option<RangeInfo<SourceChange>> {
22 match lex_single_valid_syntax_kind(new_name)? { 24 match lex_single_valid_syntax_kind(new_name)? {
23 SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (), 25 SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (),
26 SyntaxKind::SELF_KW => return rename_to_self(db, position),
24 _ => return None, 27 _ => return None,
25 } 28 }
26 29
27 let sema = Semantics::new(db); 30 let sema = Semantics::new(db);
28 let source_file = sema.parse(position.file_id); 31 let source_file = sema.parse(position.file_id);
29 if let Some((ast_name, ast_module)) = 32 let syntax = source_file.syntax();
30 find_name_and_module_at_offset(source_file.syntax(), position) 33 if let Some((ast_name, ast_module)) = find_name_and_module_at_offset(syntax, position) {
31 {
32 let range = ast_name.syntax().text_range(); 34 let range = ast_name.syntax().text_range();
33 rename_mod(&sema, &ast_name, &ast_module, position, new_name) 35 rename_mod(&sema, &ast_name, &ast_module, position, new_name)
34 .map(|info| RangeInfo::new(range, info)) 36 .map(|info| RangeInfo::new(range, info))
37 } else if let Some(self_token) =
38 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
39 {
40 rename_self_to_param(db, position, self_token, new_name)
35 } else { 41 } else {
36 rename_reference(sema.db, position, new_name) 42 rename_reference(sema.db, position, new_name)
37 } 43 }
@@ -51,13 +57,13 @@ fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFil
51 let file_id = reference.file_range.file_id; 57 let file_id = reference.file_range.file_id;
52 let range = match reference.kind { 58 let range = match reference.kind {
53 ReferenceKind::FieldShorthandForField => { 59 ReferenceKind::FieldShorthandForField => {
54 tested_by!(test_rename_struct_field_for_shorthand); 60 mark::hit!(test_rename_struct_field_for_shorthand);
55 replacement_text.push_str(new_name); 61 replacement_text.push_str(new_name);
56 replacement_text.push_str(": "); 62 replacement_text.push_str(": ");
57 TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) 63 TextRange::new(reference.file_range.range.start(), reference.file_range.range.start())
58 } 64 }
59 ReferenceKind::FieldShorthandForLocal => { 65 ReferenceKind::FieldShorthandForLocal => {
60 tested_by!(test_rename_local_for_field_shorthand); 66 mark::hit!(test_rename_local_for_field_shorthand);
61 replacement_text.push_str(": "); 67 replacement_text.push_str(": ");
62 replacement_text.push_str(new_name); 68 replacement_text.push_str(new_name);
63 TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) 69 TextRange::new(reference.file_range.range.end(), reference.file_range.range.end())
@@ -122,7 +128,113 @@ fn rename_mod(
122 source_file_edits.extend(ref_edits); 128 source_file_edits.extend(ref_edits);
123 } 129 }
124 130
125 Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits)) 131 Some(SourceChange::from_edits(source_file_edits, file_system_edits))
132}
133
134fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> {
135 let sema = Semantics::new(db);
136 let source_file = sema.parse(position.file_id);
137 let syn = source_file.syntax();
138
139 let fn_def = find_node_at_offset::<ast::FnDef>(syn, position.offset)?;
140 let params = fn_def.param_list()?;
141 if params.self_param().is_some() {
142 return None; // method already has self param
143 }
144 let first_param = params.params().next()?;
145 let mutable = match first_param.ascribed_type() {
146 Some(ast::TypeRef::ReferenceType(rt)) => rt.mut_token().is_some(),
147 _ => return None, // not renaming other types
148 };
149
150 let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?;
151
152 let param_range = first_param.syntax().text_range();
153 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
154 .into_iter()
155 .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
156
157 if param_ref.is_empty() {
158 return None;
159 }
160
161 let mut edits = usages
162 .into_iter()
163 .map(|reference| source_edit_from_reference(reference, "self"))
164 .collect::<Vec<_>>();
165
166 edits.push(SourceFileEdit {
167 file_id: position.file_id,
168 edit: TextEdit::replace(
169 param_range,
170 String::from(if mutable { "&mut self" } else { "&self" }),
171 ),
172 });
173
174 Some(RangeInfo::new(range, SourceChange::source_file_edits(edits)))
175}
176
177fn text_edit_from_self_param(
178 syn: &SyntaxNode,
179 self_param: &ast::SelfParam,
180 new_name: &str,
181) -> Option<TextEdit> {
182 fn target_type_name(impl_def: &ast::ImplDef) -> Option<String> {
183 if let Some(ast::TypeRef::PathType(p)) = impl_def.target_type() {
184 return Some(p.path()?.segment()?.name_ref()?.text().to_string());
185 }
186 None
187 }
188
189 let impl_def =
190 find_node_at_offset::<ast::ImplDef>(syn, self_param.syntax().text_range().start())?;
191 let type_name = target_type_name(&impl_def)?;
192
193 let mut replacement_text = String::from(new_name);
194 replacement_text.push_str(": ");
195 replacement_text.push_str(self_param.mut_token().map_or("&", |_| "&mut "));
196 replacement_text.push_str(type_name.as_str());
197
198 Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
199}
200
201fn rename_self_to_param(
202 db: &RootDatabase,
203 position: FilePosition,
204 self_token: SyntaxToken,
205 new_name: &str,
206) -> Option<RangeInfo<SourceChange>> {
207 let sema = Semantics::new(db);
208 let source_file = sema.parse(position.file_id);
209 let syn = source_file.syntax();
210
211 let text = db.file_text(position.file_id);
212 let fn_def = find_node_at_offset::<ast::FnDef>(syn, position.offset)?;
213 let search_range = fn_def.syntax().text_range();
214
215 let mut edits: Vec<SourceFileEdit> = vec![];
216
217 for (idx, _) in text.match_indices("self") {
218 let offset: TextSize = idx.try_into().unwrap();
219 if !search_range.contains_inclusive(offset) {
220 continue;
221 }
222 if let Some(ref usage) =
223 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
224 {
225 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
226 text_edit_from_self_param(syn, self_param, new_name)?
227 } else {
228 TextEdit::replace(usage.text_range(), String::from(new_name))
229 };
230 edits.push(SourceFileEdit { file_id: position.file_id, edit });
231 }
232 }
233
234 let range = ast::SelfParam::cast(self_token.parent())
235 .map_or(self_token.text_range(), |p| p.syntax().text_range());
236
237 Some(RangeInfo::new(range, SourceChange::source_file_edits(edits)))
126} 238}
127 239
128fn rename_reference( 240fn rename_reference(
@@ -141,14 +253,14 @@ fn rename_reference(
141 return None; 253 return None;
142 } 254 }
143 255
144 Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edit))) 256 Some(RangeInfo::new(range, SourceChange::source_file_edits(edit)))
145} 257}
146 258
147#[cfg(test)] 259#[cfg(test)]
148mod tests { 260mod tests {
149 use insta::assert_debug_snapshot; 261 use insta::assert_debug_snapshot;
150 use ra_text_edit::TextEditBuilder; 262 use ra_text_edit::TextEditBuilder;
151 use test_utils::{assert_eq_text, covers}; 263 use test_utils::{assert_eq_text, mark};
152 264
153 use crate::{ 265 use crate::{
154 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, 266 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId,
@@ -380,7 +492,7 @@ mod tests {
380 492
381 #[test] 493 #[test]
382 fn test_rename_struct_field_for_shorthand() { 494 fn test_rename_struct_field_for_shorthand() {
383 covers!(test_rename_struct_field_for_shorthand); 495 mark::check!(test_rename_struct_field_for_shorthand);
384 test_rename( 496 test_rename(
385 r#" 497 r#"
386 struct Foo { 498 struct Foo {
@@ -410,7 +522,7 @@ mod tests {
410 522
411 #[test] 523 #[test]
412 fn test_rename_local_for_field_shorthand() { 524 fn test_rename_local_for_field_shorthand() {
413 covers!(test_rename_local_for_field_shorthand); 525 mark::check!(test_rename_local_for_field_shorthand);
414 test_rename( 526 test_rename(
415 r#" 527 r#"
416 struct Foo { 528 struct Foo {
@@ -530,7 +642,6 @@ mod tests {
530 RangeInfo { 642 RangeInfo {
531 range: 4..7, 643 range: 4..7,
532 info: SourceChange { 644 info: SourceChange {
533 label: "Rename",
534 source_file_edits: [ 645 source_file_edits: [
535 SourceFileEdit { 646 SourceFileEdit {
536 file_id: FileId( 647 file_id: FileId(
@@ -557,7 +668,7 @@ mod tests {
557 dst_path: "bar/foo2.rs", 668 dst_path: "bar/foo2.rs",
558 }, 669 },
559 ], 670 ],
560 cursor_position: None, 671 is_snippet: false,
561 }, 672 },
562 }, 673 },
563 ) 674 )
@@ -582,7 +693,6 @@ mod tests {
582 RangeInfo { 693 RangeInfo {
583 range: 4..7, 694 range: 4..7,
584 info: SourceChange { 695 info: SourceChange {
585 label: "Rename",
586 source_file_edits: [ 696 source_file_edits: [
587 SourceFileEdit { 697 SourceFileEdit {
588 file_id: FileId( 698 file_id: FileId(
@@ -609,7 +719,7 @@ mod tests {
609 dst_path: "foo2/mod.rs", 719 dst_path: "foo2/mod.rs",
610 }, 720 },
611 ], 721 ],
612 cursor_position: None, 722 is_snippet: false,
613 }, 723 },
614 }, 724 },
615 ) 725 )
@@ -665,7 +775,6 @@ mod tests {
665 RangeInfo { 775 RangeInfo {
666 range: 8..11, 776 range: 8..11,
667 info: SourceChange { 777 info: SourceChange {
668 label: "Rename",
669 source_file_edits: [ 778 source_file_edits: [
670 SourceFileEdit { 779 SourceFileEdit {
671 file_id: FileId( 780 file_id: FileId(
@@ -705,7 +814,7 @@ mod tests {
705 dst_path: "bar/foo2.rs", 814 dst_path: "bar/foo2.rs",
706 }, 815 },
707 ], 816 ],
708 cursor_position: None, 817 is_snippet: false,
709 }, 818 },
710 }, 819 },
711 ) 820 )
@@ -774,6 +883,95 @@ mod tests {
774 ); 883 );
775 } 884 }
776 885
886 #[test]
887 fn test_parameter_to_self() {
888 test_rename(
889 r#"
890 struct Foo {
891 i: i32,
892 }
893
894 impl Foo {
895 fn f(foo<|>: &mut Foo) -> i32 {
896 foo.i
897 }
898 }
899 "#,
900 "self",
901 r#"
902 struct Foo {
903 i: i32,
904 }
905
906 impl Foo {
907 fn f(&mut self) -> i32 {
908 self.i
909 }
910 }
911 "#,
912 );
913 }
914
915 #[test]
916 fn test_self_to_parameter() {
917 test_rename(
918 r#"
919 struct Foo {
920 i: i32,
921 }
922
923 impl Foo {
924 fn f(&mut <|>self) -> i32 {
925 self.i
926 }
927 }
928 "#,
929 "foo",
930 r#"
931 struct Foo {
932 i: i32,
933 }
934
935 impl Foo {
936 fn f(foo: &mut Foo) -> i32 {
937 foo.i
938 }
939 }
940 "#,
941 );
942 }
943
944 #[test]
945 fn test_self_in_path_to_parameter() {
946 test_rename(
947 r#"
948 struct Foo {
949 i: i32,
950 }
951
952 impl Foo {
953 fn f(&self) -> i32 {
954 let self_var = 1;
955 self<|>.i
956 }
957 }
958 "#,
959 "foo",
960 r#"
961 struct Foo {
962 i: i32,
963 }
964
965 impl Foo {
966 fn f(foo: &Foo) -> i32 {
967 let self_var = 1;
968 foo.i
969 }
970 }
971 "#,
972 );
973 }
974
777 fn test_rename(text: &str, new_name: &str, expected: &str) { 975 fn test_rename(text: &str, new_name: &str, expected: &str) {
778 let (analysis, position) = single_file_with_position(text); 976 let (analysis, position) = single_file_with_position(text);
779 let source_change = analysis.rename(position, new_name).unwrap(); 977 let source_change = analysis.rename(position, new_name).unwrap();
@@ -782,8 +980,8 @@ mod tests {
782 if let Some(change) = source_change { 980 if let Some(change) = source_change {
783 for edit in change.info.source_file_edits { 981 for edit in change.info.source_file_edits {
784 file_id = Some(edit.file_id); 982 file_id = Some(edit.file_id);
785 for indel in edit.edit.as_indels() { 983 for indel in edit.edit.into_iter() {
786 text_edit_builder.replace(indel.delete, indel.insert.clone()); 984 text_edit_builder.replace(indel.delete, indel.insert);
787 } 985 }
788 } 986 }
789 } 987 }
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index fa8a9d92c..131b8f307 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::Semantics; 3use hir::{AsAssocItem, Semantics};
4use itertools::Itertools; 4use itertools::Itertools;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{
@@ -65,14 +65,36 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
65 RunnableKind::Bin 65 RunnableKind::Bin
66 } else { 66 } else {
67 let test_id = if let Some(module) = sema.to_def(&fn_def).map(|def| def.module(sema.db)) { 67 let test_id = if let Some(module) = sema.to_def(&fn_def).map(|def| def.module(sema.db)) {
68 let path = module 68 let def = sema.to_def(&fn_def)?;
69 let impl_trait_name =
70 def.as_assoc_item(sema.db).and_then(|assoc_item| {
71 match assoc_item.container(sema.db) {
72 hir::AssocItemContainer::Trait(trait_item) => {
73 Some(trait_item.name(sema.db).to_string())
74 }
75 hir::AssocItemContainer::ImplDef(impl_def) => impl_def
76 .target_ty(sema.db)
77 .as_adt()
78 .map(|adt| adt.name(sema.db).to_string()),
79 }
80 });
81
82 let path_iter = module
69 .path_to_root(sema.db) 83 .path_to_root(sema.db)
70 .into_iter() 84 .into_iter()
71 .rev() 85 .rev()
72 .filter_map(|it| it.name(sema.db)) 86 .filter_map(|it| it.name(sema.db))
73 .map(|name| name.to_string()) 87 .map(|name| name.to_string());
74 .chain(std::iter::once(name_string)) 88
75 .join("::"); 89 let path = if let Some(impl_trait_name) = impl_trait_name {
90 path_iter
91 .chain(std::iter::once(impl_trait_name))
92 .chain(std::iter::once(name_string))
93 .join("::")
94 } else {
95 path_iter.chain(std::iter::once(name_string)).join("::")
96 };
97
76 TestId::Path(path) 98 TestId::Path(path)
77 } else { 99 } else {
78 TestId::Name(name_string) 100 TestId::Name(name_string)
@@ -238,6 +260,44 @@ mod tests {
238 } 260 }
239 261
240 #[test] 262 #[test]
263 fn test_runnables_doc_test_in_impl() {
264 let (analysis, pos) = analysis_and_position(
265 r#"
266 //- /lib.rs
267 <|> //empty
268 fn main() {}
269
270 struct Data;
271 impl Data {
272 /// ```
273 /// let x = 5;
274 /// ```
275 fn foo() {}
276 }
277 "#,
278 );
279 let runnables = analysis.runnables(pos.file_id).unwrap();
280 assert_debug_snapshot!(&runnables,
281 @r###"
282 [
283 Runnable {
284 range: 1..21,
285 kind: Bin,
286 },
287 Runnable {
288 range: 51..105,
289 kind: DocTest {
290 test_id: Path(
291 "Data::foo",
292 ),
293 },
294 },
295 ]
296 "###
297 );
298 }
299
300 #[test]
241 fn test_runnables_module() { 301 fn test_runnables_module() {
242 let (analysis, pos) = analysis_and_position( 302 let (analysis, pos) = analysis_and_position(
243 r#" 303 r#"
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html
index 4c27aade4..2ceadf2fc 100644
--- a/crates/ra_ide/src/snapshots/highlighting.html
+++ b/crates/ra_ide/src/snapshots/highlighting.html
@@ -27,12 +27,22 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
27.keyword.unsafe { color: #BC8383; font-weight: bold; } 27.keyword.unsafe { color: #BC8383; font-weight: bold; }
28.control { font-style: italic; } 28.control { font-style: italic; }
29</style> 29</style>
30<pre><code><span class="attribute">#[derive(Clone, Debug)]</span> 30<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> { 31<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>, 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">y</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} 34}
35 35
36<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}
39
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">fn</span> <span class="function declaration">bar</span>(&<span class="keyword">self</span>) -&gt; <span class="builtin_type">i32</span> {
42 <span class="keyword">self</span>.<span class="field">x</span>
43 }
44}
45
36<span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable">STATIC_MUT</span>: <span class="builtin_type">i32</span> = <span class="numeric_literal">0</span>; 46<span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable">STATIC_MUT</span>: <span class="builtin_type">i32</span> = <span class="numeric_literal">0</span>;
37 47
38<span class="keyword">fn</span> <span class="function declaration">foo</span>&lt;<span class="lifetime declaration">'a</span>, <span class="type_param declaration">T</span>&gt;() -&gt; <span class="type_param">T</span> { 48<span class="keyword">fn</span> <span class="function declaration">foo</span>&lt;<span class="lifetime declaration">'a</span>, <span class="type_param declaration">T</span>&gt;() -&gt; <span class="type_param">T</span> {
@@ -63,6 +73,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
63 <span class="static mutable">STATIC_MUT</span> = <span class="numeric_literal">1</span>; 73 <span class="static mutable">STATIC_MUT</span> = <span class="numeric_literal">1</span>;
64 } 74 }
65 75
76 <span class="keyword control">for</span> <span class="variable declaration">e</span> <span class="keyword control">in</span> <span class="variable mutable">vec</span> {
77 <span class="comment">// Do nothing</span>
78 }
79
66 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">x</span> = <span class="numeric_literal">42</span>; 80 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">x</span> = <span class="numeric_literal">42</span>;
67 <span class="keyword">let</span> <span class="variable declaration mutable">y</span> = &<span class="keyword">mut</span> <span class="variable mutable">x</span>; 81 <span class="keyword">let</span> <span class="variable declaration mutable">y</span> = &<span class="keyword">mut</span> <span class="variable mutable">x</span>;
68 <span class="keyword">let</span> <span class="variable declaration">z</span> = &<span class="variable mutable">y</span>; 82 <span class="keyword">let</span> <span class="variable declaration">z</span> = &<span class="variable mutable">y</span>;
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index d53a39f57..b55cf748d 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) {
@@ -403,12 +405,13 @@ fn highlight_element(
403 T![break] 405 T![break]
404 | T![continue] 406 | T![continue]
405 | T![else] 407 | T![else]
406 | T![for]
407 | T![if] 408 | T![if]
408 | T![loop] 409 | T![loop]
409 | T![match] 410 | T![match]
410 | T![return] 411 | T![return]
411 | T![while] => h | HighlightModifier::ControlFlow, 412 | T![while]
413 | T![in] => h | HighlightModifier::ControlFlow,
414 T![for] if !is_child_of_impl(element) => h | HighlightModifier::ControlFlow,
412 T![unsafe] => h | HighlightModifier::Unsafe, 415 T![unsafe] => h | HighlightModifier::Unsafe,
413 _ => h, 416 _ => h,
414 } 417 }
@@ -432,6 +435,13 @@ fn highlight_element(
432 } 435 }
433} 436}
434 437
438fn is_child_of_impl(element: SyntaxElement) -> bool {
439 match element.parent() {
440 Some(e) => e.kind() == IMPL_DEF,
441 _ => false,
442 }
443}
444
435fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { 445fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
436 match def { 446 match def {
437 Definition::Macro(_) => HighlightTag::Macro, 447 Definition::Macro(_) => HighlightTag::Macro,
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs
index be1a0f12b..33e6619ec 100644
--- a/crates/ra_ide/src/syntax_highlighting/tags.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tags.rs
@@ -45,8 +45,10 @@ pub enum HighlightTag {
45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
46#[repr(u8)] 46#[repr(u8)]
47pub enum HighlightModifier { 47pub enum HighlightModifier {
48 /// Used to differentiate individual elements within attributes.
49 Attribute = 0,
48 /// Used with keywords like `if` and `break`. 50 /// Used with keywords like `if` and `break`.
49 ControlFlow = 0, 51 ControlFlow,
50 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is 52 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is
51 /// not. 53 /// not.
52 Definition, 54 Definition,
@@ -95,6 +97,7 @@ impl fmt::Display for HighlightTag {
95 97
96impl HighlightModifier { 98impl HighlightModifier {
97 const ALL: &'static [HighlightModifier] = &[ 99 const ALL: &'static [HighlightModifier] = &[
100 HighlightModifier::Attribute,
98 HighlightModifier::ControlFlow, 101 HighlightModifier::ControlFlow,
99 HighlightModifier::Definition, 102 HighlightModifier::Definition,
100 HighlightModifier::Mutable, 103 HighlightModifier::Mutable,
@@ -103,6 +106,7 @@ impl HighlightModifier {
103 106
104 fn as_str(self) -> &'static str { 107 fn as_str(self) -> &'static str {
105 match self { 108 match self {
109 HighlightModifier::Attribute => "attribute",
106 HighlightModifier::ControlFlow => "control", 110 HighlightModifier::ControlFlow => "control",
107 HighlightModifier::Definition => "declaration", 111 HighlightModifier::Definition => "declaration",
108 HighlightModifier::Mutable => "mutable", 112 HighlightModifier::Mutable => "mutable",
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index 13894869c..eb43a23da 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -17,6 +17,16 @@ struct Foo {
17 pub y: i32, 17 pub y: i32,
18} 18}
19 19
20trait Bar {
21 fn bar(&self) -> i32;
22}
23
24impl Bar for Foo {
25 fn bar(&self) -> i32 {
26 self.x
27 }
28}
29
20static mut STATIC_MUT: i32 = 0; 30static mut STATIC_MUT: i32 = 0;
21 31
22fn foo<'a, T>() -> T { 32fn foo<'a, T>() -> T {
@@ -47,6 +57,10 @@ fn main() {
47 STATIC_MUT = 1; 57 STATIC_MUT = 1;
48 } 58 }
49 59
60 for e in vec {
61 // Do nothing
62 }
63
50 let mut x = 42; 64 let mut x = 42;
51 let y = &mut x; 65 let y = &mut x;
52 let z = &y; 66 let z = &y;
diff --git a/crates/ra_ide/src/test_utils.rs b/crates/ra_ide/src/test_utils.rs
deleted file mode 100644
index 48c8fd1f4..000000000
--- a/crates/ra_ide/src/test_utils.rs
+++ /dev/null
@@ -1,25 +0,0 @@
1//! FIXME: write short doc here
2
3use ra_syntax::{SourceFile, TextSize};
4use ra_text_edit::TextEdit;
5
6pub use test_utils::*;
7
8pub fn check_action<F: Fn(&SourceFile, TextSize) -> Option<TextEdit>>(
9 before: &str,
10 after: &str,
11 f: F,
12) {
13 let (before_cursor_pos, before) = extract_offset(before);
14 let file = SourceFile::parse(&before).ok().unwrap();
15 let result = f(&file, before_cursor_pos).expect("code action is not applicable");
16 let actual = {
17 let mut actual = before.to_string();
18 result.apply(&mut actual);
19 actual
20 };
21 let actual_cursor_pos =
22 result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit");
23 let actual = add_cursor(&actual, actual_cursor_pos);
24 assert_eq_text!(after, &actual);
25}
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs
index 6f04f0be4..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,15 +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 cursor_position: None,
86 })
87} 79}
88 80
89/// 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.
90fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { 82fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
91 assert_eq!(file.syntax().text().char_at(offset), Some('.')); 83 assert_eq!(file.syntax().text().char_at(offset), Some('.'));
92 let whitespace = 84 let whitespace =
93 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)?;
@@ -108,15 +100,11 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange>
108 return None; 100 return None;
109 } 101 }
110 102
111 Some(SingleFileChange { 103 Some(TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent))
112 label: "reindent dot".to_string(),
113 edit: TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent),
114 cursor_position: Some(offset + target_indent_len - current_indent_len + TextSize::of('.')),
115 })
116} 104}
117 105
118/// 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() -> { ... }`
119fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { 107fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
120 let file_text = file.syntax().text(); 108 let file_text = file.syntax().text();
121 assert_eq!(file_text.char_at(offset), Some('>')); 109 assert_eq!(file_text.char_at(offset), Some('>'));
122 let after_arrow = offset + TextSize::of('>'); 110 let after_arrow = offset + TextSize::of('>');
@@ -127,11 +115,7 @@ fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChang
127 return None; 115 return None;
128 } 116 }
129 117
130 Some(SingleFileChange { 118 Some(TextEdit::insert(after_arrow, " ".to_string()))
131 label: "add space after return type".to_string(),
132 edit: TextEdit::insert(after_arrow, " ".to_string()),
133 cursor_position: Some(after_arrow),
134 })
135} 119}
136 120
137#[cfg(test)] 121#[cfg(test)]
@@ -140,29 +124,23 @@ mod tests {
140 124
141 use super::*; 125 use super::*;
142 126
143 fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> { 127 fn do_type_char(char_typed: char, before: &str) -> Option<String> {
144 let (offset, before) = extract_offset(before); 128 let (offset, before) = extract_offset(before);
145 let edit = TextEdit::insert(offset, char_typed.to_string()); 129 let edit = TextEdit::insert(offset, char_typed.to_string());
146 let mut before = before.to_string(); 130 let mut before = before.to_string();
147 edit.apply(&mut before); 131 edit.apply(&mut before);
148 let parse = SourceFile::parse(&before); 132 let parse = SourceFile::parse(&before);
149 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| { 133 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {
150 it.edit.apply(&mut before); 134 it.apply(&mut before);
151 (before.to_string(), it) 135 before.to_string()
152 }) 136 })
153 } 137 }
154 138
155 fn type_char(char_typed: char, before: &str, after: &str) { 139 fn type_char(char_typed: char, before: &str, after: &str) {
156 let (actual, file_change) = do_type_char(char_typed, before) 140 let actual = do_type_char(char_typed, before)
157 .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed)); 141 .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed));
158 142
159 if after.contains("<|>") { 143 assert_eq_text!(after, &actual);
160 let (offset, after) = extract_offset(after);
161 assert_eq_text!(&after, &actual);
162 assert_eq!(file_change.cursor_position, Some(offset))
163 } else {
164 assert_eq_text!(after, &actual);
165 }
166 } 144 }
167 145
168 fn type_char_noop(char_typed: char, before: &str) { 146 fn type_char_noop(char_typed: char, before: &str) {
@@ -350,6 +328,6 @@ fn foo() {
350 328
351 #[test] 329 #[test]
352 fn adds_space_after_return_type() { 330 fn adds_space_after_return_type() {
353 type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -><|> { 92 }") 331 type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -> { 92 }")
354 } 332 }
355} 333}
diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs
index 78a40cc94..e7d64b4f6 100644
--- a/crates/ra_ide/src/typing/on_enter.rs
+++ b/crates/ra_ide/src/typing/on_enter.rs
@@ -38,17 +38,12 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Sour
38 } 38 }
39 39
40 let indent = node_indent(&file, comment.syntax())?; 40 let indent = node_indent(&file, comment.syntax())?;
41 let inserted = format!("\n{}{} ", indent, prefix); 41 let inserted = format!("\n{}{} $0", indent, prefix);
42 let cursor_position = position.offset + TextSize::of(&inserted);
43 let edit = TextEdit::insert(position.offset, inserted); 42 let edit = TextEdit::insert(position.offset, inserted);
44 43
45 Some( 44 let mut res = SourceChange::from(SourceFileEdit { edit, file_id: position.file_id });
46 SourceChange::source_file_edit( 45 res.is_snippet = true;
47 "On enter", 46 Some(res)
48 SourceFileEdit { edit, file_id: position.file_id },
49 )
50 .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }),
51 )
52} 47}
53 48
54fn followed_by_comment(comment: &ast::Comment) -> bool { 49fn followed_by_comment(comment: &ast::Comment) -> bool {
@@ -84,7 +79,7 @@ fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option<SmolStr> {
84 79
85#[cfg(test)] 80#[cfg(test)]
86mod tests { 81mod tests {
87 use test_utils::{add_cursor, assert_eq_text, extract_offset}; 82 use test_utils::{assert_eq_text, extract_offset};
88 83
89 use crate::mock_analysis::single_file; 84 use crate::mock_analysis::single_file;
90 85
@@ -98,7 +93,6 @@ mod tests {
98 assert_eq!(result.source_file_edits.len(), 1); 93 assert_eq!(result.source_file_edits.len(), 1);
99 let mut actual = before.to_string(); 94 let mut actual = before.to_string();
100 result.source_file_edits[0].edit.apply(&mut actual); 95 result.source_file_edits[0].edit.apply(&mut actual);
101 let actual = add_cursor(&actual, result.cursor_position.unwrap().offset);
102 Some(actual) 96 Some(actual)
103 } 97 }
104 98
@@ -121,7 +115,7 @@ fn foo() {
121", 115",
122 r" 116 r"
123/// Some docs 117/// Some docs
124/// <|> 118/// $0
125fn foo() { 119fn foo() {
126} 120}
127", 121",
@@ -137,7 +131,7 @@ impl S {
137 r" 131 r"
138impl S { 132impl S {
139 /// Some 133 /// Some
140 /// <|> docs. 134 /// $0 docs.
141 fn foo() {} 135 fn foo() {}
142} 136}
143", 137",
@@ -151,7 +145,7 @@ fn foo() {
151", 145",
152 r" 146 r"
153/// 147///
154/// <|> Some docs 148/// $0 Some docs
155fn foo() { 149fn foo() {
156} 150}
157", 151",
@@ -175,7 +169,7 @@ fn main() {
175 r" 169 r"
176fn main() { 170fn main() {
177 // Fix 171 // Fix
178 // <|> me 172 // $0 me
179 let x = 1 + 1; 173 let x = 1 + 1;
180} 174}
181", 175",
@@ -195,7 +189,7 @@ fn main() {
195 r" 189 r"
196fn main() { 190fn main() {
197 // Fix 191 // Fix
198 // <|> 192 // $0
199 // me 193 // me
200 let x = 1 + 1; 194 let x = 1 + 1;
201} 195}