diff options
Diffstat (limited to 'crates/ra_ide/src')
30 files changed, 1077 insertions, 386 deletions
diff --git a/crates/ra_ide/src/call_info.rs b/crates/ra_ide/src/call_info.rs index 2b35a3803..39d09a07f 100644 --- a/crates/ra_ide/src/call_info.rs +++ b/crates/ra_ide/src/call_info.rs | |||
@@ -544,6 +544,20 @@ fn main() { | |||
544 | } | 544 | } |
545 | 545 | ||
546 | #[test] | 546 | #[test] |
547 | fn generic_struct() { | ||
548 | let info = call_info( | ||
549 | r#" | ||
550 | struct TS<T>(T); | ||
551 | fn main() { | ||
552 | let s = TS(<|>); | ||
553 | }"#, | ||
554 | ); | ||
555 | |||
556 | assert_eq!(info.label(), "struct TS<T>(T) -> TS"); | ||
557 | assert_eq!(info.active_parameter, Some(0)); | ||
558 | } | ||
559 | |||
560 | #[test] | ||
547 | #[should_panic] | 561 | #[should_panic] |
548 | fn cant_call_named_structs() { | 562 | fn cant_call_named_structs() { |
549 | let _ = call_info( | 563 | let _ = call_info( |
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index c378c2c62..cd0757be5 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs | |||
@@ -16,11 +16,11 @@ mod complete_scope; | |||
16 | mod complete_postfix; | 16 | mod complete_postfix; |
17 | mod complete_macro_in_item_position; | 17 | mod complete_macro_in_item_position; |
18 | mod complete_trait_impl; | 18 | mod complete_trait_impl; |
19 | #[cfg(test)] | ||
20 | mod test_utils; | ||
19 | 21 | ||
20 | use ra_ide_db::RootDatabase; | 22 | use ra_ide_db::RootDatabase; |
21 | 23 | ||
22 | #[cfg(test)] | ||
23 | use crate::completion::completion_item::do_completion; | ||
24 | use crate::{ | 24 | use crate::{ |
25 | completion::{ | 25 | completion::{ |
26 | completion_context::CompletionContext, | 26 | completion_context::CompletionContext, |
@@ -33,6 +33,23 @@ pub use crate::completion::completion_item::{ | |||
33 | CompletionItem, CompletionItemKind, InsertTextFormat, | 33 | CompletionItem, CompletionItemKind, InsertTextFormat, |
34 | }; | 34 | }; |
35 | 35 | ||
36 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
37 | pub struct CompletionOptions { | ||
38 | pub enable_postfix_completions: bool, | ||
39 | pub add_call_parenthesis: bool, | ||
40 | pub add_call_argument_snippets: bool, | ||
41 | } | ||
42 | |||
43 | impl Default for CompletionOptions { | ||
44 | fn default() -> Self { | ||
45 | CompletionOptions { | ||
46 | enable_postfix_completions: true, | ||
47 | add_call_parenthesis: true, | ||
48 | add_call_argument_snippets: true, | ||
49 | } | ||
50 | } | ||
51 | } | ||
52 | |||
36 | /// Main entry point for completion. We run completion as a two-phase process. | 53 | /// Main entry point for completion. We run completion as a two-phase process. |
37 | /// | 54 | /// |
38 | /// First, we look at the position and collect a so-called `CompletionContext. | 55 | /// First, we look at the position and collect a so-called `CompletionContext. |
@@ -55,8 +72,12 @@ pub use crate::completion::completion_item::{ | |||
55 | /// `foo` *should* be present among the completion variants. Filtering by | 72 | /// `foo` *should* be present among the completion variants. Filtering by |
56 | /// identifier prefix/fuzzy match should be done higher in the stack, together | 73 | /// identifier prefix/fuzzy match should be done higher in the stack, together |
57 | /// with ordering of completions (currently this is done by the client). | 74 | /// with ordering of completions (currently this is done by the client). |
58 | pub(crate) fn completions(db: &RootDatabase, position: FilePosition) -> Option<Completions> { | 75 | pub(crate) fn completions( |
59 | let ctx = CompletionContext::new(db, position)?; | 76 | db: &RootDatabase, |
77 | position: FilePosition, | ||
78 | options: &CompletionOptions, | ||
79 | ) -> Option<Completions> { | ||
80 | let ctx = CompletionContext::new(db, position, options)?; | ||
60 | 81 | ||
61 | let mut acc = Completions::default(); | 82 | let mut acc = Completions::default(); |
62 | 83 | ||
diff --git a/crates/ra_ide/src/completion/complete_dot.rs b/crates/ra_ide/src/completion/complete_dot.rs index f275305e2..f07611d88 100644 --- a/crates/ra_ide/src/completion/complete_dot.rs +++ b/crates/ra_ide/src/completion/complete_dot.rs | |||
@@ -70,7 +70,7 @@ fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &T | |||
70 | 70 | ||
71 | #[cfg(test)] | 71 | #[cfg(test)] |
72 | mod tests { | 72 | mod tests { |
73 | use crate::completion::{do_completion, CompletionItem, CompletionKind}; | 73 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
74 | use insta::assert_debug_snapshot; | 74 | use insta::assert_debug_snapshot; |
75 | 75 | ||
76 | fn do_ref_completion(code: &str) -> Vec<CompletionItem> { | 76 | fn do_ref_completion(code: &str) -> Vec<CompletionItem> { |
@@ -402,6 +402,38 @@ mod tests { | |||
402 | } | 402 | } |
403 | 403 | ||
404 | #[test] | 404 | #[test] |
405 | fn completes_trait_method_from_other_module() { | ||
406 | assert_debug_snapshot!( | ||
407 | do_ref_completion( | ||
408 | r" | ||
409 | struct A {} | ||
410 | mod m { | ||
411 | pub trait Trait { fn the_method(&self); } | ||
412 | } | ||
413 | use m::Trait; | ||
414 | impl Trait for A {} | ||
415 | fn foo(a: A) { | ||
416 | a.<|> | ||
417 | } | ||
418 | ", | ||
419 | ), | ||
420 | @r###" | ||
421 | [ | ||
422 | CompletionItem { | ||
423 | label: "the_method()", | ||
424 | source_range: [219; 219), | ||
425 | delete: [219; 219), | ||
426 | insert: "the_method()$0", | ||
427 | kind: Method, | ||
428 | lookup: "the_method", | ||
429 | detail: "fn the_method(&self)", | ||
430 | }, | ||
431 | ] | ||
432 | "### | ||
433 | ); | ||
434 | } | ||
435 | |||
436 | #[test] | ||
405 | fn test_no_non_self_method() { | 437 | fn test_no_non_self_method() { |
406 | assert_debug_snapshot!( | 438 | assert_debug_snapshot!( |
407 | do_ref_completion( | 439 | do_ref_completion( |
@@ -718,4 +750,35 @@ mod tests { | |||
718 | "### | 750 | "### |
719 | ); | 751 | ); |
720 | } | 752 | } |
753 | |||
754 | #[test] | ||
755 | fn test_method_completion_3547() { | ||
756 | assert_debug_snapshot!( | ||
757 | do_ref_completion( | ||
758 | r" | ||
759 | struct HashSet<T> {} | ||
760 | impl<T> HashSet<T> { | ||
761 | pub fn the_method(&self) {} | ||
762 | } | ||
763 | fn foo() { | ||
764 | let s: HashSet<_>; | ||
765 | s.<|> | ||
766 | } | ||
767 | ", | ||
768 | ), | ||
769 | @r###" | ||
770 | [ | ||
771 | CompletionItem { | ||
772 | label: "the_method()", | ||
773 | source_range: [201; 201), | ||
774 | delete: [201; 201), | ||
775 | insert: "the_method()$0", | ||
776 | kind: Method, | ||
777 | lookup: "the_method", | ||
778 | detail: "pub fn the_method(&self)", | ||
779 | }, | ||
780 | ] | ||
781 | "### | ||
782 | ); | ||
783 | } | ||
721 | } | 784 | } |
diff --git a/crates/ra_ide/src/completion/complete_fn_param.rs b/crates/ra_ide/src/completion/complete_fn_param.rs index 502458706..9226ac055 100644 --- a/crates/ra_ide/src/completion/complete_fn_param.rs +++ b/crates/ra_ide/src/completion/complete_fn_param.rs | |||
@@ -52,7 +52,7 @@ pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) | |||
52 | 52 | ||
53 | #[cfg(test)] | 53 | #[cfg(test)] |
54 | mod tests { | 54 | mod tests { |
55 | use crate::completion::{do_completion, CompletionItem, CompletionKind}; | 55 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
56 | use insta::assert_debug_snapshot; | 56 | use insta::assert_debug_snapshot; |
57 | 57 | ||
58 | fn do_magic_completion(code: &str) -> Vec<CompletionItem> { | 58 | fn do_magic_completion(code: &str) -> Vec<CompletionItem> { |
diff --git a/crates/ra_ide/src/completion/complete_keyword.rs b/crates/ra_ide/src/completion/complete_keyword.rs index e1c0ffb1f..1e053ea4a 100644 --- a/crates/ra_ide/src/completion/complete_keyword.rs +++ b/crates/ra_ide/src/completion/complete_keyword.rs | |||
@@ -117,7 +117,7 @@ fn complete_return( | |||
117 | 117 | ||
118 | #[cfg(test)] | 118 | #[cfg(test)] |
119 | mod tests { | 119 | mod tests { |
120 | use crate::completion::{do_completion, CompletionItem, CompletionKind}; | 120 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
121 | use insta::assert_debug_snapshot; | 121 | use insta::assert_debug_snapshot; |
122 | 122 | ||
123 | fn do_keyword_completion(code: &str) -> Vec<CompletionItem> { | 123 | fn do_keyword_completion(code: &str) -> Vec<CompletionItem> { |
diff --git a/crates/ra_ide/src/completion/complete_macro_in_item_position.rs b/crates/ra_ide/src/completion/complete_macro_in_item_position.rs index 1866d9e6c..270e96df0 100644 --- a/crates/ra_ide/src/completion/complete_macro_in_item_position.rs +++ b/crates/ra_ide/src/completion/complete_macro_in_item_position.rs | |||
@@ -15,9 +15,10 @@ pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &Compl | |||
15 | 15 | ||
16 | #[cfg(test)] | 16 | #[cfg(test)] |
17 | mod tests { | 17 | mod tests { |
18 | use crate::completion::{do_completion, CompletionItem, CompletionKind}; | ||
19 | use insta::assert_debug_snapshot; | 18 | use insta::assert_debug_snapshot; |
20 | 19 | ||
20 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; | ||
21 | |||
21 | fn do_reference_completion(code: &str) -> Vec<CompletionItem> { | 22 | fn do_reference_completion(code: &str) -> Vec<CompletionItem> { |
22 | do_completion(code, CompletionKind::Reference) | 23 | do_completion(code, CompletionKind::Reference) |
23 | } | 24 | } |
diff --git a/crates/ra_ide/src/completion/complete_path.rs b/crates/ra_ide/src/completion/complete_path.rs index 3c4a70561..3db17f15f 100644 --- a/crates/ra_ide/src/completion/complete_path.rs +++ b/crates/ra_ide/src/completion/complete_path.rs | |||
@@ -103,7 +103,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) { | |||
103 | mod tests { | 103 | mod tests { |
104 | use test_utils::covers; | 104 | use test_utils::covers; |
105 | 105 | ||
106 | use crate::completion::{do_completion, CompletionItem, CompletionKind}; | 106 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
107 | use insta::assert_debug_snapshot; | 107 | use insta::assert_debug_snapshot; |
108 | 108 | ||
109 | fn do_reference_completion(code: &str) -> Vec<CompletionItem> { | 109 | fn do_reference_completion(code: &str) -> Vec<CompletionItem> { |
@@ -967,4 +967,74 @@ mod tests { | |||
967 | ] | 967 | ] |
968 | "###); | 968 | "###); |
969 | } | 969 | } |
970 | |||
971 | #[test] | ||
972 | fn function_mod_share_name() { | ||
973 | assert_debug_snapshot!( | ||
974 | do_reference_completion( | ||
975 | r" | ||
976 | fn foo() { | ||
977 | self::m::<|> | ||
978 | } | ||
979 | |||
980 | mod m { | ||
981 | pub mod z {} | ||
982 | pub fn z() {} | ||
983 | } | ||
984 | ", | ||
985 | ), | ||
986 | @r###" | ||
987 | [ | ||
988 | CompletionItem { | ||
989 | label: "z", | ||
990 | source_range: [57; 57), | ||
991 | delete: [57; 57), | ||
992 | insert: "z", | ||
993 | kind: Module, | ||
994 | }, | ||
995 | CompletionItem { | ||
996 | label: "z()", | ||
997 | source_range: [57; 57), | ||
998 | delete: [57; 57), | ||
999 | insert: "z()$0", | ||
1000 | kind: Function, | ||
1001 | lookup: "z", | ||
1002 | detail: "pub fn z()", | ||
1003 | }, | ||
1004 | ] | ||
1005 | "### | ||
1006 | ); | ||
1007 | } | ||
1008 | |||
1009 | #[test] | ||
1010 | fn completes_hashmap_new() { | ||
1011 | assert_debug_snapshot!( | ||
1012 | do_reference_completion( | ||
1013 | r" | ||
1014 | struct RandomState; | ||
1015 | struct HashMap<K, V, S = RandomState> {} | ||
1016 | |||
1017 | impl<K, V> HashMap<K, V, RandomState> { | ||
1018 | pub fn new() -> HashMap<K, V, RandomState> { } | ||
1019 | } | ||
1020 | fn foo() { | ||
1021 | HashMap::<|> | ||
1022 | } | ||
1023 | " | ||
1024 | ), | ||
1025 | @r###" | ||
1026 | [ | ||
1027 | CompletionItem { | ||
1028 | label: "new()", | ||
1029 | source_range: [292; 292), | ||
1030 | delete: [292; 292), | ||
1031 | insert: "new()$0", | ||
1032 | kind: Function, | ||
1033 | lookup: "new", | ||
1034 | detail: "pub fn new() -> HashMap<K, V, RandomState>", | ||
1035 | }, | ||
1036 | ] | ||
1037 | "### | ||
1038 | ); | ||
1039 | } | ||
970 | } | 1040 | } |
diff --git a/crates/ra_ide/src/completion/complete_pattern.rs b/crates/ra_ide/src/completion/complete_pattern.rs index fa8aeceda..6a1a66ef1 100644 --- a/crates/ra_ide/src/completion/complete_pattern.rs +++ b/crates/ra_ide/src/completion/complete_pattern.rs | |||
@@ -27,7 +27,7 @@ pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { | |||
27 | 27 | ||
28 | #[cfg(test)] | 28 | #[cfg(test)] |
29 | mod tests { | 29 | mod tests { |
30 | use crate::completion::{do_completion, CompletionItem, CompletionKind}; | 30 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
31 | use insta::assert_debug_snapshot; | 31 | use insta::assert_debug_snapshot; |
32 | 32 | ||
33 | fn complete(code: &str) -> Vec<CompletionItem> { | 33 | fn complete(code: &str) -> Vec<CompletionItem> { |
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index 65ecea125..0ba382165 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs | |||
@@ -12,7 +12,7 @@ use crate::{ | |||
12 | }; | 12 | }; |
13 | 13 | ||
14 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | 14 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { |
15 | if !ctx.db.feature_flags.get("completion.enable-postfix") { | 15 | if !ctx.options.enable_postfix_completions { |
16 | return; | 16 | return; |
17 | } | 17 | } |
18 | 18 | ||
@@ -81,7 +81,7 @@ fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: | |||
81 | mod tests { | 81 | mod tests { |
82 | use insta::assert_debug_snapshot; | 82 | use insta::assert_debug_snapshot; |
83 | 83 | ||
84 | use crate::completion::{do_completion, CompletionItem, CompletionKind}; | 84 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
85 | 85 | ||
86 | fn do_postfix_completion(code: &str) -> Vec<CompletionItem> { | 86 | fn do_postfix_completion(code: &str) -> Vec<CompletionItem> { |
87 | do_completion(code, CompletionKind::Postfix) | 87 | do_completion(code, CompletionKind::Postfix) |
diff --git a/crates/ra_ide/src/completion/complete_record_literal.rs b/crates/ra_ide/src/completion/complete_record_literal.rs index be6e4194f..83ed1d52c 100644 --- a/crates/ra_ide/src/completion/complete_record_literal.rs +++ b/crates/ra_ide/src/completion/complete_record_literal.rs | |||
@@ -18,7 +18,7 @@ pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionCon | |||
18 | 18 | ||
19 | #[cfg(test)] | 19 | #[cfg(test)] |
20 | mod tests { | 20 | mod tests { |
21 | use crate::completion::{do_completion, CompletionItem, CompletionKind}; | 21 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
22 | use insta::assert_debug_snapshot; | 22 | use insta::assert_debug_snapshot; |
23 | 23 | ||
24 | fn complete(code: &str) -> Vec<CompletionItem> { | 24 | fn complete(code: &str) -> Vec<CompletionItem> { |
diff --git a/crates/ra_ide/src/completion/complete_record_pattern.rs b/crates/ra_ide/src/completion/complete_record_pattern.rs index 687c57d3e..962376428 100644 --- a/crates/ra_ide/src/completion/complete_record_pattern.rs +++ b/crates/ra_ide/src/completion/complete_record_pattern.rs | |||
@@ -17,7 +17,7 @@ pub(super) fn complete_record_pattern(acc: &mut Completions, ctx: &CompletionCon | |||
17 | 17 | ||
18 | #[cfg(test)] | 18 | #[cfg(test)] |
19 | mod tests { | 19 | mod tests { |
20 | use crate::completion::{do_completion, CompletionItem, CompletionKind}; | 20 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
21 | use insta::assert_debug_snapshot; | 21 | use insta::assert_debug_snapshot; |
22 | 22 | ||
23 | fn complete(code: &str) -> Vec<CompletionItem> { | 23 | fn complete(code: &str) -> Vec<CompletionItem> { |
diff --git a/crates/ra_ide/src/completion/complete_scope.rs b/crates/ra_ide/src/completion/complete_scope.rs index eb3c8cf1b..5ffff5a1c 100644 --- a/crates/ra_ide/src/completion/complete_scope.rs +++ b/crates/ra_ide/src/completion/complete_scope.rs | |||
@@ -14,10 +14,10 @@ pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) { | |||
14 | mod tests { | 14 | mod tests { |
15 | use insta::assert_debug_snapshot; | 15 | use insta::assert_debug_snapshot; |
16 | 16 | ||
17 | use crate::completion::{do_completion, CompletionItem, CompletionKind}; | 17 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
18 | 18 | ||
19 | fn do_reference_completion(code: &str) -> Vec<CompletionItem> { | 19 | fn do_reference_completion(ra_fixture: &str) -> Vec<CompletionItem> { |
20 | do_completion(code, CompletionKind::Reference) | 20 | do_completion(ra_fixture, CompletionKind::Reference) |
21 | } | 21 | } |
22 | 22 | ||
23 | #[test] | 23 | #[test] |
@@ -42,6 +42,7 @@ mod tests { | |||
42 | kind: Function, | 42 | kind: Function, |
43 | lookup: "quux", | 43 | lookup: "quux", |
44 | detail: "fn quux(x: i32)", | 44 | detail: "fn quux(x: i32)", |
45 | trigger_call_info: true, | ||
45 | }, | 46 | }, |
46 | CompletionItem { | 47 | CompletionItem { |
47 | label: "x", | 48 | label: "x", |
@@ -844,6 +845,7 @@ mod tests { | |||
844 | kind: Function, | 845 | kind: Function, |
845 | lookup: "quux", | 846 | lookup: "quux", |
846 | detail: "fn quux(x: i32)", | 847 | detail: "fn quux(x: i32)", |
848 | trigger_call_info: true, | ||
847 | }, | 849 | }, |
848 | CompletionItem { | 850 | CompletionItem { |
849 | label: "x", | 851 | label: "x", |
@@ -865,4 +867,38 @@ mod tests { | |||
865 | "### | 867 | "### |
866 | ); | 868 | ); |
867 | } | 869 | } |
870 | |||
871 | #[test] | ||
872 | fn completes_unresolved_uses() { | ||
873 | assert_debug_snapshot!( | ||
874 | do_reference_completion( | ||
875 | r" | ||
876 | use spam::Quux; | ||
877 | |||
878 | fn main() { | ||
879 | <|> | ||
880 | } | ||
881 | " | ||
882 | ), | ||
883 | @r###" | ||
884 | [ | ||
885 | CompletionItem { | ||
886 | label: "Quux", | ||
887 | source_range: [82; 82), | ||
888 | delete: [82; 82), | ||
889 | insert: "Quux", | ||
890 | }, | ||
891 | CompletionItem { | ||
892 | label: "main()", | ||
893 | source_range: [82; 82), | ||
894 | delete: [82; 82), | ||
895 | insert: "main()$0", | ||
896 | kind: Function, | ||
897 | lookup: "main", | ||
898 | detail: "fn main()", | ||
899 | }, | ||
900 | ] | ||
901 | "### | ||
902 | ); | ||
903 | } | ||
868 | } | 904 | } |
diff --git a/crates/ra_ide/src/completion/complete_snippet.rs b/crates/ra_ide/src/completion/complete_snippet.rs index 731b4fd82..f731e9b9a 100644 --- a/crates/ra_ide/src/completion/complete_snippet.rs +++ b/crates/ra_ide/src/completion/complete_snippet.rs | |||
@@ -42,7 +42,7 @@ fn ${1:feature}() { | |||
42 | 42 | ||
43 | #[cfg(test)] | 43 | #[cfg(test)] |
44 | mod tests { | 44 | mod tests { |
45 | use crate::completion::{do_completion, CompletionItem, CompletionKind}; | 45 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
46 | use insta::assert_debug_snapshot; | 46 | use insta::assert_debug_snapshot; |
47 | 47 | ||
48 | fn do_snippet_completion(code: &str) -> Vec<CompletionItem> { | 48 | fn do_snippet_completion(code: &str) -> Vec<CompletionItem> { |
diff --git a/crates/ra_ide/src/completion/complete_trait_impl.rs b/crates/ra_ide/src/completion/complete_trait_impl.rs index 18a1d2995..7fefa2c7a 100644 --- a/crates/ra_ide/src/completion/complete_trait_impl.rs +++ b/crates/ra_ide/src/completion/complete_trait_impl.rs | |||
@@ -34,7 +34,7 @@ | |||
34 | use hir::{self, Docs, HasSource}; | 34 | use hir::{self, Docs, HasSource}; |
35 | use ra_assists::utils::get_missing_impl_items; | 35 | use ra_assists::utils::get_missing_impl_items; |
36 | use ra_syntax::{ | 36 | use ra_syntax::{ |
37 | ast::{self, edit}, | 37 | ast::{self, edit, ImplDef}, |
38 | AstNode, SyntaxKind, SyntaxNode, TextRange, | 38 | AstNode, SyntaxKind, SyntaxNode, TextRange, |
39 | }; | 39 | }; |
40 | use ra_text_edit::TextEdit; | 40 | use ra_text_edit::TextEdit; |
@@ -47,22 +47,22 @@ use crate::{ | |||
47 | }; | 47 | }; |
48 | 48 | ||
49 | pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { | 49 | pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { |
50 | let trigger = ctx.token.ancestors().find(|p| match p.kind() { | 50 | if let Some((trigger, impl_def)) = completion_match(ctx) { |
51 | SyntaxKind::FN_DEF | ||
52 | | SyntaxKind::TYPE_ALIAS_DEF | ||
53 | | SyntaxKind::CONST_DEF | ||
54 | | SyntaxKind::BLOCK_EXPR => true, | ||
55 | _ => false, | ||
56 | }); | ||
57 | |||
58 | let impl_def = trigger | ||
59 | .as_ref() | ||
60 | .and_then(|node| node.parent()) | ||
61 | .and_then(|node| node.parent()) | ||
62 | .and_then(ast::ImplDef::cast); | ||
63 | |||
64 | if let (Some(trigger), Some(impl_def)) = (trigger, impl_def) { | ||
65 | match trigger.kind() { | 51 | match trigger.kind() { |
52 | SyntaxKind::NAME_REF => { | ||
53 | get_missing_impl_items(&ctx.sema, &impl_def).iter().for_each(|item| match item { | ||
54 | hir::AssocItem::Function(fn_item) => { | ||
55 | add_function_impl(&trigger, acc, ctx, &fn_item) | ||
56 | } | ||
57 | hir::AssocItem::TypeAlias(type_item) => { | ||
58 | add_type_alias_impl(&trigger, acc, ctx, &type_item) | ||
59 | } | ||
60 | hir::AssocItem::Const(const_item) => { | ||
61 | add_const_impl(&trigger, acc, ctx, &const_item) | ||
62 | } | ||
63 | }) | ||
64 | } | ||
65 | |||
66 | SyntaxKind::FN_DEF => { | 66 | SyntaxKind::FN_DEF => { |
67 | for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map( | 67 | for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map( |
68 | |item| match item { | 68 | |item| match item { |
@@ -101,6 +101,21 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext | |||
101 | } | 101 | } |
102 | } | 102 | } |
103 | 103 | ||
104 | fn completion_match(ctx: &CompletionContext) -> Option<(SyntaxNode, ImplDef)> { | ||
105 | let (trigger, impl_def_offset) = ctx.token.ancestors().find_map(|p| match p.kind() { | ||
106 | SyntaxKind::FN_DEF | ||
107 | | SyntaxKind::TYPE_ALIAS_DEF | ||
108 | | SyntaxKind::CONST_DEF | ||
109 | | SyntaxKind::BLOCK_EXPR => Some((p, 2)), | ||
110 | SyntaxKind::NAME_REF => Some((p, 5)), | ||
111 | _ => None, | ||
112 | })?; | ||
113 | let impl_def = (0..impl_def_offset - 1) | ||
114 | .try_fold(trigger.parent()?, |t, _| t.parent()) | ||
115 | .and_then(ast::ImplDef::cast)?; | ||
116 | Some((trigger, impl_def)) | ||
117 | } | ||
118 | |||
104 | fn add_function_impl( | 119 | fn add_function_impl( |
105 | fn_def_node: &SyntaxNode, | 120 | fn_def_node: &SyntaxNode, |
106 | acc: &mut Completions, | 121 | acc: &mut Completions, |
@@ -202,7 +217,7 @@ fn make_const_compl_syntax(const_: &ast::ConstDef) -> String { | |||
202 | 217 | ||
203 | #[cfg(test)] | 218 | #[cfg(test)] |
204 | mod tests { | 219 | mod tests { |
205 | use crate::completion::{do_completion, CompletionItem, CompletionKind}; | 220 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; |
206 | use insta::assert_debug_snapshot; | 221 | use insta::assert_debug_snapshot; |
207 | 222 | ||
208 | fn complete(code: &str) -> Vec<CompletionItem> { | 223 | fn complete(code: &str) -> Vec<CompletionItem> { |
@@ -210,6 +225,103 @@ mod tests { | |||
210 | } | 225 | } |
211 | 226 | ||
212 | #[test] | 227 | #[test] |
228 | fn name_ref_function_type_const() { | ||
229 | let completions = complete( | ||
230 | r" | ||
231 | trait Test { | ||
232 | type TestType; | ||
233 | const TEST_CONST: u16; | ||
234 | fn test(); | ||
235 | } | ||
236 | |||
237 | struct T1; | ||
238 | |||
239 | impl Test for T1 { | ||
240 | t<|> | ||
241 | } | ||
242 | ", | ||
243 | ); | ||
244 | assert_debug_snapshot!(completions, @r###" | ||
245 | [ | ||
246 | CompletionItem { | ||
247 | label: "const TEST_CONST: u16 = ", | ||
248 | source_range: [209; 210), | ||
249 | delete: [209; 210), | ||
250 | insert: "const TEST_CONST: u16 = ", | ||
251 | kind: Const, | ||
252 | lookup: "TEST_CONST", | ||
253 | }, | ||
254 | CompletionItem { | ||
255 | label: "fn test()", | ||
256 | source_range: [209; 210), | ||
257 | delete: [209; 210), | ||
258 | insert: "fn test() {}", | ||
259 | kind: Function, | ||
260 | lookup: "test", | ||
261 | }, | ||
262 | CompletionItem { | ||
263 | label: "type TestType = ", | ||
264 | source_range: [209; 210), | ||
265 | delete: [209; 210), | ||
266 | insert: "type TestType = ", | ||
267 | kind: TypeAlias, | ||
268 | lookup: "TestType", | ||
269 | }, | ||
270 | ] | ||
271 | "###); | ||
272 | } | ||
273 | |||
274 | #[test] | ||
275 | fn no_nested_fn_completions() { | ||
276 | let completions = complete( | ||
277 | r" | ||
278 | trait Test { | ||
279 | fn test(); | ||
280 | fn test2(); | ||
281 | } | ||
282 | |||
283 | struct T1; | ||
284 | |||
285 | impl Test for T1 { | ||
286 | fn test() { | ||
287 | t<|> | ||
288 | } | ||
289 | } | ||
290 | ", | ||
291 | ); | ||
292 | assert_debug_snapshot!(completions, @r###"[]"###); | ||
293 | } | ||
294 | |||
295 | #[test] | ||
296 | fn name_ref_single_function() { | ||
297 | let completions = complete( | ||
298 | r" | ||
299 | trait Test { | ||
300 | fn test(); | ||
301 | } | ||
302 | |||
303 | struct T1; | ||
304 | |||
305 | impl Test for T1 { | ||
306 | t<|> | ||
307 | } | ||
308 | ", | ||
309 | ); | ||
310 | assert_debug_snapshot!(completions, @r###" | ||
311 | [ | ||
312 | CompletionItem { | ||
313 | label: "fn test()", | ||
314 | source_range: [139; 140), | ||
315 | delete: [139; 140), | ||
316 | insert: "fn test() {}", | ||
317 | kind: Function, | ||
318 | lookup: "test", | ||
319 | }, | ||
320 | ] | ||
321 | "###); | ||
322 | } | ||
323 | |||
324 | #[test] | ||
213 | fn single_function() { | 325 | fn single_function() { |
214 | let completions = complete( | 326 | let completions = complete( |
215 | r" | 327 | r" |
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index 40535c09e..3646fb8dc 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
@@ -11,7 +11,7 @@ use ra_syntax::{ | |||
11 | }; | 11 | }; |
12 | use ra_text_edit::AtomTextEdit; | 12 | use ra_text_edit::AtomTextEdit; |
13 | 13 | ||
14 | use crate::FilePosition; | 14 | use crate::{completion::CompletionOptions, FilePosition}; |
15 | 15 | ||
16 | /// `CompletionContext` is created early during completion to figure out, where | 16 | /// `CompletionContext` is created early during completion to figure out, where |
17 | /// exactly is the cursor, syntax-wise. | 17 | /// exactly is the cursor, syntax-wise. |
@@ -19,6 +19,7 @@ use crate::FilePosition; | |||
19 | pub(crate) struct CompletionContext<'a> { | 19 | pub(crate) struct CompletionContext<'a> { |
20 | pub(super) sema: Semantics<'a, RootDatabase>, | 20 | pub(super) sema: Semantics<'a, RootDatabase>, |
21 | pub(super) db: &'a RootDatabase, | 21 | pub(super) db: &'a RootDatabase, |
22 | pub(super) options: &'a CompletionOptions, | ||
22 | pub(super) offset: TextUnit, | 23 | pub(super) offset: TextUnit, |
23 | /// The token before the cursor, in the original file. | 24 | /// The token before the cursor, in the original file. |
24 | pub(super) original_token: SyntaxToken, | 25 | pub(super) original_token: SyntaxToken, |
@@ -57,6 +58,7 @@ impl<'a> CompletionContext<'a> { | |||
57 | pub(super) fn new( | 58 | pub(super) fn new( |
58 | db: &'a RootDatabase, | 59 | db: &'a RootDatabase, |
59 | position: FilePosition, | 60 | position: FilePosition, |
61 | options: &'a CompletionOptions, | ||
60 | ) -> Option<CompletionContext<'a>> { | 62 | ) -> Option<CompletionContext<'a>> { |
61 | let sema = Semantics::new(db); | 63 | let sema = Semantics::new(db); |
62 | 64 | ||
@@ -80,6 +82,7 @@ impl<'a> CompletionContext<'a> { | |||
80 | let mut ctx = CompletionContext { | 82 | let mut ctx = CompletionContext { |
81 | sema, | 83 | sema, |
82 | db, | 84 | db, |
85 | options, | ||
83 | original_token, | 86 | original_token, |
84 | token, | 87 | token, |
85 | offset: position.offset, | 88 | offset: position.offset, |
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs index 19bbb2517..bc0f1aff5 100644 --- a/crates/ra_ide/src/completion/completion_item.rs +++ b/crates/ra_ide/src/completion/completion_item.rs | |||
@@ -13,7 +13,7 @@ pub struct CompletionItem { | |||
13 | /// Used only internally in tests, to check only specific kind of | 13 | /// Used only internally in tests, to check only specific kind of |
14 | /// completion (postfix, keyword, reference, etc). | 14 | /// completion (postfix, keyword, reference, etc). |
15 | #[allow(unused)] | 15 | #[allow(unused)] |
16 | completion_kind: CompletionKind, | 16 | pub(crate) completion_kind: CompletionKind, |
17 | /// Label in the completion pop up which identifies completion. | 17 | /// Label in the completion pop up which identifies completion. |
18 | label: String, | 18 | label: String, |
19 | /// Range of identifier that is being completed. | 19 | /// Range of identifier that is being completed. |
@@ -80,6 +80,9 @@ impl fmt::Debug for CompletionItem { | |||
80 | if self.deprecated { | 80 | if self.deprecated { |
81 | s.field("deprecated", &true); | 81 | s.field("deprecated", &true); |
82 | } | 82 | } |
83 | if self.trigger_call_info { | ||
84 | s.field("trigger_call_info", &true); | ||
85 | } | ||
83 | s.finish() | 86 | s.finish() |
84 | } | 87 | } |
85 | } | 88 | } |
@@ -318,20 +321,3 @@ impl Into<Vec<CompletionItem>> for Completions { | |||
318 | self.buf | 321 | self.buf |
319 | } | 322 | } |
320 | } | 323 | } |
321 | |||
322 | #[cfg(test)] | ||
323 | pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { | ||
324 | use crate::completion::completions; | ||
325 | use crate::mock_analysis::{analysis_and_position, single_file_with_position}; | ||
326 | let (analysis, position) = if code.contains("//-") { | ||
327 | analysis_and_position(code) | ||
328 | } else { | ||
329 | single_file_with_position(code) | ||
330 | }; | ||
331 | let completions = completions(&analysis.db, position).unwrap(); | ||
332 | let completion_items: Vec<CompletionItem> = completions.into(); | ||
333 | let mut kind_completions: Vec<CompletionItem> = | ||
334 | completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); | ||
335 | kind_completions.sort_by_key(|c| c.label.clone()); | ||
336 | kind_completions | ||
337 | } | ||
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs index aada4d025..253848602 100644 --- a/crates/ra_ide/src/completion/presentation.rs +++ b/crates/ra_ide/src/completion/presentation.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | //! This modules takes care of rendering various definitions as completion items. | 1 | //! This modules takes care of rendering various definitions as completion items. |
2 | 2 | ||
3 | use hir::{db::HirDatabase, Docs, HasAttrs, HasSource, HirDisplay, ScopeDef, StructKind, Type}; | 3 | use hir::{Docs, HasAttrs, HasSource, HirDisplay, ScopeDef, StructKind, Type}; |
4 | use join_to_string::join; | 4 | use join_to_string::join; |
5 | use ra_syntax::ast::NameOwner; | 5 | use ra_syntax::ast::NameOwner; |
6 | use test_utils::tested_by; | 6 | use test_utils::tested_by; |
@@ -9,7 +9,10 @@ use crate::completion::{ | |||
9 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, | 9 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, |
10 | }; | 10 | }; |
11 | 11 | ||
12 | use crate::display::{const_label, macro_label, type_label, FunctionSignature}; | 12 | use crate::{ |
13 | display::{const_label, macro_label, type_label, FunctionSignature}, | ||
14 | RootDatabase, | ||
15 | }; | ||
13 | 16 | ||
14 | impl Completions { | 17 | impl Completions { |
15 | pub(crate) fn add_field( | 18 | pub(crate) fn add_field( |
@@ -104,10 +107,7 @@ impl Completions { | |||
104 | }; | 107 | }; |
105 | 108 | ||
106 | // Add `<>` for generic types | 109 | // Add `<>` for generic types |
107 | if ctx.is_path_type | 110 | if ctx.is_path_type && !ctx.has_type_args && ctx.options.add_call_parenthesis { |
108 | && !ctx.has_type_args | ||
109 | && ctx.db.feature_flags.get("completion.insertion.add-call-parenthesis") | ||
110 | { | ||
111 | let has_non_default_type_params = match resolution { | 111 | let has_non_default_type_params = match resolution { |
112 | ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db), | 112 | ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db), |
113 | ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db), | 113 | ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db), |
@@ -212,21 +212,14 @@ impl Completions { | |||
212 | .detail(function_signature.to_string()); | 212 | .detail(function_signature.to_string()); |
213 | 213 | ||
214 | // If not an import, add parenthesis automatically. | 214 | // If not an import, add parenthesis automatically. |
215 | if ctx.use_item_syntax.is_none() | 215 | if ctx.use_item_syntax.is_none() && !ctx.is_call && ctx.options.add_call_parenthesis { |
216 | && !ctx.is_call | ||
217 | && ctx.db.feature_flags.get("completion.insertion.add-call-parenthesis") | ||
218 | { | ||
219 | tested_by!(inserts_parens_for_function_calls); | 216 | tested_by!(inserts_parens_for_function_calls); |
220 | 217 | ||
221 | let (snippet, label) = if params.is_empty() || has_self_param && params.len() == 1 { | 218 | let (snippet, label) = if params.is_empty() || has_self_param && params.len() == 1 { |
222 | (format!("{}()$0", name), format!("{}()", name)) | 219 | (format!("{}()$0", name), format!("{}()", name)) |
223 | } else { | 220 | } else { |
224 | builder = builder.trigger_call_info(); | 221 | builder = builder.trigger_call_info(); |
225 | let snippet = if ctx | 222 | let snippet = if ctx.options.add_call_argument_snippets { |
226 | .db | ||
227 | .feature_flags | ||
228 | .get("completion.insertion.add-argument-snippets") | ||
229 | { | ||
230 | let to_skip = if has_self_param { 1 } else { 0 }; | 223 | let to_skip = if has_self_param { 1 } else { 0 }; |
231 | let function_params_snippet = join( | 224 | let function_params_snippet = join( |
232 | function_signature.parameter_names.iter().skip(to_skip).enumerate().map( | 225 | function_signature.parameter_names.iter().skip(to_skip).enumerate().map( |
@@ -283,8 +276,10 @@ impl Completions { | |||
283 | pub(crate) fn add_enum_variant(&mut self, ctx: &CompletionContext, variant: hir::EnumVariant) { | 276 | pub(crate) fn add_enum_variant(&mut self, ctx: &CompletionContext, variant: hir::EnumVariant) { |
284 | let is_deprecated = is_deprecated(variant, ctx.db); | 277 | let is_deprecated = is_deprecated(variant, ctx.db); |
285 | let name = variant.name(ctx.db); | 278 | let name = variant.name(ctx.db); |
286 | let detail_types = | 279 | let detail_types = variant |
287 | variant.fields(ctx.db).into_iter().map(|field| (field.name(ctx.db), field.ty(ctx.db))); | 280 | .fields(ctx.db) |
281 | .into_iter() | ||
282 | .map(|field| (field.name(ctx.db), field.signature_ty(ctx.db))); | ||
288 | let detail = match variant.kind(ctx.db) { | 283 | let detail = match variant.kind(ctx.db) { |
289 | StructKind::Tuple | StructKind::Unit => { | 284 | StructKind::Tuple | StructKind::Unit => { |
290 | join(detail_types.map(|(_, t)| t.display(ctx.db).to_string())) | 285 | join(detail_types.map(|(_, t)| t.display(ctx.db).to_string())) |
@@ -308,7 +303,7 @@ impl Completions { | |||
308 | } | 303 | } |
309 | } | 304 | } |
310 | 305 | ||
311 | fn is_deprecated(node: impl HasAttrs, db: &impl HirDatabase) -> bool { | 306 | fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool { |
312 | node.attrs(db).by_key("deprecated").exists() | 307 | node.attrs(db).by_key("deprecated").exists() |
313 | } | 308 | } |
314 | 309 | ||
@@ -317,12 +312,22 @@ mod tests { | |||
317 | use insta::assert_debug_snapshot; | 312 | use insta::assert_debug_snapshot; |
318 | use test_utils::covers; | 313 | use test_utils::covers; |
319 | 314 | ||
320 | use crate::completion::{do_completion, CompletionItem, CompletionKind}; | 315 | use crate::completion::{ |
316 | test_utils::{do_completion, do_completion_with_options}, | ||
317 | CompletionItem, CompletionKind, CompletionOptions, | ||
318 | }; | ||
321 | 319 | ||
322 | fn do_reference_completion(ra_fixture: &str) -> Vec<CompletionItem> { | 320 | fn do_reference_completion(ra_fixture: &str) -> Vec<CompletionItem> { |
323 | do_completion(ra_fixture, CompletionKind::Reference) | 321 | do_completion(ra_fixture, CompletionKind::Reference) |
324 | } | 322 | } |
325 | 323 | ||
324 | fn do_reference_completion_with_options( | ||
325 | ra_fixture: &str, | ||
326 | options: CompletionOptions, | ||
327 | ) -> Vec<CompletionItem> { | ||
328 | do_completion_with_options(ra_fixture, CompletionKind::Reference, &options) | ||
329 | } | ||
330 | |||
326 | #[test] | 331 | #[test] |
327 | fn enum_detail_includes_names_for_record() { | 332 | fn enum_detail_includes_names_for_record() { |
328 | assert_debug_snapshot!( | 333 | assert_debug_snapshot!( |
@@ -510,6 +515,7 @@ mod tests { | |||
510 | kind: Function, | 515 | kind: Function, |
511 | lookup: "with_args", | 516 | lookup: "with_args", |
512 | detail: "fn with_args(x: i32, y: String)", | 517 | detail: "fn with_args(x: i32, y: String)", |
518 | trigger_call_info: true, | ||
513 | }, | 519 | }, |
514 | ] | 520 | ] |
515 | "### | 521 | "### |
@@ -543,7 +549,7 @@ mod tests { | |||
543 | } | 549 | } |
544 | 550 | ||
545 | #[test] | 551 | #[test] |
546 | fn parens_for_method_call() { | 552 | fn arg_snippets_for_method_call() { |
547 | assert_debug_snapshot!( | 553 | assert_debug_snapshot!( |
548 | do_reference_completion( | 554 | do_reference_completion( |
549 | r" | 555 | r" |
@@ -566,6 +572,42 @@ mod tests { | |||
566 | kind: Method, | 572 | kind: Method, |
567 | lookup: "foo", | 573 | lookup: "foo", |
568 | detail: "fn foo(&self, x: i32)", | 574 | detail: "fn foo(&self, x: i32)", |
575 | trigger_call_info: true, | ||
576 | }, | ||
577 | ] | ||
578 | "### | ||
579 | ) | ||
580 | } | ||
581 | |||
582 | #[test] | ||
583 | fn no_arg_snippets_for_method_call() { | ||
584 | assert_debug_snapshot!( | ||
585 | do_reference_completion_with_options( | ||
586 | r" | ||
587 | struct S {} | ||
588 | impl S { | ||
589 | fn foo(&self, x: i32) {} | ||
590 | } | ||
591 | fn bar(s: &S) { | ||
592 | s.f<|> | ||
593 | } | ||
594 | ", | ||
595 | CompletionOptions { | ||
596 | add_call_argument_snippets: false, | ||
597 | .. Default::default() | ||
598 | } | ||
599 | ), | ||
600 | @r###" | ||
601 | [ | ||
602 | CompletionItem { | ||
603 | label: "foo(…)", | ||
604 | source_range: [171; 172), | ||
605 | delete: [171; 172), | ||
606 | insert: "foo($0)", | ||
607 | kind: Method, | ||
608 | lookup: "foo", | ||
609 | detail: "fn foo(&self, x: i32)", | ||
610 | trigger_call_info: true, | ||
569 | }, | 611 | }, |
570 | ] | 612 | ] |
571 | "### | 613 | "### |
@@ -684,6 +726,7 @@ mod tests { | |||
684 | kind: Function, | 726 | kind: Function, |
685 | lookup: "foo", | 727 | lookup: "foo", |
686 | detail: "fn foo(xs: Ve)", | 728 | detail: "fn foo(xs: Ve)", |
729 | trigger_call_info: true, | ||
687 | }, | 730 | }, |
688 | ] | 731 | ] |
689 | "### | 732 | "### |
@@ -713,6 +756,7 @@ mod tests { | |||
713 | kind: Function, | 756 | kind: Function, |
714 | lookup: "foo", | 757 | lookup: "foo", |
715 | detail: "fn foo(xs: Ve)", | 758 | detail: "fn foo(xs: Ve)", |
759 | trigger_call_info: true, | ||
716 | }, | 760 | }, |
717 | ] | 761 | ] |
718 | "### | 762 | "### |
@@ -741,6 +785,7 @@ mod tests { | |||
741 | kind: Function, | 785 | kind: Function, |
742 | lookup: "foo", | 786 | lookup: "foo", |
743 | detail: "fn foo(xs: Ve)", | 787 | detail: "fn foo(xs: Ve)", |
788 | trigger_call_info: true, | ||
744 | }, | 789 | }, |
745 | ] | 790 | ] |
746 | "### | 791 | "### |
@@ -769,6 +814,7 @@ mod tests { | |||
769 | kind: Function, | 814 | kind: Function, |
770 | lookup: "foo", | 815 | lookup: "foo", |
771 | detail: "fn foo(xs: Ve<i128>)", | 816 | detail: "fn foo(xs: Ve<i128>)", |
817 | trigger_call_info: true, | ||
772 | }, | 818 | }, |
773 | ] | 819 | ] |
774 | "### | 820 | "### |
diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs new file mode 100644 index 000000000..136857315 --- /dev/null +++ b/crates/ra_ide/src/completion/test_utils.rs | |||
@@ -0,0 +1,29 @@ | |||
1 | //! Runs completion for testing purposes. | ||
2 | |||
3 | use crate::{ | ||
4 | completion::{completion_item::CompletionKind, CompletionOptions}, | ||
5 | mock_analysis::{analysis_and_position, single_file_with_position}, | ||
6 | CompletionItem, | ||
7 | }; | ||
8 | |||
9 | pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { | ||
10 | do_completion_with_options(code, kind, &CompletionOptions::default()) | ||
11 | } | ||
12 | |||
13 | pub(crate) fn do_completion_with_options( | ||
14 | code: &str, | ||
15 | kind: CompletionKind, | ||
16 | options: &CompletionOptions, | ||
17 | ) -> Vec<CompletionItem> { | ||
18 | let (analysis, position) = if code.contains("//-") { | ||
19 | analysis_and_position(code) | ||
20 | } else { | ||
21 | single_file_with_position(code) | ||
22 | }; | ||
23 | let completions = analysis.completions(position, options).unwrap().unwrap(); | ||
24 | let completion_items: Vec<CompletionItem> = completions.into(); | ||
25 | let mut kind_completions: Vec<CompletionItem> = | ||
26 | completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); | ||
27 | kind_completions.sort_by_key(|c| c.label().to_owned()); | ||
28 | kind_completions | ||
29 | } | ||
diff --git a/crates/ra_ide/src/display.rs b/crates/ra_ide/src/display.rs index eaeaaa2b4..c395057a7 100644 --- a/crates/ra_ide/src/display.rs +++ b/crates/ra_ide/src/display.rs | |||
@@ -6,6 +6,8 @@ mod navigation_target; | |||
6 | mod structure; | 6 | mod structure; |
7 | mod short_label; | 7 | mod short_label; |
8 | 8 | ||
9 | use std::fmt::{Display, Write}; | ||
10 | |||
9 | use ra_syntax::{ | 11 | use ra_syntax::{ |
10 | ast::{self, AstNode, AttrsOwner, NameOwner, TypeParamsOwner}, | 12 | ast::{self, AstNode, AttrsOwner, NameOwner, TypeParamsOwner}, |
11 | SyntaxKind::{ATTR, COMMENT}, | 13 | SyntaxKind::{ATTR, COMMENT}, |
@@ -67,24 +69,27 @@ pub(crate) fn macro_label(node: &ast::MacroCall) -> String { | |||
67 | format!("{}macro_rules! {}", vis, name) | 69 | format!("{}macro_rules! {}", vis, name) |
68 | } | 70 | } |
69 | 71 | ||
70 | pub(crate) fn rust_code_markup<CODE: AsRef<str>>(val: CODE) -> String { | 72 | pub(crate) fn rust_code_markup(code: &impl Display) -> String { |
71 | rust_code_markup_with_doc::<_, &str>(val, None, None) | 73 | rust_code_markup_with_doc(code, None, None) |
72 | } | 74 | } |
73 | 75 | ||
74 | pub(crate) fn rust_code_markup_with_doc<CODE, DOC>( | 76 | pub(crate) fn rust_code_markup_with_doc( |
75 | val: CODE, | 77 | code: &impl Display, |
76 | doc: Option<DOC>, | 78 | doc: Option<&str>, |
77 | mod_path: Option<String>, | 79 | mod_path: Option<&str>, |
78 | ) -> String | 80 | ) -> String { |
79 | where | 81 | let mut markup = "```rust\n".to_owned(); |
80 | CODE: AsRef<str>, | 82 | |
81 | DOC: AsRef<str>, | 83 | if let Some(mod_path) = mod_path { |
82 | { | 84 | if !mod_path.is_empty() { |
83 | let mod_path = | 85 | write!(markup, "{}\n", mod_path).unwrap(); |
84 | mod_path.filter(|path| !path.is_empty()).map(|path| path + "\n").unwrap_or_default(); | 86 | } |
87 | } | ||
88 | write!(markup, "{}\n```", code).unwrap(); | ||
89 | |||
85 | if let Some(doc) = doc { | 90 | if let Some(doc) = doc { |
86 | format!("```rust\n{}{}\n```\n\n{}", mod_path, val.as_ref(), doc.as_ref()) | 91 | write!(markup, "\n\n{}", doc).unwrap(); |
87 | } else { | ||
88 | format!("```rust\n{}{}\n```", mod_path, val.as_ref()) | ||
89 | } | 92 | } |
93 | |||
94 | markup | ||
90 | } | 95 | } |
diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs index 2c4c932de..ec1bbd5a0 100644 --- a/crates/ra_ide/src/display/function_signature.rs +++ b/crates/ra_ide/src/display/function_signature.rs | |||
@@ -64,7 +64,7 @@ impl FunctionSignature { | |||
64 | .fields(db) | 64 | .fields(db) |
65 | .into_iter() | 65 | .into_iter() |
66 | .map(|field: hir::StructField| { | 66 | .map(|field: hir::StructField| { |
67 | let ty = field.ty(db); | 67 | let ty = field.signature_ty(db); |
68 | format!("{}", ty.display(db)) | 68 | format!("{}", ty.display(db)) |
69 | }) | 69 | }) |
70 | .collect(); | 70 | .collect(); |
@@ -102,7 +102,7 @@ impl FunctionSignature { | |||
102 | .into_iter() | 102 | .into_iter() |
103 | .map(|field: hir::StructField| { | 103 | .map(|field: hir::StructField| { |
104 | let name = field.name(db); | 104 | let name = field.name(db); |
105 | let ty = field.ty(db); | 105 | let ty = field.signature_ty(db); |
106 | format!("{}: {}", name, ty.display(db)) | 106 | format!("{}: {}", name, ty.display(db)) |
107 | }) | 107 | }) |
108 | .collect(); | 108 | .collect(); |
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index a55a13ffc..a7be92ce3 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs | |||
@@ -788,6 +788,21 @@ mod tests { | |||
788 | } | 788 | } |
789 | 789 | ||
790 | #[test] | 790 | #[test] |
791 | fn goto_def_in_local_macro() { | ||
792 | check_goto( | ||
793 | " | ||
794 | //- /lib.rs | ||
795 | fn bar() { | ||
796 | macro_rules! foo { () => { () } } | ||
797 | <|>foo!(); | ||
798 | } | ||
799 | ", | ||
800 | "foo MACRO_CALL FileId(1) [15; 48) [28; 31)", | ||
801 | "macro_rules! foo { () => { () } }|foo", | ||
802 | ); | ||
803 | } | ||
804 | |||
805 | #[test] | ||
791 | fn goto_def_for_field_init_shorthand() { | 806 | fn goto_def_for_field_init_shorthand() { |
792 | covers!(ra_ide_db::goto_def_for_field_init_shorthand); | 807 | covers!(ra_ide_db::goto_def_for_field_init_shorthand); |
793 | check_goto( | 808 | check_goto( |
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 25e038a55..3bdd61a2e 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -1,4 +1,5 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! Logic for computing info that is displayed when the user hovers over any |
2 | //! source code items (e.g. function call, struct field, variable symbol...) | ||
2 | 3 | ||
3 | use hir::{ | 4 | use hir::{ |
4 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, | 5 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, |
@@ -24,35 +25,20 @@ use itertools::Itertools; | |||
24 | use std::iter::once; | 25 | use std::iter::once; |
25 | 26 | ||
26 | /// Contains the results when hovering over an item | 27 | /// Contains the results when hovering over an item |
27 | #[derive(Debug, Clone)] | 28 | #[derive(Debug, Default)] |
28 | pub struct HoverResult { | 29 | pub struct HoverResult { |
29 | results: Vec<String>, | 30 | results: Vec<String>, |
30 | exact: bool, | ||
31 | } | ||
32 | |||
33 | impl Default for HoverResult { | ||
34 | fn default() -> Self { | ||
35 | HoverResult::new() | ||
36 | } | ||
37 | } | 31 | } |
38 | 32 | ||
39 | impl HoverResult { | 33 | impl HoverResult { |
40 | pub fn new() -> HoverResult { | 34 | pub fn new() -> HoverResult { |
41 | HoverResult { | 35 | Self::default() |
42 | results: Vec::new(), | ||
43 | // We assume exact by default | ||
44 | exact: true, | ||
45 | } | ||
46 | } | 36 | } |
47 | 37 | ||
48 | pub fn extend(&mut self, item: Option<String>) { | 38 | pub fn extend(&mut self, item: Option<String>) { |
49 | self.results.extend(item); | 39 | self.results.extend(item); |
50 | } | 40 | } |
51 | 41 | ||
52 | pub fn is_exact(&self) -> bool { | ||
53 | self.exact | ||
54 | } | ||
55 | |||
56 | pub fn is_empty(&self) -> bool { | 42 | pub fn is_empty(&self) -> bool { |
57 | self.results.is_empty() | 43 | self.results.is_empty() |
58 | } | 44 | } |
@@ -72,20 +58,7 @@ impl HoverResult { | |||
72 | /// Returns the results converted into markup | 58 | /// Returns the results converted into markup |
73 | /// for displaying in a UI | 59 | /// for displaying in a UI |
74 | pub fn to_markup(&self) -> String { | 60 | pub fn to_markup(&self) -> String { |
75 | let mut markup = if !self.exact { | 61 | self.results.join("\n\n---\n") |
76 | let mut msg = String::from("Failed to exactly resolve the symbol. This is probably because rust_analyzer does not yet support traits."); | ||
77 | if !self.results.is_empty() { | ||
78 | msg.push_str(" \nThese items were found instead:"); | ||
79 | } | ||
80 | msg.push_str("\n\n---\n"); | ||
81 | msg | ||
82 | } else { | ||
83 | String::new() | ||
84 | }; | ||
85 | |||
86 | markup.push_str(&self.results.join("\n\n---\n")); | ||
87 | |||
88 | markup | ||
89 | } | 62 | } |
90 | } | 63 | } |
91 | 64 | ||
@@ -94,10 +67,10 @@ fn hover_text( | |||
94 | desc: Option<String>, | 67 | desc: Option<String>, |
95 | mod_path: Option<String>, | 68 | mod_path: Option<String>, |
96 | ) -> Option<String> { | 69 | ) -> Option<String> { |
97 | match (desc, docs, mod_path) { | 70 | if let Some(desc) = desc { |
98 | (Some(desc), docs, mod_path) => Some(rust_code_markup_with_doc(desc, docs, mod_path)), | 71 | Some(rust_code_markup_with_doc(&desc, docs.as_deref(), mod_path.as_deref())) |
99 | (None, Some(docs), _) => Some(docs), | 72 | } else { |
100 | _ => None, | 73 | docs |
101 | } | 74 | } |
102 | } | 75 | } |
103 | 76 | ||
@@ -133,7 +106,7 @@ fn determine_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> { | |||
133 | .flatten() | 106 | .flatten() |
134 | .join("::") | 107 | .join("::") |
135 | }); | 108 | }); |
136 | mod_path | 109 | mod_path // FIXME: replace dashes with underscores in crate display name |
137 | } | 110 | } |
138 | 111 | ||
139 | fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<String> { | 112 | fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<String> { |
@@ -170,9 +143,7 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
170 | ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), | 143 | ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), |
171 | ModuleDef::BuiltinType(it) => Some(it.to_string()), | 144 | ModuleDef::BuiltinType(it) => Some(it.to_string()), |
172 | }, | 145 | }, |
173 | Definition::Local(it) => { | 146 | Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display_truncated(db, None))), |
174 | Some(rust_code_markup(it.ty(db).display_truncated(db, None).to_string())) | ||
175 | } | ||
176 | Definition::TypeParam(_) | Definition::SelfType(_) => { | 147 | Definition::TypeParam(_) | Definition::SelfType(_) => { |
177 | // FIXME: Hover for generic param | 148 | // FIXME: Hover for generic param |
178 | None | 149 | None |
@@ -237,7 +208,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
237 | } | 208 | } |
238 | }?; | 209 | }?; |
239 | 210 | ||
240 | res.extend(Some(rust_code_markup(ty.display_truncated(db, None).to_string()))); | 211 | res.extend(Some(rust_code_markup(&ty.display_truncated(db, None)))); |
241 | let range = sema.original_range(&node).range; | 212 | let range = sema.original_range(&node).range; |
242 | Some(RangeInfo::new(range, res)) | 213 | Some(RangeInfo::new(range, res)) |
243 | } | 214 | } |
@@ -595,7 +566,6 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
595 | ); | 566 | ); |
596 | let hover = analysis.hover(position).unwrap().unwrap(); | 567 | let hover = analysis.hover(position).unwrap().unwrap(); |
597 | assert_eq!(trim_markup_opt(hover.info.first()), Some("wrapper::Thing\nfn new() -> Thing")); | 568 | assert_eq!(trim_markup_opt(hover.info.first()), Some("wrapper::Thing\nfn new() -> Thing")); |
598 | assert_eq!(hover.info.is_exact(), true); | ||
599 | } | 569 | } |
600 | 570 | ||
601 | #[test] | 571 | #[test] |
@@ -618,7 +588,6 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
618 | ); | 588 | ); |
619 | let hover = analysis.hover(position).unwrap().unwrap(); | 589 | let hover = analysis.hover(position).unwrap().unwrap(); |
620 | assert_eq!(trim_markup_opt(hover.info.first()), Some("const C: u32")); | 590 | assert_eq!(trim_markup_opt(hover.info.first()), Some("const C: u32")); |
621 | assert_eq!(hover.info.is_exact(), true); | ||
622 | } | 591 | } |
623 | 592 | ||
624 | #[test] | 593 | #[test] |
@@ -635,7 +604,6 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
635 | ); | 604 | ); |
636 | let hover = analysis.hover(position).unwrap().unwrap(); | 605 | let hover = analysis.hover(position).unwrap().unwrap(); |
637 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); | 606 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); |
638 | assert_eq!(hover.info.is_exact(), true); | ||
639 | 607 | ||
640 | /* FIXME: revive these tests | 608 | /* FIXME: revive these tests |
641 | let (analysis, position) = single_file_with_position( | 609 | let (analysis, position) = single_file_with_position( |
@@ -651,7 +619,6 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
651 | 619 | ||
652 | let hover = analysis.hover(position).unwrap().unwrap(); | 620 | let hover = analysis.hover(position).unwrap().unwrap(); |
653 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); | 621 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); |
654 | assert_eq!(hover.info.is_exact(), true); | ||
655 | 622 | ||
656 | let (analysis, position) = single_file_with_position( | 623 | let (analysis, position) = single_file_with_position( |
657 | " | 624 | " |
@@ -665,7 +632,6 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
665 | ); | 632 | ); |
666 | let hover = analysis.hover(position).unwrap().unwrap(); | 633 | let hover = analysis.hover(position).unwrap().unwrap(); |
667 | assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); | 634 | assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); |
668 | assert_eq!(hover.info.is_exact(), true); | ||
669 | 635 | ||
670 | let (analysis, position) = single_file_with_position( | 636 | let (analysis, position) = single_file_with_position( |
671 | " | 637 | " |
@@ -678,7 +644,6 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
678 | ); | 644 | ); |
679 | let hover = analysis.hover(position).unwrap().unwrap(); | 645 | let hover = analysis.hover(position).unwrap().unwrap(); |
680 | assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); | 646 | assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); |
681 | assert_eq!(hover.info.is_exact(), true); | ||
682 | */ | 647 | */ |
683 | } | 648 | } |
684 | 649 | ||
@@ -696,7 +661,6 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
696 | ); | 661 | ); |
697 | let hover = analysis.hover(position).unwrap().unwrap(); | 662 | let hover = analysis.hover(position).unwrap().unwrap(); |
698 | assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); | 663 | assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); |
699 | assert_eq!(hover.info.is_exact(), true); | ||
700 | } | 664 | } |
701 | 665 | ||
702 | #[test] | 666 | #[test] |
@@ -714,7 +678,6 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
714 | ); | 678 | ); |
715 | let hover = analysis.hover(position).unwrap().unwrap(); | 679 | let hover = analysis.hover(position).unwrap().unwrap(); |
716 | assert_eq!(trim_markup_opt(hover.info.first()), Some("macro_rules! foo")); | 680 | assert_eq!(trim_markup_opt(hover.info.first()), Some("macro_rules! foo")); |
717 | assert_eq!(hover.info.is_exact(), true); | ||
718 | } | 681 | } |
719 | 682 | ||
720 | #[test] | 683 | #[test] |
@@ -726,7 +689,6 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
726 | ); | 689 | ); |
727 | let hover = analysis.hover(position).unwrap().unwrap(); | 690 | let hover = analysis.hover(position).unwrap().unwrap(); |
728 | assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); | 691 | assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); |
729 | assert_eq!(hover.info.is_exact(), true); | ||
730 | } | 692 | } |
731 | 693 | ||
732 | #[test] | 694 | #[test] |
@@ -833,19 +795,34 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
833 | } | 795 | } |
834 | 796 | ||
835 | #[test] | 797 | #[test] |
798 | fn test_hover_through_assert_macro() { | ||
799 | let hover_on = check_hover_result( | ||
800 | r#" | ||
801 | //- /lib.rs | ||
802 | #[rustc_builtin_macro] | ||
803 | macro_rules! assert {} | ||
804 | |||
805 | fn bar() -> bool { true } | ||
806 | fn foo() { | ||
807 | assert!(ba<|>r()); | ||
808 | } | ||
809 | "#, | ||
810 | &["fn bar() -> bool"], | ||
811 | ); | ||
812 | |||
813 | assert_eq!(hover_on, "bar"); | ||
814 | } | ||
815 | |||
816 | #[test] | ||
836 | fn test_hover_through_literal_string_in_builtin_macro() { | 817 | fn test_hover_through_literal_string_in_builtin_macro() { |
837 | check_hover_no_result( | 818 | check_hover_no_result( |
838 | r#" | 819 | r#" |
839 | //- /lib.rs | 820 | //- /lib.rs |
840 | #[rustc_builtin_macro] | 821 | #[rustc_builtin_macro] |
841 | macro_rules! assert { | 822 | macro_rules! format {} |
842 | ($cond:expr) => {{ /* compiler built-in */ }}; | ||
843 | ($cond:expr,) => {{ /* compiler built-in */ }}; | ||
844 | ($cond:expr, $($arg:tt)+) => {{ /* compiler built-in */ }}; | ||
845 | } | ||
846 | 823 | ||
847 | fn foo() { | 824 | fn foo() { |
848 | assert!("hel<|>lo"); | 825 | format!("hel<|>lo {}", 0); |
849 | } | 826 | } |
850 | "#, | 827 | "#, |
851 | ); | 828 | ); |
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index cf0cbdbd0..ecd615cf4 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs | |||
@@ -10,7 +10,20 @@ use ra_syntax::{ | |||
10 | 10 | ||
11 | use crate::{FileId, FunctionSignature}; | 11 | use crate::{FileId, FunctionSignature}; |
12 | 12 | ||
13 | #[derive(Debug, PartialEq, Eq)] | 13 | #[derive(Clone, Debug, PartialEq, Eq)] |
14 | pub struct InlayHintsOptions { | ||
15 | pub type_hints: bool, | ||
16 | pub parameter_hints: bool, | ||
17 | pub max_length: Option<usize>, | ||
18 | } | ||
19 | |||
20 | impl Default for InlayHintsOptions { | ||
21 | fn default() -> Self { | ||
22 | Self { type_hints: true, parameter_hints: true, max_length: None } | ||
23 | } | ||
24 | } | ||
25 | |||
26 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
14 | pub enum InlayKind { | 27 | pub enum InlayKind { |
15 | TypeHint, | 28 | TypeHint, |
16 | ParameterHint, | 29 | ParameterHint, |
@@ -26,7 +39,7 @@ pub struct InlayHint { | |||
26 | pub(crate) fn inlay_hints( | 39 | pub(crate) fn inlay_hints( |
27 | db: &RootDatabase, | 40 | db: &RootDatabase, |
28 | file_id: FileId, | 41 | file_id: FileId, |
29 | max_inlay_hint_length: Option<usize>, | 42 | options: &InlayHintsOptions, |
30 | ) -> Vec<InlayHint> { | 43 | ) -> Vec<InlayHint> { |
31 | let _p = profile("inlay_hints"); | 44 | let _p = profile("inlay_hints"); |
32 | let sema = Semantics::new(db); | 45 | let sema = Semantics::new(db); |
@@ -36,9 +49,9 @@ pub(crate) fn inlay_hints( | |||
36 | for node in file.syntax().descendants() { | 49 | for node in file.syntax().descendants() { |
37 | match_ast! { | 50 | match_ast! { |
38 | match node { | 51 | match node { |
39 | ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, ast::Expr::from(it)); }, | 52 | ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, options, ast::Expr::from(it)); }, |
40 | ast::MethodCallExpr(it) => { get_param_name_hints(&mut res, &sema, ast::Expr::from(it)); }, | 53 | ast::MethodCallExpr(it) => { get_param_name_hints(&mut res, &sema, options, ast::Expr::from(it)); }, |
41 | ast::BindPat(it) => { get_bind_pat_hints(&mut res, &sema, max_inlay_hint_length, it); }, | 54 | ast::BindPat(it) => { get_bind_pat_hints(&mut res, &sema, options, it); }, |
42 | _ => (), | 55 | _ => (), |
43 | } | 56 | } |
44 | } | 57 | } |
@@ -49,8 +62,13 @@ pub(crate) fn inlay_hints( | |||
49 | fn get_param_name_hints( | 62 | fn get_param_name_hints( |
50 | acc: &mut Vec<InlayHint>, | 63 | acc: &mut Vec<InlayHint>, |
51 | sema: &Semantics<RootDatabase>, | 64 | sema: &Semantics<RootDatabase>, |
65 | options: &InlayHintsOptions, | ||
52 | expr: ast::Expr, | 66 | expr: ast::Expr, |
53 | ) -> Option<()> { | 67 | ) -> Option<()> { |
68 | if !options.parameter_hints { | ||
69 | return None; | ||
70 | } | ||
71 | |||
54 | let args = match &expr { | 72 | let args = match &expr { |
55 | ast::Expr::CallExpr(expr) => expr.arg_list()?.args(), | 73 | ast::Expr::CallExpr(expr) => expr.arg_list()?.args(), |
56 | ast::Expr::MethodCallExpr(expr) => expr.arg_list()?.args(), | 74 | ast::Expr::MethodCallExpr(expr) => expr.arg_list()?.args(), |
@@ -84,9 +102,13 @@ fn get_param_name_hints( | |||
84 | fn get_bind_pat_hints( | 102 | fn get_bind_pat_hints( |
85 | acc: &mut Vec<InlayHint>, | 103 | acc: &mut Vec<InlayHint>, |
86 | sema: &Semantics<RootDatabase>, | 104 | sema: &Semantics<RootDatabase>, |
87 | max_inlay_hint_length: Option<usize>, | 105 | options: &InlayHintsOptions, |
88 | pat: ast::BindPat, | 106 | pat: ast::BindPat, |
89 | ) -> Option<()> { | 107 | ) -> Option<()> { |
108 | if !options.type_hints { | ||
109 | return None; | ||
110 | } | ||
111 | |||
90 | let ty = sema.type_of_pat(&pat.clone().into())?; | 112 | let ty = sema.type_of_pat(&pat.clone().into())?; |
91 | 113 | ||
92 | if should_not_display_type_hint(sema.db, &pat, &ty) { | 114 | if should_not_display_type_hint(sema.db, &pat, &ty) { |
@@ -96,7 +118,7 @@ fn get_bind_pat_hints( | |||
96 | acc.push(InlayHint { | 118 | acc.push(InlayHint { |
97 | range: pat.syntax().text_range(), | 119 | range: pat.syntax().text_range(), |
98 | kind: InlayKind::TypeHint, | 120 | kind: InlayKind::TypeHint, |
99 | label: ty.display_truncated(sema.db, max_inlay_hint_length).to_string().into(), | 121 | label: ty.display_truncated(sema.db, options.max_length).to_string().into(), |
100 | }); | 122 | }); |
101 | Some(()) | 123 | Some(()) |
102 | } | 124 | } |
@@ -202,11 +224,66 @@ fn get_fn_signature(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option< | |||
202 | 224 | ||
203 | #[cfg(test)] | 225 | #[cfg(test)] |
204 | mod tests { | 226 | mod tests { |
227 | use crate::inlay_hints::InlayHintsOptions; | ||
205 | use insta::assert_debug_snapshot; | 228 | use insta::assert_debug_snapshot; |
206 | 229 | ||
207 | use crate::mock_analysis::single_file; | 230 | use crate::mock_analysis::single_file; |
208 | 231 | ||
209 | #[test] | 232 | #[test] |
233 | fn param_hints_only() { | ||
234 | let (analysis, file_id) = single_file( | ||
235 | r#" | ||
236 | fn foo(a: i32, b: i32) -> i32 { a + b } | ||
237 | fn main() { | ||
238 | let _x = foo(4, 4); | ||
239 | }"#, | ||
240 | ); | ||
241 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: true, type_hints: false, max_length: None}).unwrap(), @r###" | ||
242 | [ | ||
243 | InlayHint { | ||
244 | range: [106; 107), | ||
245 | kind: ParameterHint, | ||
246 | label: "a", | ||
247 | }, | ||
248 | InlayHint { | ||
249 | range: [109; 110), | ||
250 | kind: ParameterHint, | ||
251 | label: "b", | ||
252 | }, | ||
253 | ]"###); | ||
254 | } | ||
255 | |||
256 | #[test] | ||
257 | fn hints_disabled() { | ||
258 | let (analysis, file_id) = single_file( | ||
259 | r#" | ||
260 | fn foo(a: i32, b: i32) -> i32 { a + b } | ||
261 | fn main() { | ||
262 | let _x = foo(4, 4); | ||
263 | }"#, | ||
264 | ); | ||
265 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: false, parameter_hints: false, max_length: None}).unwrap(), @r###"[]"###); | ||
266 | } | ||
267 | |||
268 | #[test] | ||
269 | fn type_hints_only() { | ||
270 | let (analysis, file_id) = single_file( | ||
271 | r#" | ||
272 | fn foo(a: i32, b: i32) -> i32 { a + b } | ||
273 | fn main() { | ||
274 | let _x = foo(4, 4); | ||
275 | }"#, | ||
276 | ); | ||
277 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: true, parameter_hints: false, max_length: None}).unwrap(), @r###" | ||
278 | [ | ||
279 | InlayHint { | ||
280 | range: [97; 99), | ||
281 | kind: TypeHint, | ||
282 | label: "i32", | ||
283 | }, | ||
284 | ]"###); | ||
285 | } | ||
286 | #[test] | ||
210 | fn default_generic_types_should_not_be_displayed() { | 287 | fn default_generic_types_should_not_be_displayed() { |
211 | let (analysis, file_id) = single_file( | 288 | let (analysis, file_id) = single_file( |
212 | r#" | 289 | r#" |
@@ -221,7 +298,7 @@ fn main() { | |||
221 | }"#, | 298 | }"#, |
222 | ); | 299 | ); |
223 | 300 | ||
224 | assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" | 301 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" |
225 | [ | 302 | [ |
226 | InlayHint { | 303 | InlayHint { |
227 | range: [69; 71), | 304 | range: [69; 71), |
@@ -278,7 +355,7 @@ fn main() { | |||
278 | }"#, | 355 | }"#, |
279 | ); | 356 | ); |
280 | 357 | ||
281 | assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" | 358 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" |
282 | [ | 359 | [ |
283 | InlayHint { | 360 | InlayHint { |
284 | range: [193; 197), | 361 | range: [193; 197), |
@@ -358,7 +435,7 @@ fn main() { | |||
358 | }"#, | 435 | }"#, |
359 | ); | 436 | ); |
360 | 437 | ||
361 | assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" | 438 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" |
362 | [ | 439 | [ |
363 | InlayHint { | 440 | InlayHint { |
364 | range: [21; 30), | 441 | range: [21; 30), |
@@ -422,7 +499,7 @@ fn main() { | |||
422 | }"#, | 499 | }"#, |
423 | ); | 500 | ); |
424 | 501 | ||
425 | assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" | 502 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" |
426 | [ | 503 | [ |
427 | InlayHint { | 504 | InlayHint { |
428 | range: [21; 30), | 505 | range: [21; 30), |
@@ -472,7 +549,7 @@ fn main() { | |||
472 | }"#, | 549 | }"#, |
473 | ); | 550 | ); |
474 | 551 | ||
475 | assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" | 552 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" |
476 | [ | 553 | [ |
477 | InlayHint { | 554 | InlayHint { |
478 | range: [188; 192), | 555 | range: [188; 192), |
@@ -567,7 +644,7 @@ fn main() { | |||
567 | }"#, | 644 | }"#, |
568 | ); | 645 | ); |
569 | 646 | ||
570 | assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" | 647 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" |
571 | [ | 648 | [ |
572 | InlayHint { | 649 | InlayHint { |
573 | range: [188; 192), | 650 | range: [188; 192), |
@@ -662,7 +739,7 @@ fn main() { | |||
662 | }"#, | 739 | }"#, |
663 | ); | 740 | ); |
664 | 741 | ||
665 | assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" | 742 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" |
666 | [ | 743 | [ |
667 | InlayHint { | 744 | InlayHint { |
668 | range: [252; 256), | 745 | range: [252; 256), |
@@ -734,7 +811,7 @@ fn main() { | |||
734 | }"#, | 811 | }"#, |
735 | ); | 812 | ); |
736 | 813 | ||
737 | assert_debug_snapshot!(analysis.inlay_hints(file_id, Some(8)).unwrap(), @r###" | 814 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions { max_length: Some(8), ..Default::default() }).unwrap(), @r###" |
738 | [ | 815 | [ |
739 | InlayHint { | 816 | InlayHint { |
740 | range: [74; 75), | 817 | range: [74; 75), |
@@ -822,7 +899,7 @@ fn main() { | |||
822 | }"#, | 899 | }"#, |
823 | ); | 900 | ); |
824 | 901 | ||
825 | assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" | 902 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" |
826 | [ | 903 | [ |
827 | InlayHint { | 904 | InlayHint { |
828 | range: [798; 809), | 905 | range: [798; 809), |
@@ -944,7 +1021,7 @@ fn main() { | |||
944 | }"#, | 1021 | }"#, |
945 | ); | 1022 | ); |
946 | 1023 | ||
947 | assert_debug_snapshot!(analysis.inlay_hints(file_id, Some(8)).unwrap(), @r###" | 1024 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions { max_length: Some(8), ..Default::default() }).unwrap(), @r###" |
948 | [] | 1025 | [] |
949 | "### | 1026 | "### |
950 | ); | 1027 | ); |
@@ -970,7 +1047,7 @@ fn main() { | |||
970 | }"#, | 1047 | }"#, |
971 | ); | 1048 | ); |
972 | 1049 | ||
973 | assert_debug_snapshot!(analysis.inlay_hints(file_id, Some(8)).unwrap(), @r###" | 1050 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions { max_length: Some(8), ..Default::default() }).unwrap(), @r###" |
974 | [] | 1051 | [] |
975 | "### | 1052 | "### |
976 | ); | 1053 | ); |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 40276d4fe..5ab06c6cf 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -62,13 +62,13 @@ use crate::display::ToNav; | |||
62 | pub use crate::{ | 62 | pub use crate::{ |
63 | assists::{Assist, AssistId}, | 63 | assists::{Assist, AssistId}, |
64 | call_hierarchy::CallItem, | 64 | call_hierarchy::CallItem, |
65 | completion::{CompletionItem, CompletionItemKind, InsertTextFormat}, | 65 | completion::{CompletionItem, CompletionItemKind, CompletionOptions, InsertTextFormat}, |
66 | diagnostics::Severity, | 66 | diagnostics::Severity, |
67 | display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, | 67 | display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, |
68 | expand_macro::ExpandedMacro, | 68 | expand_macro::ExpandedMacro, |
69 | folding_ranges::{Fold, FoldKind}, | 69 | folding_ranges::{Fold, FoldKind}, |
70 | hover::HoverResult, | 70 | hover::HoverResult, |
71 | inlay_hints::{InlayHint, InlayKind}, | 71 | inlay_hints::{InlayHint, InlayHintsOptions, InlayKind}, |
72 | references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, | 72 | references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, |
73 | runnables::{Runnable, RunnableKind, TestId}, | 73 | runnables::{Runnable, RunnableKind, TestId}, |
74 | source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, | 74 | source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, |
@@ -84,7 +84,6 @@ pub use ra_db::{ | |||
84 | }; | 84 | }; |
85 | pub use ra_ide_db::{ | 85 | pub use ra_ide_db::{ |
86 | change::{AnalysisChange, LibraryData}, | 86 | change::{AnalysisChange, LibraryData}, |
87 | feature_flags::FeatureFlags, | ||
88 | line_index::{LineCol, LineIndex}, | 87 | line_index::{LineCol, LineIndex}, |
89 | line_index_utils::translate_offset_with_edit, | 88 | line_index_utils::translate_offset_with_edit, |
90 | search::SearchScope, | 89 | search::SearchScope, |
@@ -131,13 +130,13 @@ pub struct AnalysisHost { | |||
131 | 130 | ||
132 | impl Default for AnalysisHost { | 131 | impl Default for AnalysisHost { |
133 | fn default() -> AnalysisHost { | 132 | fn default() -> AnalysisHost { |
134 | AnalysisHost::new(None, FeatureFlags::default()) | 133 | AnalysisHost::new(None) |
135 | } | 134 | } |
136 | } | 135 | } |
137 | 136 | ||
138 | impl AnalysisHost { | 137 | impl AnalysisHost { |
139 | pub fn new(lru_capcity: Option<usize>, feature_flags: FeatureFlags) -> AnalysisHost { | 138 | pub fn new(lru_capacity: Option<usize>) -> AnalysisHost { |
140 | AnalysisHost { db: RootDatabase::new(lru_capcity, feature_flags) } | 139 | AnalysisHost { db: RootDatabase::new(lru_capacity) } |
141 | } | 140 | } |
142 | /// Returns a snapshot of the current state, which you can query for | 141 | /// Returns a snapshot of the current state, which you can query for |
143 | /// semantic information. | 142 | /// semantic information. |
@@ -145,10 +144,6 @@ impl AnalysisHost { | |||
145 | Analysis { db: self.db.snapshot() } | 144 | Analysis { db: self.db.snapshot() } |
146 | } | 145 | } |
147 | 146 | ||
148 | pub fn feature_flags(&self) -> &FeatureFlags { | ||
149 | &self.db.feature_flags | ||
150 | } | ||
151 | |||
152 | /// Applies changes to the current state of the world. If there are | 147 | /// Applies changes to the current state of the world. If there are |
153 | /// outstanding snapshots, they will be canceled. | 148 | /// outstanding snapshots, they will be canceled. |
154 | pub fn apply_change(&mut self, change: AnalysisChange) { | 149 | pub fn apply_change(&mut self, change: AnalysisChange) { |
@@ -217,6 +212,7 @@ impl Analysis { | |||
217 | None, | 212 | None, |
218 | cfg_options, | 213 | cfg_options, |
219 | Env::default(), | 214 | Env::default(), |
215 | Default::default(), | ||
220 | ); | 216 | ); |
221 | change.add_file(source_root, file_id, "main.rs".into(), Arc::new(text)); | 217 | change.add_file(source_root, file_id, "main.rs".into(), Arc::new(text)); |
222 | change.set_crate_graph(crate_graph); | 218 | change.set_crate_graph(crate_graph); |
@@ -224,11 +220,6 @@ impl Analysis { | |||
224 | (host.analysis(), file_id) | 220 | (host.analysis(), file_id) |
225 | } | 221 | } |
226 | 222 | ||
227 | /// Features for Analysis. | ||
228 | pub fn feature_flags(&self) -> &FeatureFlags { | ||
229 | &self.db.feature_flags | ||
230 | } | ||
231 | |||
232 | /// Debug info about the current state of the analysis. | 223 | /// Debug info about the current state of the analysis. |
233 | pub fn status(&self) -> Cancelable<String> { | 224 | pub fn status(&self) -> Cancelable<String> { |
234 | self.with_db(|db| status::status(&*db)) | 225 | self.with_db(|db| status::status(&*db)) |
@@ -328,9 +319,9 @@ impl Analysis { | |||
328 | pub fn inlay_hints( | 319 | pub fn inlay_hints( |
329 | &self, | 320 | &self, |
330 | file_id: FileId, | 321 | file_id: FileId, |
331 | max_inlay_hint_length: Option<usize>, | 322 | inlay_hint_opts: &InlayHintsOptions, |
332 | ) -> Cancelable<Vec<InlayHint>> { | 323 | ) -> Cancelable<Vec<InlayHint>> { |
333 | self.with_db(|db| inlay_hints::inlay_hints(db, file_id, max_inlay_hint_length)) | 324 | self.with_db(|db| inlay_hints::inlay_hints(db, file_id, inlay_hint_opts)) |
334 | } | 325 | } |
335 | 326 | ||
336 | /// Returns the set of folding ranges. | 327 | /// Returns the set of folding ranges. |
@@ -450,8 +441,12 @@ impl Analysis { | |||
450 | } | 441 | } |
451 | 442 | ||
452 | /// Computes completions at the given position. | 443 | /// Computes completions at the given position. |
453 | pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> { | 444 | pub fn completions( |
454 | self.with_db(|db| completion::completions(db, position).map(Into::into)) | 445 | &self, |
446 | position: FilePosition, | ||
447 | options: &CompletionOptions, | ||
448 | ) -> Cancelable<Option<Vec<CompletionItem>>> { | ||
449 | self.with_db(|db| completion::completions(db, position, options).map(Into::into)) | ||
455 | } | 450 | } |
456 | 451 | ||
457 | /// Computes assists (aka code actions aka intentions) for the given | 452 | /// Computes assists (aka code actions aka intentions) for the given |
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs index 90f84b052..25816cf6f 100644 --- a/crates/ra_ide/src/mock_analysis.rs +++ b/crates/ra_ide/src/mock_analysis.rs | |||
@@ -102,6 +102,7 @@ impl MockAnalysis { | |||
102 | None, | 102 | None, |
103 | cfg_options, | 103 | cfg_options, |
104 | Env::default(), | 104 | Env::default(), |
105 | Default::default(), | ||
105 | )); | 106 | )); |
106 | } else if path.ends_with("/lib.rs") { | 107 | } else if path.ends_with("/lib.rs") { |
107 | let crate_name = path.parent().unwrap().file_name().unwrap(); | 108 | let crate_name = path.parent().unwrap().file_name().unwrap(); |
@@ -111,6 +112,7 @@ impl MockAnalysis { | |||
111 | Some(crate_name.to_owned()), | 112 | Some(crate_name.to_owned()), |
112 | cfg_options, | 113 | cfg_options, |
113 | Env::default(), | 114 | Env::default(), |
115 | Default::default(), | ||
114 | ); | 116 | ); |
115 | if let Some(root_crate) = root_crate { | 117 | if let Some(root_crate) = root_crate { |
116 | crate_graph | 118 | crate_graph |
diff --git a/crates/ra_ide/src/parent_module.rs b/crates/ra_ide/src/parent_module.rs index b73cefd97..76d130b9b 100644 --- a/crates/ra_ide/src/parent_module.rs +++ b/crates/ra_ide/src/parent_module.rs | |||
@@ -136,6 +136,7 @@ mod tests { | |||
136 | None, | 136 | None, |
137 | CfgOptions::default(), | 137 | CfgOptions::default(), |
138 | Env::default(), | 138 | Env::default(), |
139 | Default::default(), | ||
139 | ); | 140 | ); |
140 | let mut change = AnalysisChange::new(); | 141 | let mut change = AnalysisChange::new(); |
141 | change.set_crate_graph(crate_graph); | 142 | change.set_crate_graph(crate_graph); |
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index 5b4bcf434..7d1190af9 100644 --- a/crates/ra_ide/src/references/rename.rs +++ b/crates/ra_ide/src/references/rename.rs | |||
@@ -9,7 +9,8 @@ use ra_syntax::{ | |||
9 | use ra_text_edit::TextEdit; | 9 | use ra_text_edit::TextEdit; |
10 | 10 | ||
11 | use crate::{ | 11 | use crate::{ |
12 | FileId, FilePosition, FileSystemEdit, RangeInfo, SourceChange, SourceFileEdit, TextRange, | 12 | FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, SourceChange, |
13 | SourceFileEdit, TextRange, | ||
13 | }; | 14 | }; |
14 | 15 | ||
15 | use super::find_all_refs; | 16 | use super::find_all_refs; |
@@ -46,12 +47,29 @@ fn find_name_and_module_at_offset( | |||
46 | Some((ast_name, ast_module)) | 47 | Some((ast_name, ast_module)) |
47 | } | 48 | } |
48 | 49 | ||
49 | fn source_edit_from_file_id_range( | 50 | fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFileEdit { |
50 | file_id: FileId, | 51 | let mut replacement_text = String::new(); |
51 | range: TextRange, | 52 | let file_id = reference.file_range.file_id; |
52 | new_name: &str, | 53 | let range = match reference.kind { |
53 | ) -> SourceFileEdit { | 54 | ReferenceKind::StructFieldShorthandForField => { |
54 | SourceFileEdit { file_id, edit: TextEdit::replace(range, new_name.into()) } | 55 | replacement_text.push_str(new_name); |
56 | replacement_text.push_str(": "); | ||
57 | TextRange::from_to( | ||
58 | reference.file_range.range.start(), | ||
59 | reference.file_range.range.start(), | ||
60 | ) | ||
61 | } | ||
62 | ReferenceKind::StructFieldShorthandForLocal => { | ||
63 | replacement_text.push_str(": "); | ||
64 | replacement_text.push_str(new_name); | ||
65 | TextRange::from_to(reference.file_range.range.end(), reference.file_range.range.end()) | ||
66 | } | ||
67 | _ => { | ||
68 | replacement_text.push_str(new_name); | ||
69 | reference.file_range.range | ||
70 | } | ||
71 | }; | ||
72 | SourceFileEdit { file_id, edit: TextEdit::replace(range, replacement_text) } | ||
55 | } | 73 | } |
56 | 74 | ||
57 | fn rename_mod( | 75 | fn rename_mod( |
@@ -99,13 +117,10 @@ fn rename_mod( | |||
99 | source_file_edits.push(edit); | 117 | source_file_edits.push(edit); |
100 | 118 | ||
101 | if let Some(RangeInfo { range: _, info: refs }) = find_all_refs(sema.db, position, None) { | 119 | if let Some(RangeInfo { range: _, info: refs }) = find_all_refs(sema.db, position, None) { |
102 | let ref_edits = refs.references.into_iter().map(|reference| { | 120 | let ref_edits = refs |
103 | source_edit_from_file_id_range( | 121 | .references |
104 | reference.file_range.file_id, | 122 | .into_iter() |
105 | reference.file_range.range, | 123 | .map(|reference| source_edit_from_reference(reference, new_name)); |
106 | new_name, | ||
107 | ) | ||
108 | }); | ||
109 | source_file_edits.extend(ref_edits); | 124 | source_file_edits.extend(ref_edits); |
110 | } | 125 | } |
111 | 126 | ||
@@ -121,13 +136,7 @@ fn rename_reference( | |||
121 | 136 | ||
122 | let edit = refs | 137 | let edit = refs |
123 | .into_iter() | 138 | .into_iter() |
124 | .map(|reference| { | 139 | .map(|reference| source_edit_from_reference(reference, new_name)) |
125 | source_edit_from_file_id_range( | ||
126 | reference.file_range.file_id, | ||
127 | reference.file_range.range, | ||
128 | new_name, | ||
129 | ) | ||
130 | }) | ||
131 | .collect::<Vec<_>>(); | 140 | .collect::<Vec<_>>(); |
132 | 141 | ||
133 | if edit.is_empty() { | 142 | if edit.is_empty() { |
@@ -286,6 +295,163 @@ mod tests { | |||
286 | } | 295 | } |
287 | 296 | ||
288 | #[test] | 297 | #[test] |
298 | fn test_rename_struct_field() { | ||
299 | test_rename( | ||
300 | r#" | ||
301 | struct Foo { | ||
302 | i<|>: i32, | ||
303 | } | ||
304 | |||
305 | impl Foo { | ||
306 | fn new(i: i32) -> Self { | ||
307 | Self { i: i } | ||
308 | } | ||
309 | } | ||
310 | "#, | ||
311 | "j", | ||
312 | r#" | ||
313 | struct Foo { | ||
314 | j: i32, | ||
315 | } | ||
316 | |||
317 | impl Foo { | ||
318 | fn new(i: i32) -> Self { | ||
319 | Self { j: i } | ||
320 | } | ||
321 | } | ||
322 | "#, | ||
323 | ); | ||
324 | } | ||
325 | |||
326 | #[test] | ||
327 | fn test_rename_struct_field_for_shorthand() { | ||
328 | test_rename( | ||
329 | r#" | ||
330 | struct Foo { | ||
331 | i<|>: i32, | ||
332 | } | ||
333 | |||
334 | impl Foo { | ||
335 | fn new(i: i32) -> Self { | ||
336 | Self { i } | ||
337 | } | ||
338 | } | ||
339 | "#, | ||
340 | "j", | ||
341 | r#" | ||
342 | struct Foo { | ||
343 | j: i32, | ||
344 | } | ||
345 | |||
346 | impl Foo { | ||
347 | fn new(i: i32) -> Self { | ||
348 | Self { j: i } | ||
349 | } | ||
350 | } | ||
351 | "#, | ||
352 | ); | ||
353 | } | ||
354 | |||
355 | #[test] | ||
356 | fn test_rename_local_for_field_shorthand() { | ||
357 | test_rename( | ||
358 | r#" | ||
359 | struct Foo { | ||
360 | i: i32, | ||
361 | } | ||
362 | |||
363 | impl Foo { | ||
364 | fn new(i<|>: i32) -> Self { | ||
365 | Self { i } | ||
366 | } | ||
367 | } | ||
368 | "#, | ||
369 | "j", | ||
370 | r#" | ||
371 | struct Foo { | ||
372 | i: i32, | ||
373 | } | ||
374 | |||
375 | impl Foo { | ||
376 | fn new(j: i32) -> Self { | ||
377 | Self { i: j } | ||
378 | } | ||
379 | } | ||
380 | "#, | ||
381 | ); | ||
382 | } | ||
383 | |||
384 | #[test] | ||
385 | fn test_field_shorthand_correct_struct() { | ||
386 | test_rename( | ||
387 | r#" | ||
388 | struct Foo { | ||
389 | i<|>: i32, | ||
390 | } | ||
391 | |||
392 | struct Bar { | ||
393 | i: i32, | ||
394 | } | ||
395 | |||
396 | impl Bar { | ||
397 | fn new(i: i32) -> Self { | ||
398 | Self { i } | ||
399 | } | ||
400 | } | ||
401 | "#, | ||
402 | "j", | ||
403 | r#" | ||
404 | struct Foo { | ||
405 | j: i32, | ||
406 | } | ||
407 | |||
408 | struct Bar { | ||
409 | i: i32, | ||
410 | } | ||
411 | |||
412 | impl Bar { | ||
413 | fn new(i: i32) -> Self { | ||
414 | Self { i } | ||
415 | } | ||
416 | } | ||
417 | "#, | ||
418 | ); | ||
419 | } | ||
420 | |||
421 | #[test] | ||
422 | fn test_shadow_local_for_struct_shorthand() { | ||
423 | test_rename( | ||
424 | r#" | ||
425 | struct Foo { | ||
426 | i: i32, | ||
427 | } | ||
428 | |||
429 | fn baz(i<|>: i32) -> Self { | ||
430 | let x = Foo { i }; | ||
431 | { | ||
432 | let i = 0; | ||
433 | Foo { i } | ||
434 | } | ||
435 | } | ||
436 | "#, | ||
437 | "j", | ||
438 | r#" | ||
439 | struct Foo { | ||
440 | i: i32, | ||
441 | } | ||
442 | |||
443 | fn baz(j: i32) -> Self { | ||
444 | let x = Foo { i: j }; | ||
445 | { | ||
446 | let i = 0; | ||
447 | Foo { i } | ||
448 | } | ||
449 | } | ||
450 | "#, | ||
451 | ); | ||
452 | } | ||
453 | |||
454 | #[test] | ||
289 | fn test_rename_mod() { | 455 | fn test_rename_mod() { |
290 | let (analysis, position) = analysis_and_position( | 456 | let (analysis, position) = analysis_and_position( |
291 | " | 457 | " |
diff --git a/crates/ra_ide/src/syntax_tree.rs b/crates/ra_ide/src/syntax_tree.rs index 55966daf3..f58e436d1 100644 --- a/crates/ra_ide/src/syntax_tree.rs +++ b/crates/ra_ide/src/syntax_tree.rs | |||
@@ -5,7 +5,7 @@ use ra_ide_db::RootDatabase; | |||
5 | use ra_syntax::{ | 5 | use ra_syntax::{ |
6 | algo, AstNode, NodeOrToken, SourceFile, | 6 | algo, AstNode, NodeOrToken, SourceFile, |
7 | SyntaxKind::{RAW_STRING, STRING}, | 7 | SyntaxKind::{RAW_STRING, STRING}, |
8 | SyntaxToken, TextRange, | 8 | SyntaxToken, TextRange, TextUnit, |
9 | }; | 9 | }; |
10 | 10 | ||
11 | pub use ra_db::FileId; | 11 | pub use ra_db::FileId; |
@@ -56,19 +56,23 @@ fn syntax_tree_for_token(node: &SyntaxToken, text_range: TextRange) -> Option<St | |||
56 | let start = text_range.start() - node_range.start(); | 56 | let start = text_range.start() - node_range.start(); |
57 | 57 | ||
58 | // how many characters we have selected | 58 | // how many characters we have selected |
59 | let len = text_range.len().to_usize(); | 59 | let len = text_range.len(); |
60 | 60 | ||
61 | let node_len = node_range.len().to_usize(); | 61 | let node_len = node_range.len(); |
62 | 62 | ||
63 | let start = start.to_usize(); | 63 | let start = start; |
64 | 64 | ||
65 | // We want to cap our length | 65 | // We want to cap our length |
66 | let len = len.min(node_len); | 66 | let len = len.min(node_len); |
67 | 67 | ||
68 | // Ensure our slice is inside the actual string | 68 | // Ensure our slice is inside the actual string |
69 | let end = if start + len < text.len() { start + len } else { text.len() - start }; | 69 | let end = if start + len < TextUnit::of_str(&text) { |
70 | start + len | ||
71 | } else { | ||
72 | TextUnit::of_str(&text) - start | ||
73 | }; | ||
70 | 74 | ||
71 | let text = &text[start..end]; | 75 | let text = &text[TextRange::from_to(start, end)]; |
72 | 76 | ||
73 | // Remove possible extra string quotes from the start | 77 | // Remove possible extra string quotes from the start |
74 | // and the end of the string | 78 | // and the end of the string |
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs index 7f1b9150f..cb2cd2479 100644 --- a/crates/ra_ide/src/typing.rs +++ b/crates/ra_ide/src/typing.rs | |||
@@ -13,77 +13,21 @@ | |||
13 | //! Language server executes such typing assists synchronously. That is, they | 13 | //! Language server executes such typing assists synchronously. That is, they |
14 | //! block user's typing and should be pretty fast for this reason! | 14 | //! block user's typing and should be pretty fast for this reason! |
15 | 15 | ||
16 | mod on_enter; | ||
17 | |||
16 | use ra_db::{FilePosition, SourceDatabase}; | 18 | use ra_db::{FilePosition, SourceDatabase}; |
17 | use ra_fmt::leading_indent; | 19 | use ra_fmt::leading_indent; |
18 | use ra_ide_db::RootDatabase; | 20 | use ra_ide_db::RootDatabase; |
19 | use ra_syntax::{ | 21 | use ra_syntax::{ |
20 | algo::find_node_at_offset, | 22 | algo::find_node_at_offset, |
21 | ast::{self, AstToken}, | 23 | ast::{self, AstToken}, |
22 | AstNode, SmolStr, SourceFile, | 24 | AstNode, SourceFile, TextRange, TextUnit, |
23 | SyntaxKind::*, | ||
24 | SyntaxToken, TextRange, TextUnit, TokenAtOffset, | ||
25 | }; | 25 | }; |
26 | use ra_text_edit::TextEdit; | 26 | use ra_text_edit::TextEdit; |
27 | 27 | ||
28 | use crate::{source_change::SingleFileChange, SourceChange, SourceFileEdit}; | 28 | use crate::{source_change::SingleFileChange, SourceChange}; |
29 | |||
30 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<SourceChange> { | ||
31 | let parse = db.parse(position.file_id); | ||
32 | let file = parse.tree(); | ||
33 | let comment = file | ||
34 | .syntax() | ||
35 | .token_at_offset(position.offset) | ||
36 | .left_biased() | ||
37 | .and_then(ast::Comment::cast)?; | ||
38 | |||
39 | if comment.kind().shape.is_block() { | ||
40 | return None; | ||
41 | } | ||
42 | |||
43 | let prefix = comment.prefix(); | ||
44 | let comment_range = comment.syntax().text_range(); | ||
45 | if position.offset < comment_range.start() + TextUnit::of_str(prefix) { | ||
46 | return None; | ||
47 | } | ||
48 | |||
49 | // Continuing non-doc line comments (like this one :) ) is annoying | ||
50 | if prefix == "//" && comment_range.end() == position.offset { | ||
51 | return None; | ||
52 | } | ||
53 | |||
54 | let indent = node_indent(&file, comment.syntax())?; | ||
55 | let inserted = format!("\n{}{} ", indent, prefix); | ||
56 | let cursor_position = position.offset + TextUnit::of_str(&inserted); | ||
57 | let edit = TextEdit::insert(position.offset, inserted); | ||
58 | 29 | ||
59 | Some( | 30 | pub(crate) use on_enter::on_enter; |
60 | SourceChange::source_file_edit( | ||
61 | "on enter", | ||
62 | SourceFileEdit { edit, file_id: position.file_id }, | ||
63 | ) | ||
64 | .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }), | ||
65 | ) | ||
66 | } | ||
67 | |||
68 | fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option<SmolStr> { | ||
69 | let ws = match file.syntax().token_at_offset(token.text_range().start()) { | ||
70 | TokenAtOffset::Between(l, r) => { | ||
71 | assert!(r == *token); | ||
72 | l | ||
73 | } | ||
74 | TokenAtOffset::Single(n) => { | ||
75 | assert!(n == *token); | ||
76 | return Some("".into()); | ||
77 | } | ||
78 | TokenAtOffset::None => unreachable!(), | ||
79 | }; | ||
80 | if ws.kind() != WHITESPACE { | ||
81 | return None; | ||
82 | } | ||
83 | let text = ws.text(); | ||
84 | let pos = text.rfind('\n').map(|it| it + 1).unwrap_or(0); | ||
85 | Some(text[pos..].into()) | ||
86 | } | ||
87 | 31 | ||
88 | pub(crate) const TRIGGER_CHARS: &str = ".=>"; | 32 | pub(crate) const TRIGGER_CHARS: &str = ".=>"; |
89 | 33 | ||
@@ -196,102 +140,10 @@ fn on_arrow_typed(file: &SourceFile, offset: TextUnit) -> Option<SingleFileChang | |||
196 | 140 | ||
197 | #[cfg(test)] | 141 | #[cfg(test)] |
198 | mod tests { | 142 | mod tests { |
199 | use test_utils::{add_cursor, assert_eq_text, extract_offset}; | 143 | use test_utils::{assert_eq_text, extract_offset}; |
200 | |||
201 | use crate::mock_analysis::single_file; | ||
202 | 144 | ||
203 | use super::*; | 145 | use super::*; |
204 | 146 | ||
205 | #[test] | ||
206 | fn test_on_enter() { | ||
207 | fn apply_on_enter(before: &str) -> Option<String> { | ||
208 | let (offset, before) = extract_offset(before); | ||
209 | let (analysis, file_id) = single_file(&before); | ||
210 | let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; | ||
211 | |||
212 | assert_eq!(result.source_file_edits.len(), 1); | ||
213 | let actual = result.source_file_edits[0].edit.apply(&before); | ||
214 | let actual = add_cursor(&actual, result.cursor_position.unwrap().offset); | ||
215 | Some(actual) | ||
216 | } | ||
217 | |||
218 | fn do_check(before: &str, after: &str) { | ||
219 | let actual = apply_on_enter(before).unwrap(); | ||
220 | assert_eq_text!(after, &actual); | ||
221 | } | ||
222 | |||
223 | fn do_check_noop(text: &str) { | ||
224 | assert!(apply_on_enter(text).is_none()) | ||
225 | } | ||
226 | |||
227 | do_check( | ||
228 | r" | ||
229 | /// Some docs<|> | ||
230 | fn foo() { | ||
231 | } | ||
232 | ", | ||
233 | r" | ||
234 | /// Some docs | ||
235 | /// <|> | ||
236 | fn foo() { | ||
237 | } | ||
238 | ", | ||
239 | ); | ||
240 | do_check( | ||
241 | r" | ||
242 | impl S { | ||
243 | /// Some<|> docs. | ||
244 | fn foo() {} | ||
245 | } | ||
246 | ", | ||
247 | r" | ||
248 | impl S { | ||
249 | /// Some | ||
250 | /// <|> docs. | ||
251 | fn foo() {} | ||
252 | } | ||
253 | ", | ||
254 | ); | ||
255 | do_check( | ||
256 | r" | ||
257 | fn main() { | ||
258 | // Fix<|> me | ||
259 | let x = 1 + 1; | ||
260 | } | ||
261 | ", | ||
262 | r" | ||
263 | fn main() { | ||
264 | // Fix | ||
265 | // <|> me | ||
266 | let x = 1 + 1; | ||
267 | } | ||
268 | ", | ||
269 | ); | ||
270 | do_check( | ||
271 | r" | ||
272 | ///<|> Some docs | ||
273 | fn foo() { | ||
274 | } | ||
275 | ", | ||
276 | r" | ||
277 | /// | ||
278 | /// <|> Some docs | ||
279 | fn foo() { | ||
280 | } | ||
281 | ", | ||
282 | ); | ||
283 | do_check_noop( | ||
284 | r" | ||
285 | fn main() { | ||
286 | // Fix me<|> | ||
287 | let x = 1 + 1; | ||
288 | } | ||
289 | ", | ||
290 | ); | ||
291 | |||
292 | do_check_noop(r"<|>//! docz"); | ||
293 | } | ||
294 | |||
295 | fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> { | 147 | fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> { |
296 | let (offset, before) = extract_offset(before); | 148 | let (offset, before) = extract_offset(before); |
297 | let edit = TextEdit::insert(offset, char_typed.to_string()); | 149 | let edit = TextEdit::insert(offset, char_typed.to_string()); |
@@ -361,14 +213,14 @@ fn foo() { | |||
361 | type_char( | 213 | type_char( |
362 | '.', | 214 | '.', |
363 | r" | 215 | r" |
364 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 216 | fn main() { |
365 | self.child_impl(db, name) | 217 | xs.foo() |
366 | <|> | 218 | <|> |
367 | } | 219 | } |
368 | ", | 220 | ", |
369 | r" | 221 | r" |
370 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 222 | fn main() { |
371 | self.child_impl(db, name) | 223 | xs.foo() |
372 | . | 224 | . |
373 | } | 225 | } |
374 | ", | 226 | ", |
@@ -376,8 +228,8 @@ fn foo() { | |||
376 | type_char_noop( | 228 | type_char_noop( |
377 | '.', | 229 | '.', |
378 | r" | 230 | r" |
379 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 231 | fn main() { |
380 | self.child_impl(db, name) | 232 | xs.foo() |
381 | <|> | 233 | <|> |
382 | } | 234 | } |
383 | ", | 235 | ", |
@@ -389,14 +241,14 @@ fn foo() { | |||
389 | type_char( | 241 | type_char( |
390 | '.', | 242 | '.', |
391 | r" | 243 | r" |
392 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 244 | fn main() { |
393 | self.child_impl(db, name) | 245 | xs.foo() |
394 | <|>; | 246 | <|>; |
395 | } | 247 | } |
396 | ", | 248 | ", |
397 | r" | 249 | r" |
398 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 250 | fn main() { |
399 | self.child_impl(db, name) | 251 | xs.foo() |
400 | .; | 252 | .; |
401 | } | 253 | } |
402 | ", | 254 | ", |
@@ -404,8 +256,8 @@ fn foo() { | |||
404 | type_char_noop( | 256 | type_char_noop( |
405 | '.', | 257 | '.', |
406 | r" | 258 | r" |
407 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 259 | fn main() { |
408 | self.child_impl(db, name) | 260 | xs.foo() |
409 | <|>; | 261 | <|>; |
410 | } | 262 | } |
411 | ", | 263 | ", |
@@ -417,15 +269,15 @@ fn foo() { | |||
417 | type_char( | 269 | type_char( |
418 | '.', | 270 | '.', |
419 | r" | 271 | r" |
420 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 272 | fn main() { |
421 | self.child_impl(db, name) | 273 | xs.foo() |
422 | .first() | 274 | .first() |
423 | <|> | 275 | <|> |
424 | } | 276 | } |
425 | ", | 277 | ", |
426 | r" | 278 | r" |
427 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 279 | fn main() { |
428 | self.child_impl(db, name) | 280 | xs.foo() |
429 | .first() | 281 | .first() |
430 | . | 282 | . |
431 | } | 283 | } |
@@ -434,8 +286,8 @@ fn foo() { | |||
434 | type_char_noop( | 286 | type_char_noop( |
435 | '.', | 287 | '.', |
436 | r" | 288 | r" |
437 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 289 | fn main() { |
438 | self.child_impl(db, name) | 290 | xs.foo() |
439 | .first() | 291 | .first() |
440 | <|> | 292 | <|> |
441 | } | 293 | } |
@@ -482,7 +334,7 @@ fn foo() { | |||
482 | type_char_noop( | 334 | type_char_noop( |
483 | '.', | 335 | '.', |
484 | r" | 336 | r" |
485 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 337 | fn main() { |
486 | <|> | 338 | <|> |
487 | } | 339 | } |
488 | ", | 340 | ", |
@@ -490,7 +342,7 @@ fn foo() { | |||
490 | type_char_noop( | 342 | type_char_noop( |
491 | '.', | 343 | '.', |
492 | r" | 344 | r" |
493 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 345 | fn main() { |
494 | <|> | 346 | <|> |
495 | } | 347 | } |
496 | ", | 348 | ", |
diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs new file mode 100644 index 000000000..6bcf2d72b --- /dev/null +++ b/crates/ra_ide/src/typing/on_enter.rs | |||
@@ -0,0 +1,216 @@ | |||
1 | //! Handles the `Enter` key press. At the momently, this only continues | ||
2 | //! comments, but should handle indent some time in the future as well. | ||
3 | |||
4 | use ra_db::{FilePosition, SourceDatabase}; | ||
5 | use ra_ide_db::RootDatabase; | ||
6 | use ra_syntax::{ | ||
7 | ast::{self, AstToken}, | ||
8 | AstNode, SmolStr, SourceFile, | ||
9 | SyntaxKind::*, | ||
10 | SyntaxToken, TextUnit, TokenAtOffset, | ||
11 | }; | ||
12 | use ra_text_edit::TextEdit; | ||
13 | |||
14 | use crate::{SourceChange, SourceFileEdit}; | ||
15 | |||
16 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<SourceChange> { | ||
17 | let parse = db.parse(position.file_id); | ||
18 | let file = parse.tree(); | ||
19 | let comment = file | ||
20 | .syntax() | ||
21 | .token_at_offset(position.offset) | ||
22 | .left_biased() | ||
23 | .and_then(ast::Comment::cast)?; | ||
24 | |||
25 | if comment.kind().shape.is_block() { | ||
26 | return None; | ||
27 | } | ||
28 | |||
29 | let prefix = comment.prefix(); | ||
30 | let comment_range = comment.syntax().text_range(); | ||
31 | if position.offset < comment_range.start() + TextUnit::of_str(prefix) { | ||
32 | return None; | ||
33 | } | ||
34 | |||
35 | // Continuing single-line non-doc comments (like this one :) ) is annoying | ||
36 | if prefix == "//" && comment_range.end() == position.offset && !followed_by_comment(&comment) { | ||
37 | return None; | ||
38 | } | ||
39 | |||
40 | let indent = node_indent(&file, comment.syntax())?; | ||
41 | let inserted = format!("\n{}{} ", indent, prefix); | ||
42 | let cursor_position = position.offset + TextUnit::of_str(&inserted); | ||
43 | let edit = TextEdit::insert(position.offset, inserted); | ||
44 | |||
45 | Some( | ||
46 | SourceChange::source_file_edit( | ||
47 | "on enter", | ||
48 | SourceFileEdit { edit, file_id: position.file_id }, | ||
49 | ) | ||
50 | .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }), | ||
51 | ) | ||
52 | } | ||
53 | |||
54 | fn followed_by_comment(comment: &ast::Comment) -> bool { | ||
55 | let ws = match comment.syntax().next_token().and_then(ast::Whitespace::cast) { | ||
56 | Some(it) => it, | ||
57 | None => return false, | ||
58 | }; | ||
59 | if ws.spans_multiple_lines() { | ||
60 | return false; | ||
61 | } | ||
62 | ws.syntax().next_token().and_then(ast::Comment::cast).is_some() | ||
63 | } | ||
64 | |||
65 | fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option<SmolStr> { | ||
66 | let ws = match file.syntax().token_at_offset(token.text_range().start()) { | ||
67 | TokenAtOffset::Between(l, r) => { | ||
68 | assert!(r == *token); | ||
69 | l | ||
70 | } | ||
71 | TokenAtOffset::Single(n) => { | ||
72 | assert!(n == *token); | ||
73 | return Some("".into()); | ||
74 | } | ||
75 | TokenAtOffset::None => unreachable!(), | ||
76 | }; | ||
77 | if ws.kind() != WHITESPACE { | ||
78 | return None; | ||
79 | } | ||
80 | let text = ws.text(); | ||
81 | let pos = text.rfind('\n').map(|it| it + 1).unwrap_or(0); | ||
82 | Some(text[pos..].into()) | ||
83 | } | ||
84 | |||
85 | #[cfg(test)] | ||
86 | mod tests { | ||
87 | use test_utils::{add_cursor, assert_eq_text, extract_offset}; | ||
88 | |||
89 | use crate::mock_analysis::single_file; | ||
90 | |||
91 | use super::*; | ||
92 | |||
93 | fn apply_on_enter(before: &str) -> Option<String> { | ||
94 | let (offset, before) = extract_offset(before); | ||
95 | let (analysis, file_id) = single_file(&before); | ||
96 | let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; | ||
97 | |||
98 | assert_eq!(result.source_file_edits.len(), 1); | ||
99 | let actual = result.source_file_edits[0].edit.apply(&before); | ||
100 | let actual = add_cursor(&actual, result.cursor_position.unwrap().offset); | ||
101 | Some(actual) | ||
102 | } | ||
103 | |||
104 | fn do_check(ra_fixture_before: &str, ra_fixture_after: &str) { | ||
105 | let actual = apply_on_enter(ra_fixture_before).unwrap(); | ||
106 | assert_eq_text!(ra_fixture_after, &actual); | ||
107 | } | ||
108 | |||
109 | fn do_check_noop(ra_fixture_text: &str) { | ||
110 | assert!(apply_on_enter(ra_fixture_text).is_none()) | ||
111 | } | ||
112 | |||
113 | #[test] | ||
114 | fn continues_doc_comment() { | ||
115 | do_check( | ||
116 | r" | ||
117 | /// Some docs<|> | ||
118 | fn foo() { | ||
119 | } | ||
120 | ", | ||
121 | r" | ||
122 | /// Some docs | ||
123 | /// <|> | ||
124 | fn foo() { | ||
125 | } | ||
126 | ", | ||
127 | ); | ||
128 | |||
129 | do_check( | ||
130 | r" | ||
131 | impl S { | ||
132 | /// Some<|> docs. | ||
133 | fn foo() {} | ||
134 | } | ||
135 | ", | ||
136 | r" | ||
137 | impl S { | ||
138 | /// Some | ||
139 | /// <|> docs. | ||
140 | fn foo() {} | ||
141 | } | ||
142 | ", | ||
143 | ); | ||
144 | |||
145 | do_check( | ||
146 | r" | ||
147 | ///<|> Some docs | ||
148 | fn foo() { | ||
149 | } | ||
150 | ", | ||
151 | r" | ||
152 | /// | ||
153 | /// <|> Some docs | ||
154 | fn foo() { | ||
155 | } | ||
156 | ", | ||
157 | ); | ||
158 | } | ||
159 | |||
160 | #[test] | ||
161 | fn does_not_continue_before_doc_comment() { | ||
162 | do_check_noop(r"<|>//! docz"); | ||
163 | } | ||
164 | |||
165 | #[test] | ||
166 | fn continues_code_comment_in_the_middle_of_line() { | ||
167 | do_check( | ||
168 | r" | ||
169 | fn main() { | ||
170 | // Fix<|> me | ||
171 | let x = 1 + 1; | ||
172 | } | ||
173 | ", | ||
174 | r" | ||
175 | fn main() { | ||
176 | // Fix | ||
177 | // <|> me | ||
178 | let x = 1 + 1; | ||
179 | } | ||
180 | ", | ||
181 | ); | ||
182 | } | ||
183 | |||
184 | #[test] | ||
185 | fn continues_code_comment_in_the_middle_several_lines() { | ||
186 | do_check( | ||
187 | r" | ||
188 | fn main() { | ||
189 | // Fix<|> | ||
190 | // me | ||
191 | let x = 1 + 1; | ||
192 | } | ||
193 | ", | ||
194 | r" | ||
195 | fn main() { | ||
196 | // Fix | ||
197 | // <|> | ||
198 | // me | ||
199 | let x = 1 + 1; | ||
200 | } | ||
201 | ", | ||
202 | ); | ||
203 | } | ||
204 | |||
205 | #[test] | ||
206 | fn does_not_continue_end_of_code_comment() { | ||
207 | do_check_noop( | ||
208 | r" | ||
209 | fn main() { | ||
210 | // Fix me<|> | ||
211 | let x = 1 + 1; | ||
212 | } | ||
213 | ", | ||
214 | ); | ||
215 | } | ||
216 | } | ||