aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src')
-rw-r--r--crates/ra_ide/src/call_info.rs14
-rw-r--r--crates/ra_ide/src/completion.rs29
-rw-r--r--crates/ra_ide/src/completion/complete_dot.rs65
-rw-r--r--crates/ra_ide/src/completion/complete_fn_param.rs2
-rw-r--r--crates/ra_ide/src/completion/complete_keyword.rs2
-rw-r--r--crates/ra_ide/src/completion/complete_macro_in_item_position.rs3
-rw-r--r--crates/ra_ide/src/completion/complete_path.rs72
-rw-r--r--crates/ra_ide/src/completion/complete_pattern.rs2
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs4
-rw-r--r--crates/ra_ide/src/completion/complete_record_literal.rs2
-rw-r--r--crates/ra_ide/src/completion/complete_record_pattern.rs2
-rw-r--r--crates/ra_ide/src/completion/complete_scope.rs42
-rw-r--r--crates/ra_ide/src/completion/complete_snippet.rs2
-rw-r--r--crates/ra_ide/src/completion/complete_trait_impl.rs146
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs5
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs22
-rw-r--r--crates/ra_ide/src/completion/presentation.rs86
-rw-r--r--crates/ra_ide/src/completion/test_utils.rs29
-rw-r--r--crates/ra_ide/src/display.rs37
-rw-r--r--crates/ra_ide/src/display/function_signature.rs4
-rw-r--r--crates/ra_ide/src/goto_definition.rs15
-rw-r--r--crates/ra_ide/src/hover.rs89
-rw-r--r--crates/ra_ide/src/inlay_hints.rs113
-rw-r--r--crates/ra_ide/src/lib.rs33
-rw-r--r--crates/ra_ide/src/mock_analysis.rs2
-rw-r--r--crates/ra_ide/src/parent_module.rs1
-rw-r--r--crates/ra_ide/src/references/rename.rs208
-rw-r--r--crates/ra_ide/src/syntax_tree.rs16
-rw-r--r--crates/ra_ide/src/typing.rs200
-rw-r--r--crates/ra_ide/src/typing/on_enter.rs216
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#"
550struct TS<T>(T);
551fn 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;
16mod complete_postfix; 16mod complete_postfix;
17mod complete_macro_in_item_position; 17mod complete_macro_in_item_position;
18mod complete_trait_impl; 18mod complete_trait_impl;
19#[cfg(test)]
20mod test_utils;
19 21
20use ra_ide_db::RootDatabase; 22use ra_ide_db::RootDatabase;
21 23
22#[cfg(test)]
23use crate::completion::completion_item::do_completion;
24use crate::{ 24use 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)]
37pub struct CompletionOptions {
38 pub enable_postfix_completions: bool,
39 pub add_call_parenthesis: bool,
40 pub add_call_argument_snippets: bool,
41}
42
43impl 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).
58pub(crate) fn completions(db: &RootDatabase, position: FilePosition) -> Option<Completions> { 75pub(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)]
72mod tests { 72mod 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)]
54mod tests { 54mod 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)]
119mod tests { 119mod 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)]
17mod tests { 17mod 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) {
103mod tests { 103mod 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)]
29mod tests { 29mod 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
14pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { 14pub(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:
81mod tests { 81mod 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)]
20mod tests { 20mod 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)]
19mod tests { 19mod 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) {
14mod tests { 14mod 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)]
44mod tests { 44mod 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 @@
34use hir::{self, Docs, HasSource}; 34use hir::{self, Docs, HasSource};
35use ra_assists::utils::get_missing_impl_items; 35use ra_assists::utils::get_missing_impl_items;
36use ra_syntax::{ 36use ra_syntax::{
37 ast::{self, edit}, 37 ast::{self, edit, ImplDef},
38 AstNode, SyntaxKind, SyntaxNode, TextRange, 38 AstNode, SyntaxKind, SyntaxNode, TextRange,
39}; 39};
40use ra_text_edit::TextEdit; 40use ra_text_edit::TextEdit;
@@ -47,22 +47,22 @@ use crate::{
47}; 47};
48 48
49pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { 49pub(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
104fn 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
104fn add_function_impl( 119fn 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)]
204mod tests { 219mod 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};
12use ra_text_edit::AtomTextEdit; 12use ra_text_edit::AtomTextEdit;
13 13
14use crate::FilePosition; 14use 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;
19pub(crate) struct CompletionContext<'a> { 19pub(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)]
323pub(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
3use hir::{db::HirDatabase, Docs, HasAttrs, HasSource, HirDisplay, ScopeDef, StructKind, Type}; 3use hir::{Docs, HasAttrs, HasSource, HirDisplay, ScopeDef, StructKind, Type};
4use join_to_string::join; 4use join_to_string::join;
5use ra_syntax::ast::NameOwner; 5use ra_syntax::ast::NameOwner;
6use test_utils::tested_by; 6use 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
12use crate::display::{const_label, macro_label, type_label, FunctionSignature}; 12use crate::{
13 display::{const_label, macro_label, type_label, FunctionSignature},
14 RootDatabase,
15};
13 16
14impl Completions { 17impl 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
311fn is_deprecated(node: impl HasAttrs, db: &impl HirDatabase) -> bool { 306fn 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
3use crate::{
4 completion::{completion_item::CompletionKind, CompletionOptions},
5 mock_analysis::{analysis_and_position, single_file_with_position},
6 CompletionItem,
7};
8
9pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
10 do_completion_with_options(code, kind, &CompletionOptions::default())
11}
12
13pub(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;
6mod structure; 6mod structure;
7mod short_label; 7mod short_label;
8 8
9use std::fmt::{Display, Write};
10
9use ra_syntax::{ 11use 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
70pub(crate) fn rust_code_markup<CODE: AsRef<str>>(val: CODE) -> String { 72pub(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
74pub(crate) fn rust_code_markup_with_doc<CODE, DOC>( 76pub(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 {
79where 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
3use hir::{ 4use hir::{
4 Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, 5 Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef,
@@ -24,35 +25,20 @@ use itertools::Itertools;
24use std::iter::once; 25use 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)]
28pub struct HoverResult { 29pub struct HoverResult {
29 results: Vec<String>, 30 results: Vec<String>,
30 exact: bool,
31}
32
33impl Default for HoverResult {
34 fn default() -> Self {
35 HoverResult::new()
36 }
37} 31}
38 32
39impl HoverResult { 33impl 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
139fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<String> { 112fn 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
11use crate::{FileId, FunctionSignature}; 11use crate::{FileId, FunctionSignature};
12 12
13#[derive(Debug, PartialEq, Eq)] 13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct InlayHintsOptions {
15 pub type_hints: bool,
16 pub parameter_hints: bool,
17 pub max_length: Option<usize>,
18}
19
20impl 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)]
14pub enum InlayKind { 27pub enum InlayKind {
15 TypeHint, 28 TypeHint,
16 ParameterHint, 29 ParameterHint,
@@ -26,7 +39,7 @@ pub struct InlayHint {
26pub(crate) fn inlay_hints( 39pub(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(
49fn get_param_name_hints( 62fn 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(
84fn get_bind_pat_hints( 102fn 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)]
204mod tests { 226mod 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;
62pub use crate::{ 62pub 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};
85pub use ra_ide_db::{ 85pub 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
132impl Default for AnalysisHost { 131impl 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
138impl AnalysisHost { 137impl 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::{
9use ra_text_edit::TextEdit; 9use ra_text_edit::TextEdit;
10 10
11use crate::{ 11use crate::{
12 FileId, FilePosition, FileSystemEdit, RangeInfo, SourceChange, SourceFileEdit, TextRange, 12 FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, SourceChange,
13 SourceFileEdit, TextRange,
13}; 14};
14 15
15use super::find_all_refs; 16use 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
49fn source_edit_from_file_id_range( 50fn 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
57fn rename_mod( 75fn 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;
5use ra_syntax::{ 5use 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
11pub use ra_db::FileId; 11pub 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
16mod on_enter;
17
16use ra_db::{FilePosition, SourceDatabase}; 18use ra_db::{FilePosition, SourceDatabase};
17use ra_fmt::leading_indent; 19use ra_fmt::leading_indent;
18use ra_ide_db::RootDatabase; 20use ra_ide_db::RootDatabase;
19use ra_syntax::{ 21use 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};
26use ra_text_edit::TextEdit; 26use ra_text_edit::TextEdit;
27 27
28use crate::{source_change::SingleFileChange, SourceChange, SourceFileEdit}; 28use crate::{source_change::SingleFileChange, SourceChange};
29
30pub(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( 30pub(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
68fn 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
88pub(crate) const TRIGGER_CHARS: &str = ".=>"; 32pub(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)]
198mod tests { 142mod 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<|>
230fn foo() {
231}
232",
233 r"
234/// Some docs
235/// <|>
236fn foo() {
237}
238",
239 );
240 do_check(
241 r"
242impl S {
243 /// Some<|> docs.
244 fn foo() {}
245}
246",
247 r"
248impl S {
249 /// Some
250 /// <|> docs.
251 fn foo() {}
252}
253",
254 );
255 do_check(
256 r"
257fn main() {
258 // Fix<|> me
259 let x = 1 + 1;
260}
261",
262 r"
263fn main() {
264 // Fix
265 // <|> me
266 let x = 1 + 1;
267}
268",
269 );
270 do_check(
271 r"
272///<|> Some docs
273fn foo() {
274}
275",
276 r"
277///
278/// <|> Some docs
279fn foo() {
280}
281",
282 );
283 do_check_noop(
284 r"
285fn 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
4use ra_db::{FilePosition, SourceDatabase};
5use ra_ide_db::RootDatabase;
6use ra_syntax::{
7 ast::{self, AstToken},
8 AstNode, SmolStr, SourceFile,
9 SyntaxKind::*,
10 SyntaxToken, TextUnit, TokenAtOffset,
11};
12use ra_text_edit::TextEdit;
13
14use crate::{SourceChange, SourceFileEdit};
15
16pub(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
54fn 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
65fn 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)]
86mod 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<|>
118fn foo() {
119}
120",
121 r"
122/// Some docs
123/// <|>
124fn foo() {
125}
126",
127 );
128
129 do_check(
130 r"
131impl S {
132 /// Some<|> docs.
133 fn foo() {}
134}
135",
136 r"
137impl S {
138 /// Some
139 /// <|> docs.
140 fn foo() {}
141}
142",
143 );
144
145 do_check(
146 r"
147///<|> Some docs
148fn foo() {
149}
150",
151 r"
152///
153/// <|> Some docs
154fn 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"
169fn main() {
170 // Fix<|> me
171 let x = 1 + 1;
172}
173",
174 r"
175fn 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"
188fn main() {
189 // Fix<|>
190 // me
191 let x = 1 + 1;
192}
193",
194 r"
195fn 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"
209fn main() {
210 // Fix me<|>
211 let x = 1 + 1;
212}
213",
214 );
215 }
216}