diff options
Diffstat (limited to 'crates/ra_ide')
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 | }; |
8 | use test_utils::tested_by; | 8 | use test_utils::mark; |
9 | 9 | ||
10 | use crate::{CallInfo, FilePosition, FunctionSignature}; | 10 | use 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)] |
215 | mod tests { | 215 | mod 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). |
60 | pub(crate) fn completions( | 60 | pub(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 | }; |
17 | use ra_assists::utils::TryEnum; | ||
17 | 18 | ||
18 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | 19 | pub(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 @@ | |||
3 | use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; | 3 | use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; |
4 | use ra_syntax::AstNode; | 4 | use ra_syntax::AstNode; |
5 | use rustc_hash::FxHashSet; | 5 | use rustc_hash::FxHashSet; |
6 | use test_utils::tested_by; | 6 | use test_utils::mark; |
7 | 7 | ||
8 | use crate::completion::{CompletionContext, Completions}; | 8 | use 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)] |
149 | mod tests { | 149 | mod 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)] | ||
42 | mod 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 | ||
3 | use hir::ScopeDef; | 3 | use hir::ScopeDef; |
4 | use test_utils::tested_by; | 4 | use test_utils::mark; |
5 | 5 | ||
6 | use crate::completion::{CompletionContext, Completions}; | 6 | use crate::completion::{CompletionContext, Completions}; |
7 | use hir::{Adt, ModuleDef, Type}; | 7 | use 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)] |
67 | mod tests { | 67 | mod 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 @@ | |||
3 | use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; | 3 | use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; |
4 | use ra_syntax::ast::NameOwner; | 4 | use ra_syntax::ast::NameOwner; |
5 | use stdx::SepBy; | 5 | use stdx::SepBy; |
6 | use test_utils::tested_by; | 6 | use test_utils::mark; |
7 | 7 | ||
8 | use crate::{ | 8 | use crate::{ |
9 | completion::{ | 9 | completion::{ |
@@ -17,12 +17,11 @@ use crate::{ | |||
17 | impl Completions { | 17 | impl 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 | ||
320 | pub(crate) fn compute_score( | 325 | pub(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)] |
452 | mod tests { | 458 | mod 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 | }; |
22 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 22 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
23 | 23 | ||
24 | use crate::{Diagnostic, FileId, FileSystemEdit, SourceChange, SourceFileEdit}; | 24 | use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceChange, SourceFileEdit}; |
25 | 25 | ||
26 | #[derive(Debug, Copy, Clone)] | 26 | #[derive(Debug, Copy, Clone)] |
27 | pub enum Severity { | 27 | pub 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)] |
95 | mod tests { | 95 | mod 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 | &[" |
505 | Option | 505 | Option |
506 | ___ | ||
507 | |||
508 | ```rust | ||
506 | None | 509 | None |
507 | ``` | 510 | ``` |
508 | 511 | ||
@@ -524,6 +527,9 @@ The None variant | |||
524 | "#, | 527 | "#, |
525 | &[" | 528 | &[" |
526 | Option | 529 | Option |
530 | ___ | ||
531 | |||
532 | ```rust | ||
527 | Some | 533 | Some |
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)] |
168 | mod tests { | 168 | mod 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; | |||
42 | mod expand_macro; | 42 | mod expand_macro; |
43 | mod ssr; | 43 | mod ssr; |
44 | 44 | ||
45 | #[cfg(test)] | ||
46 | mod marks; | ||
47 | #[cfg(test)] | ||
48 | mod test_utils; | ||
49 | |||
50 | use std::sync::Arc; | 45 | use std::sync::Arc; |
51 | 46 | ||
52 | use ra_cfg::CfgOptions; | 47 | use ra_cfg::CfgOptions; |
@@ -82,19 +77,19 @@ pub use crate::{ | |||
82 | }; | 77 | }; |
83 | 78 | ||
84 | pub use hir::Documentation; | 79 | pub use hir::Documentation; |
85 | pub use ra_assists::AssistId; | 80 | pub use ra_assists::{AssistConfig, AssistId}; |
86 | pub use ra_db::{ | 81 | pub use ra_db::{ |
87 | Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, | 82 | Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, |
88 | }; | 83 | }; |
89 | pub use ra_ide_db::{ | 84 | pub 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 | }; |
92 | pub use ra_text_edit::{Indel, TextEdit}; | ||
98 | 93 | ||
99 | pub type Cancelable<T> = Result<T, Canceled>; | 94 | pub type Cancelable<T> = Result<T, Canceled>; |
100 | 95 | ||
@@ -102,8 +97,22 @@ pub type Cancelable<T> = Result<T, Canceled>; | |||
102 | pub struct Diagnostic { | 97 | pub 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)] | ||
105 | pub struct Fix { | ||
106 | pub label: String, | ||
107 | pub source_change: SourceChange, | ||
108 | } | ||
109 | |||
110 | impl 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 | |||
3 | test_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 | }; |
10 | use test_utils::tested_by; | 10 | use test_utils::mark; |
11 | 11 | ||
12 | use crate::NavigationTarget; | 12 | use 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> { | |||
57 | mod tests { | 57 | mod 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)] |
192 | mod tests { | 192 | mod 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}; | |||
4 | use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt}; | 4 | use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt}; |
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use 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 | }; |
9 | use ra_text_edit::TextEdit; | 10 | use ra_text_edit::TextEdit; |
10 | use test_utils::tested_by; | 11 | use std::convert::TryInto; |
12 | use test_utils::mark; | ||
11 | 13 | ||
12 | use crate::{ | 14 | use 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 | ||
17 | pub(crate) fn rename( | 19 | pub(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 | |||
134 | fn 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 | |||
177 | fn 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 | |||
201 | fn 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 | ||
128 | fn rename_reference( | 240 | fn 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)] |
148 | mod tests { | 260 | mod 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 | ||
3 | use hir::Semantics; | 3 | use hir::{AsAssocItem, Semantics}; |
4 | use itertools::Itertools; | 4 | use itertools::Itertools; |
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use 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>) -> <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>) -> <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><<span class="lifetime declaration">'a</span>, <span class="type_param declaration">T</span>>() -> <span class="type_param">T</span> { | 48 | <span class="keyword">fn</span> <span class="function declaration">foo</span><<span class="lifetime declaration">'a</span>, <span class="type_param declaration">T</span>>() -> <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 | ||
438 | fn is_child_of_impl(element: SyntaxElement) -> bool { | ||
439 | match element.parent() { | ||
440 | Some(e) => e.kind() == IMPL_DEF, | ||
441 | _ => false, | ||
442 | } | ||
443 | } | ||
444 | |||
435 | fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { | 445 | fn 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)] |
47 | pub enum HighlightModifier { | 47 | pub 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 | ||
96 | impl HighlightModifier { | 98 | impl 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 | ||
20 | trait Bar { | ||
21 | fn bar(&self) -> i32; | ||
22 | } | ||
23 | |||
24 | impl Bar for Foo { | ||
25 | fn bar(&self) -> i32 { | ||
26 | self.x | ||
27 | } | ||
28 | } | ||
29 | |||
20 | static mut STATIC_MUT: i32 = 0; | 30 | static mut STATIC_MUT: i32 = 0; |
21 | 31 | ||
22 | fn foo<'a, T>() -> T { | 32 | fn 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 | |||
3 | use ra_syntax::{SourceFile, TextSize}; | ||
4 | use ra_text_edit::TextEdit; | ||
5 | |||
6 | pub use test_utils::*; | ||
7 | |||
8 | pub 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 | ||
18 | use ra_db::{FilePosition, SourceDatabase}; | 18 | use ra_db::{FilePosition, SourceDatabase}; |
19 | use ra_fmt::leading_indent; | 19 | use ra_fmt::leading_indent; |
20 | use ra_ide_db::{source_change::SingleFileChange, RootDatabase}; | 20 | use ra_ide_db::RootDatabase; |
21 | use ra_syntax::{ | 21 | use 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 | ||
47 | fn on_char_typed_inner( | 47 | fn 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. |
64 | fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { | 60 | fn 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. |
90 | fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { | 82 | fn 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() -> { ... }` |
119 | fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { | 107 | fn 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 | ||
54 | fn followed_by_comment(comment: &ast::Comment) -> bool { | 49 | fn 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)] |
86 | mod tests { | 81 | mod 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 |
125 | fn foo() { | 119 | fn foo() { |
126 | } | 120 | } |
127 | ", | 121 | ", |
@@ -137,7 +131,7 @@ impl S { | |||
137 | r" | 131 | r" |
138 | impl S { | 132 | impl 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 |
155 | fn foo() { | 149 | fn foo() { |
156 | } | 150 | } |
157 | ", | 151 | ", |
@@ -175,7 +169,7 @@ fn main() { | |||
175 | r" | 169 | r" |
176 | fn main() { | 170 | fn 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" |
196 | fn main() { | 190 | fn main() { |
197 | // Fix | 191 | // Fix |
198 | // <|> | 192 | // $0 |
199 | // me | 193 | // me |
200 | let x = 1 + 1; | 194 | let x = 1 + 1; |
201 | } | 195 | } |