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/completion.rs29
-rw-r--r--crates/ra_ide/src/completion/complete_dot.rs173
-rw-r--r--crates/ra_ide/src/completion/complete_fn_param.rs2
-rw-r--r--crates/ra_ide/src/completion/complete_keyword.rs3
-rw-r--r--crates/ra_ide/src/completion/complete_macro_in_item_position.rs3
-rw-r--r--crates/ra_ide/src/completion/complete_path.rs144
-rw-r--r--crates/ra_ide/src/completion/complete_pattern.rs20
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs69
-rw-r--r--crates/ra_ide/src/completion/complete_record_literal.rs27
-rw-r--r--crates/ra_ide/src/completion/complete_record_pattern.rs30
-rw-r--r--crates/ra_ide/src/completion/complete_scope.rs76
-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.rs116
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs34
-rw-r--r--crates/ra_ide/src/completion/presentation.rs76
-rw-r--r--crates/ra_ide/src/completion/test_utils.rs29
-rw-r--r--crates/ra_ide/src/display.rs14
-rw-r--r--crates/ra_ide/src/hover.rs114
-rw-r--r--crates/ra_ide/src/inlay_hints.rs32
-rw-r--r--crates/ra_ide/src/lib.rs39
-rw-r--r--crates/ra_ide/src/mock_analysis.rs12
-rw-r--r--crates/ra_ide/src/parent_module.rs2
-rw-r--r--crates/ra_ide/src/references/rename.rs208
-rw-r--r--crates/ra_ide/src/typing.rs160
-rw-r--r--crates/ra_ide/src/typing/on_enter.rs216
26 files changed, 1435 insertions, 341 deletions
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs
index c378c2c62..93e53c921 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 opts: &CompletionOptions,
79) -> Option<Completions> {
80 let ctx = CompletionContext::new(db, position, opts)?;
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 9145aa183..81e5037aa 100644
--- a/crates/ra_ide/src/completion/complete_dot.rs
+++ b/crates/ra_ide/src/completion/complete_dot.rs
@@ -38,7 +38,7 @@ pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
38fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { 38fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
39 for receiver in receiver.autoderef(ctx.db) { 39 for receiver in receiver.autoderef(ctx.db) {
40 for (field, ty) in receiver.fields(ctx.db) { 40 for (field, ty) in receiver.fields(ctx.db) {
41 if ctx.module.map_or(false, |m| !field.is_visible_from(ctx.db, m)) { 41 if ctx.scope().module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) {
42 // Skip private field. FIXME: If the definition location of the 42 // Skip private field. FIXME: If the definition location of the
43 // field is editable, we should show the completion 43 // field is editable, we should show the completion
44 continue; 44 continue;
@@ -53,11 +53,14 @@ fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Ty
53} 53}
54 54
55fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { 55fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
56 if let Some(krate) = ctx.module.map(|it| it.krate()) { 56 if let Some(krate) = ctx.krate {
57 let mut seen_methods = FxHashSet::default(); 57 let mut seen_methods = FxHashSet::default();
58 let traits_in_scope = ctx.scope().traits_in_scope(); 58 let traits_in_scope = ctx.scope().traits_in_scope();
59 receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| { 59 receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| {
60 if func.has_self_param(ctx.db) && seen_methods.insert(func.name(ctx.db)) { 60 if func.has_self_param(ctx.db)
61 && ctx.scope().module().map_or(true, |m| func.is_visible_from(ctx.db, m))
62 && seen_methods.insert(func.name(ctx.db))
63 {
61 acc.add_function(ctx, func); 64 acc.add_function(ctx, func);
62 } 65 }
63 None::<()> 66 None::<()>
@@ -67,7 +70,7 @@ fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &T
67 70
68#[cfg(test)] 71#[cfg(test)]
69mod tests { 72mod tests {
70 use crate::completion::{do_completion, CompletionItem, CompletionKind}; 73 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
71 use insta::assert_debug_snapshot; 74 use insta::assert_debug_snapshot;
72 75
73 fn do_ref_completion(code: &str) -> Vec<CompletionItem> { 76 fn do_ref_completion(code: &str) -> Vec<CompletionItem> {
@@ -308,6 +311,39 @@ mod tests {
308 } 311 }
309 312
310 #[test] 313 #[test]
314 fn test_method_completion_private() {
315 assert_debug_snapshot!(
316 do_ref_completion(
317 r"
318 struct A {}
319 mod m {
320 impl super::A {
321 fn private_method(&self) {}
322 pub(super) fn the_method(&self) {}
323 }
324 }
325 fn foo(a: A) {
326 a.<|>
327 }
328 ",
329 ),
330 @r###"
331 [
332 CompletionItem {
333 label: "the_method()",
334 source_range: [256; 256),
335 delete: [256; 256),
336 insert: "the_method()$0",
337 kind: Method,
338 lookup: "the_method",
339 detail: "pub(super) fn the_method(&self)",
340 },
341 ]
342 "###
343 );
344 }
345
346 #[test]
311 fn test_trait_method_completion() { 347 fn test_trait_method_completion() {
312 assert_debug_snapshot!( 348 assert_debug_snapshot!(
313 do_ref_completion( 349 do_ref_completion(
@@ -584,4 +620,133 @@ mod tests {
584 "### 620 "###
585 ); 621 );
586 } 622 }
623
624 #[test]
625 fn works_in_simple_macro_1() {
626 assert_debug_snapshot!(
627 do_ref_completion(
628 r"
629 macro_rules! m { ($e:expr) => { $e } }
630 struct A { the_field: u32 }
631 fn foo(a: A) {
632 m!(a.x<|>)
633 }
634 ",
635 ),
636 @r###"
637 [
638 CompletionItem {
639 label: "the_field",
640 source_range: [156; 157),
641 delete: [156; 157),
642 insert: "the_field",
643 kind: Field,
644 detail: "u32",
645 },
646 ]
647 "###
648 );
649 }
650
651 #[test]
652 fn works_in_simple_macro_recursive() {
653 assert_debug_snapshot!(
654 do_ref_completion(
655 r"
656 macro_rules! m { ($e:expr) => { $e } }
657 struct A { the_field: u32 }
658 fn foo(a: A) {
659 m!(a.x<|>)
660 }
661 ",
662 ),
663 @r###"
664 [
665 CompletionItem {
666 label: "the_field",
667 source_range: [156; 157),
668 delete: [156; 157),
669 insert: "the_field",
670 kind: Field,
671 detail: "u32",
672 },
673 ]
674 "###
675 );
676 }
677
678 #[test]
679 fn works_in_simple_macro_2() {
680 // this doesn't work yet because the macro doesn't expand without the token -- maybe it can be fixed with better recovery
681 assert_debug_snapshot!(
682 do_ref_completion(
683 r"
684 macro_rules! m { ($e:expr) => { $e } }
685 struct A { the_field: u32 }
686 fn foo(a: A) {
687 m!(a.<|>)
688 }
689 ",
690 ),
691 @r###"[]"###
692 );
693 }
694
695 #[test]
696 fn works_in_simple_macro_recursive_1() {
697 assert_debug_snapshot!(
698 do_ref_completion(
699 r"
700 macro_rules! m { ($e:expr) => { $e } }
701 struct A { the_field: u32 }
702 fn foo(a: A) {
703 m!(m!(m!(a.x<|>)))
704 }
705 ",
706 ),
707 @r###"
708 [
709 CompletionItem {
710 label: "the_field",
711 source_range: [162; 163),
712 delete: [162; 163),
713 insert: "the_field",
714 kind: Field,
715 detail: "u32",
716 },
717 ]
718 "###
719 );
720 }
721
722 #[test]
723 fn test_method_completion_3547() {
724 assert_debug_snapshot!(
725 do_ref_completion(
726 r"
727 struct HashSet<T> {}
728 impl<T> HashSet<T> {
729 pub fn the_method(&self) {}
730 }
731 fn foo() {
732 let s: HashSet<_>;
733 s.<|>
734 }
735 ",
736 ),
737 @r###"
738 [
739 CompletionItem {
740 label: "the_method()",
741 source_range: [201; 201),
742 delete: [201; 201),
743 insert: "the_method()$0",
744 kind: Method,
745 lookup: "the_method",
746 detail: "pub fn the_method(&self)",
747 },
748 ]
749 "###
750 );
751 }
587} 752}
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 eb7cd9ac2..1e053ea4a 100644
--- a/crates/ra_ide/src/completion/complete_keyword.rs
+++ b/crates/ra_ide/src/completion/complete_keyword.rs
@@ -79,6 +79,7 @@ pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
79} 79}
80 80
81fn is_in_loop_body(leaf: &SyntaxToken) -> bool { 81fn is_in_loop_body(leaf: &SyntaxToken) -> bool {
82 // FIXME move this to CompletionContext and make it handle macros
82 for node in leaf.parent().ancestors() { 83 for node in leaf.parent().ancestors() {
83 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { 84 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
84 break; 85 break;
@@ -116,7 +117,7 @@ fn complete_return(
116 117
117#[cfg(test)] 118#[cfg(test)]
118mod tests { 119mod tests {
119 use crate::completion::{do_completion, CompletionItem, CompletionKind}; 120 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
120 use insta::assert_debug_snapshot; 121 use insta::assert_debug_snapshot;
121 122
122 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 1a9699466..d588ee364 100644
--- a/crates/ra_ide/src/completion/complete_path.rs
+++ b/crates/ra_ide/src/completion/complete_path.rs
@@ -1,6 +1,6 @@
1//! Completion of paths, including when writing a single name. 1//! Completion of paths, i.e. `some::prefix::<|>`.
2 2
3use hir::{Adt, PathResolution, ScopeDef}; 3use hir::{Adt, HasVisibility, PathResolution, ScopeDef};
4use ra_syntax::AstNode; 4use ra_syntax::AstNode;
5use test_utils::tested_by; 5use test_utils::tested_by;
6 6
@@ -15,9 +15,10 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
15 Some(PathResolution::Def(def)) => def, 15 Some(PathResolution::Def(def)) => def,
16 _ => return, 16 _ => return,
17 }; 17 };
18 let context_module = ctx.scope().module();
18 match def { 19 match def {
19 hir::ModuleDef::Module(module) => { 20 hir::ModuleDef::Module(module) => {
20 let module_scope = module.scope(ctx.db); 21 let module_scope = module.scope(ctx.db, context_module);
21 for (name, def) in module_scope { 22 for (name, def) in module_scope {
22 if ctx.use_item_syntax.is_some() { 23 if ctx.use_item_syntax.is_some() {
23 if let ScopeDef::Unknown = def { 24 if let ScopeDef::Unknown = def {
@@ -47,10 +48,13 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
47 }; 48 };
48 // Iterate assoc types separately 49 // Iterate assoc types separately
49 // FIXME: complete T::AssocType 50 // FIXME: complete T::AssocType
50 let krate = ctx.module.map(|m| m.krate()); 51 let krate = ctx.krate;
51 if let Some(krate) = krate { 52 if let Some(krate) = krate {
52 let traits_in_scope = ctx.scope().traits_in_scope(); 53 let traits_in_scope = ctx.scope().traits_in_scope();
53 ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| { 54 ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| {
55 if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
56 return None;
57 }
54 match item { 58 match item {
55 hir::AssocItem::Function(func) => { 59 hir::AssocItem::Function(func) => {
56 if !func.has_self_param(ctx.db) { 60 if !func.has_self_param(ctx.db) {
@@ -64,6 +68,9 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
64 }); 68 });
65 69
66 ty.iterate_impl_items(ctx.db, krate, |item| { 70 ty.iterate_impl_items(ctx.db, krate, |item| {
71 if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
72 return None;
73 }
67 match item { 74 match item {
68 hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => {} 75 hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => {}
69 hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), 76 hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
@@ -74,6 +81,9 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
74 } 81 }
75 hir::ModuleDef::Trait(t) => { 82 hir::ModuleDef::Trait(t) => {
76 for item in t.items(ctx.db) { 83 for item in t.items(ctx.db) {
84 if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
85 continue;
86 }
77 match item { 87 match item {
78 hir::AssocItem::Function(func) => { 88 hir::AssocItem::Function(func) => {
79 if !func.has_self_param(ctx.db) { 89 if !func.has_self_param(ctx.db) {
@@ -93,7 +103,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
93mod tests { 103mod tests {
94 use test_utils::covers; 104 use test_utils::covers;
95 105
96 use crate::completion::{do_completion, CompletionItem, CompletionKind}; 106 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
97 use insta::assert_debug_snapshot; 107 use insta::assert_debug_snapshot;
98 108
99 fn do_reference_completion(code: &str) -> Vec<CompletionItem> { 109 fn do_reference_completion(code: &str) -> Vec<CompletionItem> {
@@ -170,6 +180,41 @@ mod tests {
170 } 180 }
171 181
172 #[test] 182 #[test]
183 fn path_visibility() {
184 assert_debug_snapshot!(
185 do_reference_completion(
186 r"
187 use self::my::<|>;
188
189 mod my {
190 struct Bar;
191 pub struct Foo;
192 pub use Bar as PublicBar;
193 }
194 "
195 ),
196 @r###"
197 [
198 CompletionItem {
199 label: "Foo",
200 source_range: [31; 31),
201 delete: [31; 31),
202 insert: "Foo",
203 kind: Struct,
204 },
205 CompletionItem {
206 label: "PublicBar",
207 source_range: [31; 31),
208 delete: [31; 31),
209 insert: "PublicBar",
210 kind: Struct,
211 },
212 ]
213 "###
214 );
215 }
216
217 #[test]
173 fn completes_use_item_starting_with_self() { 218 fn completes_use_item_starting_with_self() {
174 assert_debug_snapshot!( 219 assert_debug_snapshot!(
175 do_reference_completion( 220 do_reference_completion(
@@ -177,7 +222,7 @@ mod tests {
177 use self::m::<|>; 222 use self::m::<|>;
178 223
179 mod m { 224 mod m {
180 struct Bar; 225 pub struct Bar;
181 } 226 }
182 " 227 "
183 ), 228 ),
@@ -502,6 +547,60 @@ mod tests {
502 } 547 }
503 548
504 #[test] 549 #[test]
550 fn associated_item_visibility() {
551 assert_debug_snapshot!(
552 do_reference_completion(
553 "
554 //- /lib.rs
555 struct S;
556
557 mod m {
558 impl super::S {
559 pub(super) fn public_method() { }
560 fn private_method() { }
561 pub(super) type PublicType = u32;
562 type PrivateType = u32;
563 pub(super) const PUBLIC_CONST: u32 = 1;
564 const PRIVATE_CONST: u32 = 1;
565 }
566 }
567
568 fn foo() { let _ = S::<|> }
569 "
570 ),
571 @r###"
572 [
573 CompletionItem {
574 label: "PUBLIC_CONST",
575 source_range: [302; 302),
576 delete: [302; 302),
577 insert: "PUBLIC_CONST",
578 kind: Const,
579 detail: "pub(super) const PUBLIC_CONST: u32 = 1;",
580 },
581 CompletionItem {
582 label: "PublicType",
583 source_range: [302; 302),
584 delete: [302; 302),
585 insert: "PublicType",
586 kind: TypeAlias,
587 detail: "pub(super) type PublicType = u32;",
588 },
589 CompletionItem {
590 label: "public_method()",
591 source_range: [302; 302),
592 delete: [302; 302),
593 insert: "public_method()$0",
594 kind: Function,
595 lookup: "public_method",
596 detail: "pub(super) fn public_method()",
597 },
598 ]
599 "###
600 );
601 }
602
603 #[test]
505 fn completes_enum_associated_method() { 604 fn completes_enum_associated_method() {
506 assert_debug_snapshot!( 605 assert_debug_snapshot!(
507 do_reference_completion( 606 do_reference_completion(
@@ -835,4 +934,37 @@ mod tests {
835 "### 934 "###
836 ); 935 );
837 } 936 }
937
938 #[test]
939 fn completes_in_simple_macro_call() {
940 let completions = do_reference_completion(
941 r#"
942 macro_rules! m { ($e:expr) => { $e } }
943 fn main() { m!(self::f<|>); }
944 fn foo() {}
945 "#,
946 );
947 assert_debug_snapshot!(completions, @r###"
948 [
949 CompletionItem {
950 label: "foo()",
951 source_range: [93; 94),
952 delete: [93; 94),
953 insert: "foo()$0",
954 kind: Function,
955 lookup: "foo",
956 detail: "fn foo()",
957 },
958 CompletionItem {
959 label: "main()",
960 source_range: [93; 94),
961 delete: [93; 94),
962 insert: "main()$0",
963 kind: Function,
964 lookup: "main",
965 detail: "fn main()",
966 },
967 ]
968 "###);
969 }
838} 970}
diff --git a/crates/ra_ide/src/completion/complete_pattern.rs b/crates/ra_ide/src/completion/complete_pattern.rs
index c2c6ca002..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> {
@@ -86,4 +86,22 @@ mod tests {
86 ] 86 ]
87 "###); 87 "###);
88 } 88 }
89
90 #[test]
91 fn completes_in_simple_macro_call() {
92 // FIXME: doesn't work yet because of missing error recovery in macro expansion
93 let completions = complete(
94 r"
95 macro_rules! m { ($e:expr) => { $e } }
96 enum E { X }
97
98 fn foo() {
99 m!(match E::X {
100 <|>
101 })
102 }
103 ",
104 );
105 assert_debug_snapshot!(completions, @r###"[]"###);
106 }
89} 107}
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs
index 8a74f993a..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
@@ -67,8 +67,8 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
67 67
68fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder { 68fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder {
69 let edit = { 69 let edit = {
70 let receiver_range = 70 let receiver_syntax = ctx.dot_receiver.as_ref().expect("no receiver available").syntax();
71 ctx.dot_receiver.as_ref().expect("no receiver available").syntax().text_range(); 71 let receiver_range = ctx.sema.original_range(receiver_syntax).range;
72 let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end()); 72 let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end());
73 TextEdit::replace(delete_range, snippet.to_string()) 73 TextEdit::replace(delete_range, snippet.to_string())
74 }; 74 };
@@ -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)
@@ -279,4 +279,65 @@ mod tests {
279 "### 279 "###
280 ); 280 );
281 } 281 }
282
283 #[test]
284 fn works_in_simple_macro() {
285 assert_debug_snapshot!(
286 do_postfix_completion(
287 r#"
288 macro_rules! m { ($e:expr) => { $e } }
289 fn main() {
290 let bar: u8 = 12;
291 m!(bar.b<|>)
292 }
293 "#,
294 ),
295 @r###"
296 [
297 CompletionItem {
298 label: "box",
299 source_range: [149; 150),
300 delete: [145; 150),
301 insert: "Box::new(bar)",
302 detail: "Box::new(expr)",
303 },
304 CompletionItem {
305 label: "dbg",
306 source_range: [149; 150),
307 delete: [145; 150),
308 insert: "dbg!(bar)",
309 detail: "dbg!(expr)",
310 },
311 CompletionItem {
312 label: "match",
313 source_range: [149; 150),
314 delete: [145; 150),
315 insert: "match bar {\n ${1:_} => {$0\\},\n}",
316 detail: "match expr {}",
317 },
318 CompletionItem {
319 label: "not",
320 source_range: [149; 150),
321 delete: [145; 150),
322 insert: "!bar",
323 detail: "!expr",
324 },
325 CompletionItem {
326 label: "ref",
327 source_range: [149; 150),
328 delete: [145; 150),
329 insert: "&bar",
330 detail: "&expr",
331 },
332 CompletionItem {
333 label: "refm",
334 source_range: [149; 150),
335 delete: [145; 150),
336 insert: "&mut bar",
337 detail: "&mut expr",
338 },
339 ]
340 "###
341 );
342 }
282} 343}
diff --git a/crates/ra_ide/src/completion/complete_record_literal.rs b/crates/ra_ide/src/completion/complete_record_literal.rs
index f98353d76..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> {
@@ -153,4 +153,29 @@ mod tests {
153 ] 153 ]
154 "###); 154 "###);
155 } 155 }
156
157 #[test]
158 fn test_record_literal_field_in_simple_macro() {
159 let completions = complete(
160 r"
161 macro_rules! m { ($e:expr) => { $e } }
162 struct A { the_field: u32 }
163 fn foo() {
164 m!(A { the<|> })
165 }
166 ",
167 );
168 assert_debug_snapshot!(completions, @r###"
169 [
170 CompletionItem {
171 label: "the_field",
172 source_range: [137; 140),
173 delete: [137; 140),
174 insert: "the_field",
175 kind: Field,
176 detail: "u32",
177 },
178 ]
179 "###);
180 }
156} 181}
diff --git a/crates/ra_ide/src/completion/complete_record_pattern.rs b/crates/ra_ide/src/completion/complete_record_pattern.rs
index 9bdeae49f..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> {
@@ -87,4 +87,32 @@ mod tests {
87 ] 87 ]
88 "###); 88 "###);
89 } 89 }
90
91 #[test]
92 fn test_record_pattern_field_in_simple_macro() {
93 let completions = complete(
94 r"
95 macro_rules! m { ($e:expr) => { $e } }
96 struct S { foo: u32 }
97
98 fn process(f: S) {
99 m!(match f {
100 S { f<|>: 92 } => (),
101 })
102 }
103 ",
104 );
105 assert_debug_snapshot!(completions, @r###"
106 [
107 CompletionItem {
108 label: "foo",
109 source_range: [171; 172),
110 delete: [171; 172),
111 insert: "foo",
112 kind: Field,
113 detail: "u32",
114 },
115 ]
116 "###);
117 }
90} 118}
diff --git a/crates/ra_ide/src/completion/complete_scope.rs b/crates/ra_ide/src/completion/complete_scope.rs
index 2b9a0e556..bd4adf23a 100644
--- a/crates/ra_ide/src/completion/complete_scope.rs
+++ b/crates/ra_ide/src/completion/complete_scope.rs
@@ -1,4 +1,4 @@
1//! FIXME: write short doc here 1//! Completion of names from the current scope, e.g. locals and imported items.
2 2
3use crate::completion::{CompletionContext, Completions}; 3use crate::completion::{CompletionContext, Completions};
4 4
@@ -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]
@@ -797,4 +797,72 @@ mod tests {
797 "### 797 "###
798 ) 798 )
799 } 799 }
800
801 #[test]
802 fn completes_in_simple_macro_1() {
803 assert_debug_snapshot!(
804 do_reference_completion(
805 r"
806 macro_rules! m { ($e:expr) => { $e } }
807 fn quux(x: i32) {
808 let y = 92;
809 m!(<|>);
810 }
811 "
812 ),
813 @"[]"
814 );
815 }
816
817 #[test]
818 fn completes_in_simple_macro_2() {
819 assert_debug_snapshot!(
820 do_reference_completion(
821 r"
822 macro_rules! m { ($e:expr) => { $e } }
823 fn quux(x: i32) {
824 let y = 92;
825 m!(x<|>);
826 }
827 "
828 ),
829 @r###"
830 [
831 CompletionItem {
832 label: "m!",
833 source_range: [145; 146),
834 delete: [145; 146),
835 insert: "m!($0)",
836 kind: Macro,
837 detail: "macro_rules! m",
838 },
839 CompletionItem {
840 label: "quux(…)",
841 source_range: [145; 146),
842 delete: [145; 146),
843 insert: "quux(${1:x})$0",
844 kind: Function,
845 lookup: "quux",
846 detail: "fn quux(x: i32)",
847 },
848 CompletionItem {
849 label: "x",
850 source_range: [145; 146),
851 delete: [145; 146),
852 insert: "x",
853 kind: Binding,
854 detail: "i32",
855 },
856 CompletionItem {
857 label: "y",
858 source_range: [145; 146),
859 delete: [145; 146),
860 insert: "y",
861 kind: Binding,
862 detail: "i32",
863 },
864 ]
865 "###
866 );
867 }
800} 868}
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 9aa5a705d..3646fb8dc 100644
--- a/crates/ra_ide/src/completion/completion_context.rs
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -5,13 +5,13 @@ use ra_db::SourceDatabase;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{
7 algo::{find_covering_element, find_node_at_offset}, 7 algo::{find_covering_element, find_node_at_offset},
8 ast, AstNode, SourceFile, 8 ast, AstNode,
9 SyntaxKind::*, 9 SyntaxKind::*,
10 SyntaxNode, SyntaxToken, TextRange, TextUnit, 10 SyntaxNode, SyntaxToken, TextRange, TextUnit,
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,9 +19,13 @@ 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,
24 /// The token before the cursor, in the original file.
25 pub(super) original_token: SyntaxToken,
26 /// The token before the cursor, in the macro-expanded file.
23 pub(super) token: SyntaxToken, 27 pub(super) token: SyntaxToken,
24 pub(super) module: Option<hir::Module>, 28 pub(super) krate: Option<hir::Crate>,
25 pub(super) name_ref_syntax: Option<ast::NameRef>, 29 pub(super) name_ref_syntax: Option<ast::NameRef>,
26 pub(super) function_syntax: Option<ast::FnDef>, 30 pub(super) function_syntax: Option<ast::FnDef>,
27 pub(super) use_item_syntax: Option<ast::UseItem>, 31 pub(super) use_item_syntax: Option<ast::UseItem>,
@@ -54,6 +58,7 @@ impl<'a> CompletionContext<'a> {
54 pub(super) fn new( 58 pub(super) fn new(
55 db: &'a RootDatabase, 59 db: &'a RootDatabase,
56 position: FilePosition, 60 position: FilePosition,
61 options: &'a CompletionOptions,
57 ) -> Option<CompletionContext<'a>> { 62 ) -> Option<CompletionContext<'a>> {
58 let sema = Semantics::new(db); 63 let sema = Semantics::new(db);
59 64
@@ -67,15 +72,21 @@ impl<'a> CompletionContext<'a> {
67 let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); 72 let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string());
68 parse.reparse(&edit).tree() 73 parse.reparse(&edit).tree()
69 }; 74 };
75 let fake_ident_token =
76 file_with_fake_ident.syntax().token_at_offset(position.offset).right_biased().unwrap();
70 77
71 let module = sema.to_module_def(position.file_id); 78 let krate = sema.to_module_def(position.file_id).map(|m| m.krate());
72 let token = original_file.syntax().token_at_offset(position.offset).left_biased()?; 79 let original_token =
80 original_file.syntax().token_at_offset(position.offset).left_biased()?;
81 let token = sema.descend_into_macros(original_token.clone());
73 let mut ctx = CompletionContext { 82 let mut ctx = CompletionContext {
74 sema, 83 sema,
75 db, 84 db,
85 options,
86 original_token,
76 token, 87 token,
77 offset: position.offset, 88 offset: position.offset,
78 module, 89 krate,
79 name_ref_syntax: None, 90 name_ref_syntax: None,
80 function_syntax: None, 91 function_syntax: None,
81 use_item_syntax: None, 92 use_item_syntax: None,
@@ -95,15 +106,57 @@ impl<'a> CompletionContext<'a> {
95 has_type_args: false, 106 has_type_args: false,
96 dot_receiver_is_ambiguous_float_literal: false, 107 dot_receiver_is_ambiguous_float_literal: false,
97 }; 108 };
98 ctx.fill(&original_file, file_with_fake_ident, position.offset); 109
110 let mut original_file = original_file.syntax().clone();
111 let mut hypothetical_file = file_with_fake_ident.syntax().clone();
112 let mut offset = position.offset;
113 let mut fake_ident_token = fake_ident_token;
114
115 // Are we inside a macro call?
116 while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
117 find_node_at_offset::<ast::MacroCall>(&original_file, offset),
118 find_node_at_offset::<ast::MacroCall>(&hypothetical_file, offset),
119 ) {
120 if actual_macro_call.path().as_ref().map(|s| s.syntax().text())
121 != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text())
122 {
123 break;
124 }
125 let hypothetical_args = match macro_call_with_fake_ident.token_tree() {
126 Some(tt) => tt,
127 None => break,
128 };
129 if let (Some(actual_expansion), Some(hypothetical_expansion)) = (
130 ctx.sema.expand(&actual_macro_call),
131 ctx.sema.expand_hypothetical(
132 &actual_macro_call,
133 &hypothetical_args,
134 fake_ident_token,
135 ),
136 ) {
137 let new_offset = hypothetical_expansion.1.text_range().start();
138 if new_offset >= actual_expansion.text_range().end() {
139 break;
140 }
141 original_file = actual_expansion;
142 hypothetical_file = hypothetical_expansion.0;
143 fake_ident_token = hypothetical_expansion.1;
144 offset = new_offset;
145 } else {
146 break;
147 }
148 }
149
150 ctx.fill(&original_file, hypothetical_file, offset);
99 Some(ctx) 151 Some(ctx)
100 } 152 }
101 153
102 // The range of the identifier that is being completed. 154 // The range of the identifier that is being completed.
103 pub(crate) fn source_range(&self) -> TextRange { 155 pub(crate) fn source_range(&self) -> TextRange {
156 // check kind of macro-expanded token, but use range of original token
104 match self.token.kind() { 157 match self.token.kind() {
105 // workaroud when completion is triggered by trigger characters. 158 // workaroud when completion is triggered by trigger characters.
106 IDENT => self.token.text_range(), 159 IDENT => self.original_token.text_range(),
107 _ => TextRange::offset_len(self.offset, 0.into()), 160 _ => TextRange::offset_len(self.offset, 0.into()),
108 } 161 }
109 } 162 }
@@ -114,27 +167,24 @@ impl<'a> CompletionContext<'a> {
114 167
115 fn fill( 168 fn fill(
116 &mut self, 169 &mut self,
117 original_file: &ast::SourceFile, 170 original_file: &SyntaxNode,
118 file_with_fake_ident: ast::SourceFile, 171 file_with_fake_ident: SyntaxNode,
119 offset: TextUnit, 172 offset: TextUnit,
120 ) { 173 ) {
121 // First, let's try to complete a reference to some declaration. 174 // First, let's try to complete a reference to some declaration.
122 if let Some(name_ref) = 175 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
123 find_node_at_offset::<ast::NameRef>(file_with_fake_ident.syntax(), offset)
124 {
125 // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. 176 // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
126 // See RFC#1685. 177 // See RFC#1685.
127 if is_node::<ast::Param>(name_ref.syntax()) { 178 if is_node::<ast::Param>(name_ref.syntax()) {
128 self.is_param = true; 179 self.is_param = true;
129 return; 180 return;
130 } 181 }
131 self.classify_name_ref(original_file, name_ref); 182 self.classify_name_ref(original_file, name_ref, offset);
132 } 183 }
133 184
134 // Otherwise, see if this is a declaration. We can use heuristics to 185 // Otherwise, see if this is a declaration. We can use heuristics to
135 // suggest declaration names, see `CompletionKind::Magic`. 186 // suggest declaration names, see `CompletionKind::Magic`.
136 if let Some(name) = find_node_at_offset::<ast::Name>(file_with_fake_ident.syntax(), offset) 187 if let Some(name) = find_node_at_offset::<ast::Name>(&file_with_fake_ident, offset) {
137 {
138 if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) { 188 if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) {
139 let parent = bind_pat.syntax().parent(); 189 let parent = bind_pat.syntax().parent();
140 if parent.clone().and_then(ast::MatchArm::cast).is_some() 190 if parent.clone().and_then(ast::MatchArm::cast).is_some()
@@ -148,23 +198,29 @@ impl<'a> CompletionContext<'a> {
148 return; 198 return;
149 } 199 }
150 if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() { 200 if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() {
151 self.record_lit_pat = find_node_at_offset(original_file.syntax(), self.offset); 201 self.record_lit_pat =
202 self.sema.find_node_at_offset_with_macros(&original_file, offset);
152 } 203 }
153 } 204 }
154 } 205 }
155 206
156 fn classify_name_ref(&mut self, original_file: &SourceFile, name_ref: ast::NameRef) { 207 fn classify_name_ref(
208 &mut self,
209 original_file: &SyntaxNode,
210 name_ref: ast::NameRef,
211 offset: TextUnit,
212 ) {
157 self.name_ref_syntax = 213 self.name_ref_syntax =
158 find_node_at_offset(original_file.syntax(), name_ref.syntax().text_range().start()); 214 find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
159 let name_range = name_ref.syntax().text_range(); 215 let name_range = name_ref.syntax().text_range();
160 if name_ref.syntax().parent().and_then(ast::RecordField::cast).is_some() { 216 if name_ref.syntax().parent().and_then(ast::RecordField::cast).is_some() {
161 self.record_lit_syntax = find_node_at_offset(original_file.syntax(), self.offset); 217 self.record_lit_syntax =
218 self.sema.find_node_at_offset_with_macros(&original_file, offset);
162 } 219 }
163 220
164 self.impl_def = self 221 self.impl_def = self
165 .token 222 .sema
166 .parent() 223 .ancestors_with_macros(self.token.parent())
167 .ancestors()
168 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) 224 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
169 .find_map(ast::ImplDef::cast); 225 .find_map(ast::ImplDef::cast);
170 226
@@ -183,12 +239,12 @@ impl<'a> CompletionContext<'a> {
183 _ => (), 239 _ => (),
184 } 240 }
185 241
186 self.use_item_syntax = self.token.parent().ancestors().find_map(ast::UseItem::cast); 242 self.use_item_syntax =
243 self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::UseItem::cast);
187 244
188 self.function_syntax = self 245 self.function_syntax = self
189 .token 246 .sema
190 .parent() 247 .ancestors_with_macros(self.token.parent())
191 .ancestors()
192 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) 248 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
193 .find_map(ast::FnDef::cast); 249 .find_map(ast::FnDef::cast);
194 250
@@ -242,7 +298,7 @@ impl<'a> CompletionContext<'a> {
242 298
243 if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { 299 if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) {
244 if let Some(if_expr) = 300 if let Some(if_expr) =
245 find_node_at_offset::<ast::IfExpr>(original_file.syntax(), off) 301 self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off)
246 { 302 {
247 if if_expr.syntax().text_range().end() 303 if if_expr.syntax().text_range().end()
248 < name_ref.syntax().text_range().start() 304 < name_ref.syntax().text_range().start()
@@ -259,7 +315,7 @@ impl<'a> CompletionContext<'a> {
259 self.dot_receiver = field_expr 315 self.dot_receiver = field_expr
260 .expr() 316 .expr()
261 .map(|e| e.syntax().text_range()) 317 .map(|e| e.syntax().text_range())
262 .and_then(|r| find_node_with_range(original_file.syntax(), r)); 318 .and_then(|r| find_node_with_range(original_file, r));
263 self.dot_receiver_is_ambiguous_float_literal = 319 self.dot_receiver_is_ambiguous_float_literal =
264 if let Some(ast::Expr::Literal(l)) = &self.dot_receiver { 320 if let Some(ast::Expr::Literal(l)) = &self.dot_receiver {
265 match l.kind() { 321 match l.kind() {
@@ -275,7 +331,7 @@ impl<'a> CompletionContext<'a> {
275 self.dot_receiver = method_call_expr 331 self.dot_receiver = method_call_expr
276 .expr() 332 .expr()
277 .map(|e| e.syntax().text_range()) 333 .map(|e| e.syntax().text_range())
278 .and_then(|r| find_node_with_range(original_file.syntax(), r)); 334 .and_then(|r| find_node_with_range(original_file, r));
279 self.is_call = true; 335 self.is_call = true;
280 } 336 }
281 } 337 }
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs
index 61867c0ff..ef0eb43b2 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.
@@ -47,6 +47,10 @@ pub struct CompletionItem {
47 47
48 /// Whether this item is marked as deprecated 48 /// Whether this item is marked as deprecated
49 deprecated: bool, 49 deprecated: bool,
50
51 /// If completing a function call, ask the editor to show parameter popup
52 /// after completion.
53 trigger_call_info: bool,
50} 54}
51 55
52// We use custom debug for CompletionItem to make `insta`'s diffs more readable. 56// We use custom debug for CompletionItem to make `insta`'s diffs more readable.
@@ -139,6 +143,7 @@ impl CompletionItem {
139 kind: None, 143 kind: None,
140 text_edit: None, 144 text_edit: None,
141 deprecated: None, 145 deprecated: None,
146 trigger_call_info: None,
142 } 147 }
143 } 148 }
144 /// What user sees in pop-up in the UI. 149 /// What user sees in pop-up in the UI.
@@ -177,6 +182,10 @@ impl CompletionItem {
177 pub fn deprecated(&self) -> bool { 182 pub fn deprecated(&self) -> bool {
178 self.deprecated 183 self.deprecated
179 } 184 }
185
186 pub fn trigger_call_info(&self) -> bool {
187 self.trigger_call_info
188 }
180} 189}
181 190
182/// A helper to make `CompletionItem`s. 191/// A helper to make `CompletionItem`s.
@@ -193,6 +202,7 @@ pub(crate) struct Builder {
193 kind: Option<CompletionItemKind>, 202 kind: Option<CompletionItemKind>,
194 text_edit: Option<TextEdit>, 203 text_edit: Option<TextEdit>,
195 deprecated: Option<bool>, 204 deprecated: Option<bool>,
205 trigger_call_info: Option<bool>,
196} 206}
197 207
198impl Builder { 208impl Builder {
@@ -221,6 +231,7 @@ impl Builder {
221 kind: self.kind, 231 kind: self.kind,
222 completion_kind: self.completion_kind, 232 completion_kind: self.completion_kind,
223 deprecated: self.deprecated.unwrap_or(false), 233 deprecated: self.deprecated.unwrap_or(false),
234 trigger_call_info: self.trigger_call_info.unwrap_or(false),
224 } 235 }
225 } 236 }
226 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { 237 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
@@ -271,6 +282,10 @@ impl Builder {
271 self.deprecated = Some(deprecated); 282 self.deprecated = Some(deprecated);
272 self 283 self
273 } 284 }
285 pub(crate) fn trigger_call_info(mut self) -> Builder {
286 self.trigger_call_info = Some(true);
287 self
288 }
274} 289}
275 290
276impl<'a> Into<CompletionItem> for Builder { 291impl<'a> Into<CompletionItem> for Builder {
@@ -303,20 +318,3 @@ impl Into<Vec<CompletionItem>> for Completions {
303 self.buf 318 self.buf
304 } 319 }
305} 320}
306
307#[cfg(test)]
308pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
309 use crate::completion::completions;
310 use crate::mock_analysis::{analysis_and_position, single_file_with_position};
311 let (analysis, position) = if code.contains("//-") {
312 analysis_and_position(code)
313 } else {
314 single_file_with_position(code)
315 };
316 let completions = completions(&analysis.db, position).unwrap();
317 let completion_items: Vec<CompletionItem> = completions.into();
318 let mut kind_completions: Vec<CompletionItem> =
319 completion_items.into_iter().filter(|c| c.completion_kind == kind).collect();
320 kind_completions.sort_by_key(|c| c.label.clone());
321 kind_completions
322}
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs
index dac232a85..5213def20 100644
--- a/crates/ra_ide/src/completion/presentation.rs
+++ b/crates/ra_ide/src/completion/presentation.rs
@@ -103,11 +103,8 @@ impl Completions {
103 } 103 }
104 }; 104 };
105 105
106 // If not an import, add parenthesis automatically. 106 // Add `<>` for generic types
107 if ctx.is_path_type 107 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 { 108 let has_non_default_type_params = match resolution {
112 ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db), 109 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), 110 ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db),
@@ -211,26 +208,29 @@ impl Completions {
211 .set_deprecated(is_deprecated(func, ctx.db)) 208 .set_deprecated(is_deprecated(func, ctx.db))
212 .detail(function_signature.to_string()); 209 .detail(function_signature.to_string());
213 210
214 // Add `<>` for generic types 211 // If not an import, add parenthesis automatically.
215 if ctx.use_item_syntax.is_none() 212 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); 213 tested_by!(inserts_parens_for_function_calls);
220 214
221 let (snippet, label) = if params.is_empty() || has_self_param && params.len() == 1 { 215 let (snippet, label) = if params.is_empty() || has_self_param && params.len() == 1 {
222 (format!("{}()$0", name), format!("{}()", name)) 216 (format!("{}()$0", name), format!("{}()", name))
223 } else { 217 } else {
224 let to_skip = if has_self_param { 1 } else { 0 }; 218 builder = builder.trigger_call_info();
225 let function_params_snippet = 219 let snippet = if ctx.options.add_call_argument_snippets {
226 join( 220 let to_skip = if has_self_param { 1 } else { 0 };
221 let function_params_snippet = join(
227 function_signature.parameter_names.iter().skip(to_skip).enumerate().map( 222 function_signature.parameter_names.iter().skip(to_skip).enumerate().map(
228 |(index, param_name)| format!("${{{}:{}}}", index + 1, param_name), 223 |(index, param_name)| format!("${{{}:{}}}", index + 1, param_name),
229 ), 224 ),
230 ) 225 )
231 .separator(", ") 226 .separator(", ")
232 .to_string(); 227 .to_string();
233 (format!("{}({})$0", name, function_params_snippet), format!("{}(…)", name)) 228 format!("{}({})$0", name, function_params_snippet)
229 } else {
230 format!("{}($0)", name)
231 };
232
233 (snippet, format!("{}(…)", name))
234 }; 234 };
235 builder = builder.lookup_by(name).label(label).insert_snippet(snippet); 235 builder = builder.lookup_by(name).label(label).insert_snippet(snippet);
236 } 236 }
@@ -307,12 +307,22 @@ mod tests {
307 use insta::assert_debug_snapshot; 307 use insta::assert_debug_snapshot;
308 use test_utils::covers; 308 use test_utils::covers;
309 309
310 use crate::completion::{do_completion, CompletionItem, CompletionKind}; 310 use crate::completion::{
311 test_utils::{do_completion, do_completion_with_options},
312 CompletionItem, CompletionKind, CompletionOptions,
313 };
311 314
312 fn do_reference_completion(ra_fixture: &str) -> Vec<CompletionItem> { 315 fn do_reference_completion(ra_fixture: &str) -> Vec<CompletionItem> {
313 do_completion(ra_fixture, CompletionKind::Reference) 316 do_completion(ra_fixture, CompletionKind::Reference)
314 } 317 }
315 318
319 fn do_reference_completion_with_options(
320 ra_fixture: &str,
321 options: CompletionOptions,
322 ) -> Vec<CompletionItem> {
323 do_completion_with_options(ra_fixture, CompletionKind::Reference, &options)
324 }
325
316 #[test] 326 #[test]
317 fn enum_detail_includes_names_for_record() { 327 fn enum_detail_includes_names_for_record() {
318 assert_debug_snapshot!( 328 assert_debug_snapshot!(
@@ -533,7 +543,7 @@ mod tests {
533 } 543 }
534 544
535 #[test] 545 #[test]
536 fn parens_for_method_call() { 546 fn arg_snippets_for_method_call() {
537 assert_debug_snapshot!( 547 assert_debug_snapshot!(
538 do_reference_completion( 548 do_reference_completion(
539 r" 549 r"
@@ -563,6 +573,40 @@ mod tests {
563 } 573 }
564 574
565 #[test] 575 #[test]
576 fn no_arg_snippets_for_method_call() {
577 assert_debug_snapshot!(
578 do_reference_completion_with_options(
579 r"
580 struct S {}
581 impl S {
582 fn foo(&self, x: i32) {}
583 }
584 fn bar(s: &S) {
585 s.f<|>
586 }
587 ",
588 CompletionOptions {
589 add_call_argument_snippets: false,
590 .. Default::default()
591 }
592 ),
593 @r###"
594 [
595 CompletionItem {
596 label: "foo(…)",
597 source_range: [171; 172),
598 delete: [171; 172),
599 insert: "foo($0)",
600 kind: Method,
601 lookup: "foo",
602 detail: "fn foo(&self, x: i32)",
603 },
604 ]
605 "###
606 )
607 }
608
609 #[test]
566 fn dont_render_function_parens_in_use_item() { 610 fn dont_render_function_parens_in_use_item() {
567 assert_debug_snapshot!( 611 assert_debug_snapshot!(
568 do_reference_completion( 612 do_reference_completion(
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 1c26a8697..eaeaaa2b4 100644
--- a/crates/ra_ide/src/display.rs
+++ b/crates/ra_ide/src/display.rs
@@ -68,17 +68,23 @@ pub(crate) fn macro_label(node: &ast::MacroCall) -> String {
68} 68}
69 69
70pub(crate) fn rust_code_markup<CODE: AsRef<str>>(val: CODE) -> String { 70pub(crate) fn rust_code_markup<CODE: AsRef<str>>(val: CODE) -> String {
71 rust_code_markup_with_doc::<_, &str>(val, None) 71 rust_code_markup_with_doc::<_, &str>(val, None, None)
72} 72}
73 73
74pub(crate) fn rust_code_markup_with_doc<CODE, DOC>(val: CODE, doc: Option<DOC>) -> String 74pub(crate) fn rust_code_markup_with_doc<CODE, DOC>(
75 val: CODE,
76 doc: Option<DOC>,
77 mod_path: Option<String>,
78) -> String
75where 79where
76 CODE: AsRef<str>, 80 CODE: AsRef<str>,
77 DOC: AsRef<str>, 81 DOC: AsRef<str>,
78{ 82{
83 let mod_path =
84 mod_path.filter(|path| !path.is_empty()).map(|path| path + "\n").unwrap_or_default();
79 if let Some(doc) = doc { 85 if let Some(doc) = doc {
80 format!("```rust\n{}\n```\n\n{}", val.as_ref(), doc.as_ref()) 86 format!("```rust\n{}{}\n```\n\n{}", mod_path, val.as_ref(), doc.as_ref())
81 } else { 87 } else {
82 format!("```rust\n{}\n```", val.as_ref()) 88 format!("```rust\n{}{}\n```", mod_path, val.as_ref())
83 } 89 }
84} 90}
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index e9c682557..25e038a55 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -1,6 +1,10 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use hir::{Adt, HasSource, HirDisplay, Semantics}; 3use hir::{
4 Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef,
5 ModuleSource, Semantics,
6};
7use ra_db::SourceDatabase;
4use ra_ide_db::{ 8use ra_ide_db::{
5 defs::{classify_name, classify_name_ref, Definition}, 9 defs::{classify_name, classify_name_ref, Definition},
6 RootDatabase, 10 RootDatabase,
@@ -16,6 +20,8 @@ use crate::{
16 display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, 20 display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel},
17 FilePosition, RangeInfo, 21 FilePosition, RangeInfo,
18}; 22};
23use itertools::Itertools;
24use std::iter::once;
19 25
20/// Contains the results when hovering over an item 26/// Contains the results when hovering over an item
21#[derive(Debug, Clone)] 27#[derive(Debug, Clone)]
@@ -83,44 +89,86 @@ impl HoverResult {
83 } 89 }
84} 90}
85 91
86fn hover_text(docs: Option<String>, desc: Option<String>) -> Option<String> { 92fn hover_text(
87 match (desc, docs) { 93 docs: Option<String>,
88 (Some(desc), docs) => Some(rust_code_markup_with_doc(desc, docs)), 94 desc: Option<String>,
89 (None, Some(docs)) => Some(docs), 95 mod_path: Option<String>,
96) -> Option<String> {
97 match (desc, docs, mod_path) {
98 (Some(desc), docs, mod_path) => Some(rust_code_markup_with_doc(desc, docs, mod_path)),
99 (None, Some(docs), _) => Some(docs),
100 _ => None,
101 }
102}
103
104fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> {
105 match def {
106 Definition::StructField(f) => Some(f.parent_def(db).name(db)),
107 Definition::Local(l) => l.parent(db).name(db),
108 Definition::ModuleDef(md) => match md {
109 ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) {
110 AssocItemContainer::Trait(t) => Some(t.name(db)),
111 AssocItemContainer::ImplDef(i) => i.target_ty(db).as_adt().map(|adt| adt.name(db)),
112 },
113 ModuleDef::EnumVariant(e) => Some(e.parent_enum(db).name(db)),
114 _ => None,
115 },
116 Definition::SelfType(i) => i.target_ty(db).as_adt().map(|adt| adt.name(db)),
90 _ => None, 117 _ => None,
91 } 118 }
119 .map(|name| name.to_string())
120}
121
122fn determine_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
123 let mod_path = def.module(db).map(|module| {
124 once(db.crate_graph()[module.krate().into()].display_name.clone())
125 .chain(
126 module
127 .path_to_root(db)
128 .into_iter()
129 .rev()
130 .map(|it| it.name(db).map(|name| name.to_string())),
131 )
132 .chain(once(definition_owner_name(db, def)))
133 .flatten()
134 .join("::")
135 });
136 mod_path
92} 137}
93 138
94fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<String> { 139fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<String> {
140 let mod_path = determine_mod_path(db, &def);
95 return match def { 141 return match def {
96 Definition::Macro(it) => { 142 Definition::Macro(it) => {
97 let src = it.source(db); 143 let src = it.source(db);
98 hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value))) 144 hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value)), mod_path)
99 } 145 }
100 Definition::StructField(it) => { 146 Definition::StructField(it) => {
101 let src = it.source(db); 147 let src = it.source(db);
102 match src.value { 148 match src.value {
103 hir::FieldSource::Named(it) => hover_text(it.doc_comment_text(), it.short_label()), 149 FieldSource::Named(it) => {
150 hover_text(it.doc_comment_text(), it.short_label(), mod_path)
151 }
104 _ => None, 152 _ => None,
105 } 153 }
106 } 154 }
107 Definition::ModuleDef(it) => match it { 155 Definition::ModuleDef(it) => match it {
108 hir::ModuleDef::Module(it) => match it.definition_source(db).value { 156 ModuleDef::Module(it) => match it.definition_source(db).value {
109 hir::ModuleSource::Module(it) => { 157 ModuleSource::Module(it) => {
110 hover_text(it.doc_comment_text(), it.short_label()) 158 hover_text(it.doc_comment_text(), it.short_label(), mod_path)
111 } 159 }
112 _ => None, 160 _ => None,
113 }, 161 },
114 hir::ModuleDef::Function(it) => from_def_source(db, it), 162 ModuleDef::Function(it) => from_def_source(db, it, mod_path),
115 hir::ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it), 163 ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it, mod_path),
116 hir::ModuleDef::Adt(Adt::Union(it)) => from_def_source(db, it), 164 ModuleDef::Adt(Adt::Union(it)) => from_def_source(db, it, mod_path),
117 hir::ModuleDef::Adt(Adt::Enum(it)) => from_def_source(db, it), 165 ModuleDef::Adt(Adt::Enum(it)) => from_def_source(db, it, mod_path),
118 hir::ModuleDef::EnumVariant(it) => from_def_source(db, it), 166 ModuleDef::EnumVariant(it) => from_def_source(db, it, mod_path),
119 hir::ModuleDef::Const(it) => from_def_source(db, it), 167 ModuleDef::Const(it) => from_def_source(db, it, mod_path),
120 hir::ModuleDef::Static(it) => from_def_source(db, it), 168 ModuleDef::Static(it) => from_def_source(db, it, mod_path),
121 hir::ModuleDef::Trait(it) => from_def_source(db, it), 169 ModuleDef::Trait(it) => from_def_source(db, it, mod_path),
122 hir::ModuleDef::TypeAlias(it) => from_def_source(db, it), 170 ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path),
123 hir::ModuleDef::BuiltinType(it) => Some(it.to_string()), 171 ModuleDef::BuiltinType(it) => Some(it.to_string()),
124 }, 172 },
125 Definition::Local(it) => { 173 Definition::Local(it) => {
126 Some(rust_code_markup(it.ty(db).display_truncated(db, None).to_string())) 174 Some(rust_code_markup(it.ty(db).display_truncated(db, None).to_string()))
@@ -131,13 +179,13 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
131 } 179 }
132 }; 180 };
133 181
134 fn from_def_source<A, D>(db: &RootDatabase, def: D) -> Option<String> 182 fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<String>
135 where 183 where
136 D: HasSource<Ast = A>, 184 D: HasSource<Ast = A>,
137 A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, 185 A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel,
138 { 186 {
139 let src = def.source(db); 187 let src = def.source(db);
140 hover_text(src.value.doc_comment_text(), src.value.short_label()) 188 hover_text(src.value.doc_comment_text(), src.value.short_label(), mod_path)
141 } 189 }
142} 190}
143 191
@@ -345,7 +393,7 @@ mod tests {
345 }; 393 };
346 } 394 }
347 "#, 395 "#,
348 &["field_a: u32"], 396 &["Foo\nfield_a: u32"],
349 ); 397 );
350 398
351 // Hovering over the field in the definition 399 // Hovering over the field in the definition
@@ -362,7 +410,7 @@ mod tests {
362 }; 410 };
363 } 411 }
364 "#, 412 "#,
365 &["field_a: u32"], 413 &["Foo\nfield_a: u32"],
366 ); 414 );
367 } 415 }
368 416
@@ -415,7 +463,7 @@ fn main() {
415 ", 463 ",
416 ); 464 );
417 let hover = analysis.hover(position).unwrap().unwrap(); 465 let hover = analysis.hover(position).unwrap().unwrap();
418 assert_eq!(trim_markup_opt(hover.info.first()), Some("Some")); 466 assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\nSome"));
419 467
420 let (analysis, position) = single_file_with_position( 468 let (analysis, position) = single_file_with_position(
421 " 469 "
@@ -442,6 +490,7 @@ fn main() {
442 } 490 }
443 "#, 491 "#,
444 &[" 492 &["
493Option
445None 494None
446``` 495```
447 496
@@ -462,6 +511,7 @@ The None variant
462 } 511 }
463 "#, 512 "#,
464 &[" 513 &["
514Option
465Some 515Some
466``` 516```
467 517
@@ -528,21 +578,23 @@ fn func(foo: i32) { if true { <|>foo; }; }
528 fn test_hover_infer_associated_method_exact() { 578 fn test_hover_infer_associated_method_exact() {
529 let (analysis, position) = single_file_with_position( 579 let (analysis, position) = single_file_with_position(
530 " 580 "
531 struct Thing { x: u32 } 581 mod wrapper {
582 struct Thing { x: u32 }
532 583
533 impl Thing { 584 impl Thing {
534 fn new() -> Thing { 585 fn new() -> Thing {
535 Thing { x: 0 } 586 Thing { x: 0 }
587 }
536 } 588 }
537 } 589 }
538 590
539 fn main() { 591 fn main() {
540 let foo_test = Thing::new<|>(); 592 let foo_test = wrapper::Thing::new<|>();
541 } 593 }
542 ", 594 ",
543 ); 595 );
544 let hover = analysis.hover(position).unwrap().unwrap(); 596 let hover = analysis.hover(position).unwrap().unwrap();
545 assert_eq!(trim_markup_opt(hover.info.first()), Some("fn new() -> Thing")); 597 assert_eq!(trim_markup_opt(hover.info.first()), Some("wrapper::Thing\nfn new() -> Thing"));
546 assert_eq!(hover.info.is_exact(), true); 598 assert_eq!(hover.info.is_exact(), true);
547 } 599 }
548 600
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs
index 69098a630..cf0cbdbd0 100644
--- a/crates/ra_ide/src/inlay_hints.rs
+++ b/crates/ra_ide/src/inlay_hints.rs
@@ -119,6 +119,12 @@ fn should_not_display_type_hint(db: &RootDatabase, bind_pat: &ast::BindPat, pat_
119 return true; 119 return true;
120 } 120 }
121 121
122 if let Some(Adt::Struct(s)) = pat_ty.as_adt() {
123 if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.syntax().to_string() {
124 return true;
125 }
126 }
127
122 for node in bind_pat.syntax().ancestors() { 128 for node in bind_pat.syntax().ancestors() {
123 match_ast! { 129 match_ast! {
124 match node { 130 match node {
@@ -943,4 +949,30 @@ fn main() {
943 "### 949 "###
944 ); 950 );
945 } 951 }
952
953 #[test]
954 fn unit_structs_have_no_type_hints() {
955 let (analysis, file_id) = single_file(
956 r#"
957enum CustomResult<T, E> {
958 Ok(T),
959 Err(E),
960}
961use CustomResult::*;
962
963struct SyntheticSyntax;
964
965fn main() {
966 match Ok(()) {
967 Ok(_) => (),
968 Err(SyntheticSyntax) => (),
969 }
970}"#,
971 );
972
973 assert_debug_snapshot!(analysis.inlay_hints(file_id, Some(8)).unwrap(), @r###"
974 []
975 "###
976 );
977 }
946} 978}
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 4dfe0553e..015fae195 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -62,7 +62,7 @@ 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,
@@ -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) {
@@ -211,18 +206,20 @@ impl Analysis {
211 // Default to enable test for single file. 206 // Default to enable test for single file.
212 let mut cfg_options = CfgOptions::default(); 207 let mut cfg_options = CfgOptions::default();
213 cfg_options.insert_atom("test".into()); 208 cfg_options.insert_atom("test".into());
214 crate_graph.add_crate_root(file_id, Edition::Edition2018, cfg_options, Env::default()); 209 crate_graph.add_crate_root(
210 file_id,
211 Edition::Edition2018,
212 None,
213 cfg_options,
214 Env::default(),
215 Default::default(),
216 );
215 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));
216 change.set_crate_graph(crate_graph); 218 change.set_crate_graph(crate_graph);
217 host.apply_change(change); 219 host.apply_change(change);
218 (host.analysis(), file_id) 220 (host.analysis(), file_id)
219 } 221 }
220 222
221 /// Features for Analysis.
222 pub fn feature_flags(&self) -> &FeatureFlags {
223 &self.db.feature_flags
224 }
225
226 /// Debug info about the current state of the analysis. 223 /// Debug info about the current state of the analysis.
227 pub fn status(&self) -> Cancelable<String> { 224 pub fn status(&self) -> Cancelable<String> {
228 self.with_db(|db| status::status(&*db)) 225 self.with_db(|db| status::status(&*db))
@@ -415,12 +412,12 @@ impl Analysis {
415 412
416 /// Returns the edition of the given crate. 413 /// Returns the edition of the given crate.
417 pub fn crate_edition(&self, crate_id: CrateId) -> Cancelable<Edition> { 414 pub fn crate_edition(&self, crate_id: CrateId) -> Cancelable<Edition> {
418 self.with_db(|db| db.crate_graph().edition(crate_id)) 415 self.with_db(|db| db.crate_graph()[crate_id].edition)
419 } 416 }
420 417
421 /// Returns the root file of the given crate. 418 /// Returns the root file of the given crate.
422 pub fn crate_root(&self, crate_id: CrateId) -> Cancelable<FileId> { 419 pub fn crate_root(&self, crate_id: CrateId) -> Cancelable<FileId> {
423 self.with_db(|db| db.crate_graph().crate_root(crate_id)) 420 self.with_db(|db| db.crate_graph()[crate_id].root_file_id)
424 } 421 }
425 422
426 /// Returns the set of possible targets to run for the current file. 423 /// Returns the set of possible targets to run for the current file.
@@ -444,8 +441,12 @@ impl Analysis {
444 } 441 }
445 442
446 /// Computes completions at the given position. 443 /// Computes completions at the given position.
447 pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> { 444 pub fn completions(
448 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))
449 } 450 }
450 451
451 /// 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 f4cd6deb7..25816cf6f 100644
--- a/crates/ra_ide/src/mock_analysis.rs
+++ b/crates/ra_ide/src/mock_analysis.rs
@@ -99,13 +99,21 @@ impl MockAnalysis {
99 root_crate = Some(crate_graph.add_crate_root( 99 root_crate = Some(crate_graph.add_crate_root(
100 file_id, 100 file_id,
101 Edition2018, 101 Edition2018,
102 None,
102 cfg_options, 103 cfg_options,
103 Env::default(), 104 Env::default(),
105 Default::default(),
104 )); 106 ));
105 } else if path.ends_with("/lib.rs") { 107 } else if path.ends_with("/lib.rs") {
106 let other_crate =
107 crate_graph.add_crate_root(file_id, Edition2018, cfg_options, Env::default());
108 let crate_name = path.parent().unwrap().file_name().unwrap(); 108 let crate_name = path.parent().unwrap().file_name().unwrap();
109 let other_crate = crate_graph.add_crate_root(
110 file_id,
111 Edition2018,
112 Some(crate_name.to_owned()),
113 cfg_options,
114 Env::default(),
115 Default::default(),
116 );
109 if let Some(root_crate) = root_crate { 117 if let Some(root_crate) = root_crate {
110 crate_graph 118 crate_graph
111 .add_dep(root_crate, CrateName::new(crate_name).unwrap(), other_crate) 119 .add_dep(root_crate, CrateName::new(crate_name).unwrap(), other_crate)
diff --git a/crates/ra_ide/src/parent_module.rs b/crates/ra_ide/src/parent_module.rs
index 2c4bdb039..76d130b9b 100644
--- a/crates/ra_ide/src/parent_module.rs
+++ b/crates/ra_ide/src/parent_module.rs
@@ -133,8 +133,10 @@ mod tests {
133 let crate_id = crate_graph.add_crate_root( 133 let crate_id = crate_graph.add_crate_root(
134 root_file, 134 root_file,
135 Edition2018, 135 Edition2018,
136 None,
136 CfgOptions::default(), 137 CfgOptions::default(),
137 Env::default(), 138 Env::default(),
139 Default::default(),
138 ); 140 );
139 let mut change = AnalysisChange::new(); 141 let mut change = AnalysisChange::new();
140 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/typing.rs b/crates/ra_ide/src/typing.rs
index 7f1b9150f..53c65f8bc 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());
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}