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/assists.rs47
-rw-r--r--crates/ra_ide/src/completion.rs31
-rw-r--r--crates/ra_ide/src/completion/complete_attribute.rs19
-rw-r--r--crates/ra_ide/src/completion/complete_qualified_path.rs25
-rw-r--r--crates/ra_ide/src/completion/complete_trait_impl.rs46
-rw-r--r--crates/ra_ide/src/completion/complete_unqualified_path.rs21
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs4
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs7
-rw-r--r--crates/ra_ide/src/diagnostics.rs36
-rw-r--r--crates/ra_ide/src/display/function_signature.rs8
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs10
-rw-r--r--crates/ra_ide/src/goto_definition.rs43
-rw-r--r--crates/ra_ide/src/hover.rs49
-rw-r--r--crates/ra_ide/src/inlay_hints.rs74
-rw-r--r--crates/ra_ide/src/join_lines.rs6
-rw-r--r--crates/ra_ide/src/lib.rs38
-rw-r--r--crates/ra_ide/src/marks.rs2
-rw-r--r--crates/ra_ide/src/references/rename.rs117
-rw-r--r--crates/ra_ide/src/runnables.rs43
-rw-r--r--crates/ra_ide/src/source_change.rs119
-rw-r--r--crates/ra_ide/src/ssr.rs46
-rw-r--r--crates/ra_ide/src/test_utils.rs6
-rw-r--r--crates/ra_ide/src/typing.rs14
-rw-r--r--crates/ra_ide/src/typing/on_enter.rs5
24 files changed, 487 insertions, 329 deletions
diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs
deleted file mode 100644
index 2b5d11681..000000000
--- a/crates/ra_ide/src/assists.rs
+++ /dev/null
@@ -1,47 +0,0 @@
1//! FIXME: write short doc here
2
3use ra_assists::{resolved_assists, AssistAction, AssistLabel};
4use ra_db::{FilePosition, FileRange};
5use ra_ide_db::RootDatabase;
6
7use crate::{FileId, SourceChange, SourceFileEdit};
8
9pub use ra_assists::AssistId;
10
11#[derive(Debug)]
12pub struct Assist {
13 pub id: AssistId,
14 pub label: String,
15 pub group_label: Option<String>,
16 pub source_change: SourceChange,
17}
18
19pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> {
20 resolved_assists(db, frange)
21 .into_iter()
22 .map(|assist| {
23 let file_id = frange.file_id;
24 let assist_label = &assist.label;
25 Assist {
26 id: assist_label.id,
27 label: assist_label.label.clone(),
28 group_label: assist.group_label.map(|it| it.0),
29 source_change: action_to_edit(assist.action, file_id, assist_label),
30 }
31 })
32 .collect()
33}
34
35fn action_to_edit(
36 action: AssistAction,
37 file_id: FileId,
38 assist_label: &AssistLabel,
39) -> SourceChange {
40 let file_id = match action.file {
41 ra_assists::AssistFile::TargetFile(it) => it,
42 _ => file_id,
43 };
44 let file_edit = SourceFileEdit { file_id, edit: action.edit };
45 SourceChange::source_file_edit(assist_label.label.clone(), file_edit)
46 .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id }))
47}
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs
index a0e06faa2..8bdc43b1a 100644
--- a/crates/ra_ide/src/completion.rs
+++ b/crates/ra_ide/src/completion.rs
@@ -65,23 +65,20 @@ pub(crate) fn completions(
65 let ctx = CompletionContext::new(db, position, config)?; 65 let ctx = CompletionContext::new(db, position, config)?;
66 66
67 let mut acc = Completions::default(); 67 let mut acc = Completions::default();
68 if ctx.attribute_under_caret.is_some() { 68 complete_attribute::complete_attribute(&mut acc, &ctx);
69 complete_attribute::complete_attribute(&mut acc, &ctx); 69 complete_fn_param::complete_fn_param(&mut acc, &ctx);
70 } else { 70 complete_keyword::complete_expr_keyword(&mut acc, &ctx);
71 complete_fn_param::complete_fn_param(&mut acc, &ctx); 71 complete_keyword::complete_use_tree_keyword(&mut acc, &ctx);
72 complete_keyword::complete_expr_keyword(&mut acc, &ctx); 72 complete_snippet::complete_expr_snippet(&mut acc, &ctx);
73 complete_keyword::complete_use_tree_keyword(&mut acc, &ctx); 73 complete_snippet::complete_item_snippet(&mut acc, &ctx);
74 complete_snippet::complete_expr_snippet(&mut acc, &ctx); 74 complete_qualified_path::complete_qualified_path(&mut acc, &ctx);
75 complete_snippet::complete_item_snippet(&mut acc, &ctx); 75 complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx);
76 complete_qualified_path::complete_qualified_path(&mut acc, &ctx); 76 complete_dot::complete_dot(&mut acc, &ctx);
77 complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx); 77 complete_record::complete_record(&mut acc, &ctx);
78 complete_dot::complete_dot(&mut acc, &ctx); 78 complete_pattern::complete_pattern(&mut acc, &ctx);
79 complete_record::complete_record(&mut acc, &ctx); 79 complete_postfix::complete_postfix(&mut acc, &ctx);
80 complete_pattern::complete_pattern(&mut acc, &ctx); 80 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
81 complete_postfix::complete_postfix(&mut acc, &ctx); 81 complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
82 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
83 complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
84 }
85 82
86 Some(acc) 83 Some(acc)
87} 84}
diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs
index 20e6edc17..f17266221 100644
--- a/crates/ra_ide/src/completion/complete_attribute.rs
+++ b/crates/ra_ide/src/completion/complete_attribute.rs
@@ -3,20 +3,21 @@
3//! This module uses a bit of static metadata to provide completions 3//! This module uses a bit of static metadata to provide completions
4//! for built-in attributes. 4//! for built-in attributes.
5 5
6use super::completion_context::CompletionContext; 6use ra_syntax::{ast, AstNode, SyntaxKind};
7use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions};
8use ast::AttrInput;
9use ra_syntax::{
10 ast::{self, AttrKind},
11 AstNode, SyntaxKind,
12};
13use rustc_hash::FxHashSet; 7use rustc_hash::FxHashSet;
14 8
9use crate::completion::{
10 completion_context::CompletionContext,
11 completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions},
12};
13
15pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { 14pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
16 let attribute = ctx.attribute_under_caret.as_ref()?; 15 let attribute = ctx.attribute_under_caret.as_ref()?;
17 16
18 match (attribute.path(), attribute.input()) { 17 match (attribute.path(), attribute.input()) {
19 (Some(path), Some(AttrInput::TokenTree(token_tree))) if path.to_string() == "derive" => { 18 (Some(path), Some(ast::AttrInput::TokenTree(token_tree)))
19 if path.to_string() == "derive" =>
20 {
20 complete_derive(acc, ctx, token_tree) 21 complete_derive(acc, ctx, token_tree)
21 } 22 }
22 _ => complete_attribute_start(acc, ctx, attribute), 23 _ => complete_attribute_start(acc, ctx, attribute),
@@ -40,7 +41,7 @@ fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attr
40 _ => {} 41 _ => {}
41 } 42 }
42 43
43 if attribute.kind() == AttrKind::Inner || !attr_completion.should_be_inner { 44 if attribute.kind() == ast::AttrKind::Inner || !attr_completion.should_be_inner {
44 acc.add(item); 45 acc.add(item);
45 } 46 }
46 } 47 }
diff --git a/crates/ra_ide/src/completion/complete_qualified_path.rs b/crates/ra_ide/src/completion/complete_qualified_path.rs
index aa56a5cd8..7fcd22525 100644
--- a/crates/ra_ide/src/completion/complete_qualified_path.rs
+++ b/crates/ra_ide/src/completion/complete_qualified_path.rs
@@ -2,16 +2,21 @@
2 2
3use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; 3use hir::{Adt, HasVisibility, PathResolution, ScopeDef};
4use ra_syntax::AstNode; 4use ra_syntax::AstNode;
5use rustc_hash::FxHashSet;
5use test_utils::tested_by; 6use test_utils::tested_by;
6 7
7use crate::completion::{CompletionContext, Completions}; 8use crate::completion::{CompletionContext, Completions};
8use rustc_hash::FxHashSet;
9 9
10pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) { 10pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) {
11 let path = match &ctx.path_prefix { 11 let path = match &ctx.path_prefix {
12 Some(path) => path.clone(), 12 Some(path) => path.clone(),
13 _ => return, 13 None => return,
14 }; 14 };
15
16 if ctx.attribute_under_caret.is_some() {
17 return;
18 }
19
15 let scope = ctx.scope(); 20 let scope = ctx.scope();
16 let context_module = scope.module(); 21 let context_module = scope.module();
17 22
@@ -79,7 +84,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
79 }); 84 });
80 85
81 // Iterate assoc types separately 86 // Iterate assoc types separately
82 ty.iterate_impl_items(ctx.db, krate, |item| { 87 ty.iterate_assoc_items(ctx.db, krate, |item| {
83 if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { 88 if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
84 return None; 89 return None;
85 } 90 }
@@ -1325,4 +1330,18 @@ mod tests {
1325 "### 1330 "###
1326 ); 1331 );
1327 } 1332 }
1333
1334 #[test]
1335 fn dont_complete_attr() {
1336 assert_debug_snapshot!(
1337 do_reference_completion(
1338 r"
1339 mod foo { pub struct Foo; }
1340 #[foo::<|>]
1341 fn f() {}
1342 "
1343 ),
1344 @r###"[]"###
1345 )
1346 }
1328} 1347}
diff --git a/crates/ra_ide/src/completion/complete_trait_impl.rs b/crates/ra_ide/src/completion/complete_trait_impl.rs
index ee32d1ff6..039df03e0 100644
--- a/crates/ra_ide/src/completion/complete_trait_impl.rs
+++ b/crates/ra_ide/src/completion/complete_trait_impl.rs
@@ -32,7 +32,7 @@
32//! ``` 32//! ```
33 33
34use hir::{self, Docs, HasSource}; 34use hir::{self, Docs, HasSource};
35use ra_assists::utils::get_missing_impl_items; 35use ra_assists::utils::get_missing_assoc_items;
36use ra_syntax::{ 36use ra_syntax::{
37 ast::{self, edit, ImplDef}, 37 ast::{self, edit, ImplDef},
38 AstNode, SyntaxKind, SyntaxNode, TextRange, T, 38 AstNode, SyntaxKind, SyntaxNode, TextRange, T,
@@ -50,7 +50,7 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext
50 if let Some((trigger, impl_def)) = completion_match(ctx) { 50 if let Some((trigger, impl_def)) = completion_match(ctx) {
51 match trigger.kind() { 51 match trigger.kind() {
52 SyntaxKind::NAME_REF => { 52 SyntaxKind::NAME_REF => {
53 get_missing_impl_items(&ctx.sema, &impl_def).iter().for_each(|item| match item { 53 get_missing_assoc_items(&ctx.sema, &impl_def).iter().for_each(|item| match item {
54 hir::AssocItem::Function(fn_item) => { 54 hir::AssocItem::Function(fn_item) => {
55 add_function_impl(&trigger, acc, ctx, &fn_item) 55 add_function_impl(&trigger, acc, ctx, &fn_item)
56 } 56 }
@@ -64,34 +64,40 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext
64 } 64 }
65 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
68 |item| match item { 68 get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| {
69 hir::AssocItem::Function(fn_item) => Some(fn_item), 69 match item {
70 _ => None, 70 hir::AssocItem::Function(fn_item) => Some(fn_item),
71 }, 71 _ => None,
72 ) { 72 }
73 })
74 {
73 add_function_impl(&trigger, acc, ctx, &missing_fn); 75 add_function_impl(&trigger, acc, ctx, &missing_fn);
74 } 76 }
75 } 77 }
76 78
77 SyntaxKind::TYPE_ALIAS_DEF => { 79 SyntaxKind::TYPE_ALIAS_DEF => {
78 for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map( 80 for missing_fn in
79 |item| match item { 81 get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| {
80 hir::AssocItem::TypeAlias(type_item) => Some(type_item), 82 match item {
81 _ => None, 83 hir::AssocItem::TypeAlias(type_item) => Some(type_item),
82 }, 84 _ => None,
83 ) { 85 }
86 })
87 {
84 add_type_alias_impl(&trigger, acc, ctx, &missing_fn); 88 add_type_alias_impl(&trigger, acc, ctx, &missing_fn);
85 } 89 }
86 } 90 }
87 91
88 SyntaxKind::CONST_DEF => { 92 SyntaxKind::CONST_DEF => {
89 for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map( 93 for missing_fn in
90 |item| match item { 94 get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| {
91 hir::AssocItem::Const(const_item) => Some(const_item), 95 match item {
92 _ => None, 96 hir::AssocItem::Const(const_item) => Some(const_item),
93 }, 97 _ => None,
94 ) { 98 }
99 })
100 {
95 add_const_impl(&trigger, acc, ctx, &missing_fn); 101 add_const_impl(&trigger, acc, ctx, &missing_fn);
96 } 102 }
97 } 103 }
diff --git a/crates/ra_ide/src/completion/complete_unqualified_path.rs b/crates/ra_ide/src/completion/complete_unqualified_path.rs
index a6a5568de..bd40af1cb 100644
--- a/crates/ra_ide/src/completion/complete_unqualified_path.rs
+++ b/crates/ra_ide/src/completion/complete_unqualified_path.rs
@@ -8,9 +8,12 @@ use hir::{Adt, ModuleDef, Type};
8use ra_syntax::AstNode; 8use ra_syntax::AstNode;
9 9
10pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { 10pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
11 if (!ctx.is_trivial_path && !ctx.is_pat_binding_or_const) 11 if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) {
12 || ctx.record_lit_syntax.is_some() 12 return;
13 }
14 if ctx.record_lit_syntax.is_some()
13 || ctx.record_pat_syntax.is_some() 15 || ctx.record_pat_syntax.is_some()
16 || ctx.attribute_under_caret.is_some()
14 { 17 {
15 return; 18 return;
16 } 19 }
@@ -1369,4 +1372,18 @@ mod tests {
1369 "### 1372 "###
1370 ) 1373 )
1371 } 1374 }
1375
1376 #[test]
1377 fn dont_complete_attr() {
1378 assert_debug_snapshot!(
1379 do_reference_completion(
1380 r"
1381 struct Foo;
1382 #[<|>]
1383 fn f() {}
1384 "
1385 ),
1386 @r###"[]"###
1387 )
1388 }
1372} 1389}
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
index dd87bd119..b6b9627de 100644
--- a/crates/ra_ide/src/completion/completion_context.rs
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -9,7 +9,7 @@ use ra_syntax::{
9 SyntaxKind::*, 9 SyntaxKind::*,
10 SyntaxNode, SyntaxToken, TextRange, TextSize, 10 SyntaxNode, SyntaxToken, TextRange, TextSize,
11}; 11};
12use ra_text_edit::AtomTextEdit; 12use ra_text_edit::Indel;
13 13
14use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; 14use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition};
15 15
@@ -76,7 +76,7 @@ impl<'a> CompletionContext<'a> {
76 // actual completion. 76 // actual completion.
77 let file_with_fake_ident = { 77 let file_with_fake_ident = {
78 let parse = db.parse(position.file_id); 78 let parse = db.parse(position.file_id);
79 let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); 79 let edit = Indel::insert(position.offset, "intellijRulezz".to_string());
80 parse.reparse(&edit).tree() 80 parse.reparse(&edit).tree()
81 }; 81 };
82 let fake_ident_token = 82 let fake_ident_token =
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs
index 5936fb8f7..6021f7279 100644
--- a/crates/ra_ide/src/completion/completion_item.rs
+++ b/crates/ra_ide/src/completion/completion_item.rs
@@ -2,11 +2,12 @@
2 2
3use std::fmt; 3use std::fmt;
4 4
5use super::completion_config::SnippetCap;
6use hir::Documentation; 5use hir::Documentation;
7use ra_syntax::TextRange; 6use ra_syntax::TextRange;
8use ra_text_edit::TextEdit; 7use ra_text_edit::TextEdit;
9 8
9use crate::completion::completion_config::SnippetCap;
10
10/// `CompletionItem` describes a single completion variant in the editor pop-up. 11/// `CompletionItem` describes a single completion variant in the editor pop-up.
11/// It is basically a POD with various properties. To construct a 12/// It is basically a POD with various properties. To construct a
12/// `CompletionItem`, use `new` method and the `Builder` struct. 13/// `CompletionItem`, use `new` method and the `Builder` struct.
@@ -62,8 +63,8 @@ impl fmt::Debug for CompletionItem {
62 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 63 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63 let mut s = f.debug_struct("CompletionItem"); 64 let mut s = f.debug_struct("CompletionItem");
64 s.field("label", &self.label()).field("source_range", &self.source_range()); 65 s.field("label", &self.label()).field("source_range", &self.source_range());
65 if self.text_edit().as_atoms().len() == 1 { 66 if self.text_edit().as_indels().len() == 1 {
66 let atom = &self.text_edit().as_atoms()[0]; 67 let atom = &self.text_edit().as_indels()[0];
67 s.field("delete", &atom.delete); 68 s.field("delete", &atom.delete);
68 s.field("insert", &atom.insert); 69 s.field("insert", &atom.insert);
69 } else { 70 } else {
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index a6b4c2c28..87a0b80f1 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -64,7 +64,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
64 .unwrap_or_else(|| RelativePath::new("")) 64 .unwrap_or_else(|| RelativePath::new(""))
65 .join(&d.candidate); 65 .join(&d.candidate);
66 let create_file = FileSystemEdit::CreateFile { source_root, path }; 66 let create_file = FileSystemEdit::CreateFile { source_root, path };
67 let fix = SourceChange::file_system_edit("create module", create_file); 67 let fix = SourceChange::file_system_edit("Create module", create_file);
68 res.borrow_mut().push(Diagnostic { 68 res.borrow_mut().push(Diagnostic {
69 range: sema.diagnostics_range(d).range, 69 range: sema.diagnostics_range(d).range,
70 message: d.message(), 70 message: d.message(),
@@ -92,7 +92,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
92 algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); 92 algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder);
93 93
94 Some(SourceChange::source_file_edit_from( 94 Some(SourceChange::source_file_edit_from(
95 "fill struct fields", 95 "Fill struct fields",
96 file_id, 96 file_id,
97 builder.finish(), 97 builder.finish(),
98 )) 98 ))
@@ -117,7 +117,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
117 let node = d.ast(db); 117 let node = d.ast(db);
118 let replacement = format!("Ok({})", node.syntax()); 118 let replacement = format!("Ok({})", node.syntax());
119 let edit = TextEdit::replace(node.syntax().text_range(), replacement); 119 let edit = TextEdit::replace(node.syntax().text_range(), replacement);
120 let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit); 120 let fix = SourceChange::source_file_edit_from("Wrap with ok", file_id, edit);
121 res.borrow_mut().push(Diagnostic { 121 res.borrow_mut().push(Diagnostic {
122 range: sema.diagnostics_range(d).range, 122 range: sema.diagnostics_range(d).range,
123 message: d.message(), 123 message: d.message(),
@@ -199,7 +199,7 @@ fn check_struct_shorthand_initialization(
199 message: "Shorthand struct initialization".to_string(), 199 message: "Shorthand struct initialization".to_string(),
200 severity: Severity::WeakWarning, 200 severity: Severity::WeakWarning,
201 fix: Some(SourceChange::source_file_edit( 201 fix: Some(SourceChange::source_file_edit(
202 "use struct shorthand initialization", 202 "Use struct shorthand initialization",
203 SourceFileEdit { file_id, edit }, 203 SourceFileEdit { file_id, edit },
204 )), 204 )),
205 }); 205 });
@@ -241,7 +241,11 @@ mod tests {
241 diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); 241 diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before));
242 let mut fix = diagnostic.fix.unwrap(); 242 let mut fix = diagnostic.fix.unwrap();
243 let edit = fix.source_file_edits.pop().unwrap().edit; 243 let edit = fix.source_file_edits.pop().unwrap().edit;
244 let actual = edit.apply(&before); 244 let actual = {
245 let mut actual = before.to_string();
246 edit.apply(&mut actual);
247 actual
248 };
245 assert_eq_text!(after, &actual); 249 assert_eq_text!(after, &actual);
246 } 250 }
247 251
@@ -256,7 +260,11 @@ mod tests {
256 let mut fix = diagnostic.fix.unwrap(); 260 let mut fix = diagnostic.fix.unwrap();
257 let edit = fix.source_file_edits.pop().unwrap().edit; 261 let edit = fix.source_file_edits.pop().unwrap().edit;
258 let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); 262 let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
259 let actual = edit.apply(&target_file_contents); 263 let actual = {
264 let mut actual = target_file_contents.to_string();
265 edit.apply(&mut actual);
266 actual
267 };
260 268
261 // Strip indent and empty lines from `after`, to match the behaviour of 269 // Strip indent and empty lines from `after`, to match the behaviour of
262 // `parse_fixture` called from `analysis_and_position`. 270 // `parse_fixture` called from `analysis_and_position`.
@@ -288,7 +296,11 @@ mod tests {
288 let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); 296 let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap();
289 let mut fix = diagnostic.fix.unwrap(); 297 let mut fix = diagnostic.fix.unwrap();
290 let edit = fix.source_file_edits.pop().unwrap().edit; 298 let edit = fix.source_file_edits.pop().unwrap().edit;
291 let actual = edit.apply(&before); 299 let actual = {
300 let mut actual = before.to_string();
301 edit.apply(&mut actual);
302 actual
303 };
292 assert_eq_text!(after, &actual); 304 assert_eq_text!(after, &actual);
293 } 305 }
294 306
@@ -606,7 +618,7 @@ mod tests {
606 range: 0..8, 618 range: 0..8,
607 fix: Some( 619 fix: Some(
608 SourceChange { 620 SourceChange {
609 label: "create module", 621 label: "Create module",
610 source_file_edits: [], 622 source_file_edits: [],
611 file_system_edits: [ 623 file_system_edits: [
612 CreateFile { 624 CreateFile {
@@ -655,17 +667,17 @@ mod tests {
655 range: 224..233, 667 range: 224..233,
656 fix: Some( 668 fix: Some(
657 SourceChange { 669 SourceChange {
658 label: "fill struct fields", 670 label: "Fill struct fields",
659 source_file_edits: [ 671 source_file_edits: [
660 SourceFileEdit { 672 SourceFileEdit {
661 file_id: FileId( 673 file_id: FileId(
662 1, 674 1,
663 ), 675 ),
664 edit: TextEdit { 676 edit: TextEdit {
665 atoms: [ 677 indels: [
666 AtomTextEdit { 678 Indel {
667 delete: 3..9,
668 insert: "{a:42, b: ()}", 679 insert: "{a:42, b: ()}",
680 delete: 3..9,
669 }, 681 },
670 ], 682 ],
671 }, 683 },
diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs
index db3907fe6..f16d42276 100644
--- a/crates/ra_ide/src/display/function_signature.rs
+++ b/crates/ra_ide/src/display/function_signature.rs
@@ -1,5 +1,7 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3// FIXME: this modules relies on strings and AST way too much, and it should be
4// rewritten (matklad 2020-05-07)
3use std::{ 5use std::{
4 convert::From, 6 convert::From,
5 fmt::{self, Display}, 7 fmt::{self, Display},
@@ -202,7 +204,11 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
202 204
203 res.extend(param_list.params().map(|param| param.syntax().text().to_string())); 205 res.extend(param_list.params().map(|param| param.syntax().text().to_string()));
204 res_types.extend(param_list.params().map(|param| { 206 res_types.extend(param_list.params().map(|param| {
205 param.syntax().text().to_string().split(':').nth(1).unwrap()[1..].to_string() 207 let param_text = param.syntax().text().to_string();
208 match param_text.split(':').nth(1) {
209 Some(it) => it[1..].to_string(),
210 None => param_text,
211 }
206 })); 212 }));
207 } 213 }
208 (has_self_param, res, res_types) 214 (has_self_param, res, res_types)
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs
index 914a8b471..de35c6711 100644
--- a/crates/ra_ide/src/display/navigation_target.rs
+++ b/crates/ra_ide/src/display/navigation_target.rs
@@ -376,16 +376,20 @@ impl ToNav for hir::Local {
376impl ToNav for hir::TypeParam { 376impl ToNav for hir::TypeParam {
377 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { 377 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
378 let src = self.source(db); 378 let src = self.source(db);
379 let range = match src.value { 379 let full_range = match &src.value {
380 Either::Left(it) => it.syntax().text_range(), 380 Either::Left(it) => it.syntax().text_range(),
381 Either::Right(it) => it.syntax().text_range(), 381 Either::Right(it) => it.syntax().text_range(),
382 }; 382 };
383 let focus_range = match &src.value {
384 Either::Left(_) => None,
385 Either::Right(it) => it.name().map(|it| it.syntax().text_range()),
386 };
383 NavigationTarget { 387 NavigationTarget {
384 file_id: src.file_id.original_file(db), 388 file_id: src.file_id.original_file(db),
385 name: self.name(db).to_string().into(), 389 name: self.name(db).to_string().into(),
386 kind: TYPE_PARAM, 390 kind: TYPE_PARAM,
387 full_range: range, 391 full_range,
388 focus_range: None, 392 focus_range,
389 container_name: None, 393 container_name: None,
390 description: None, 394 description: None,
391 docs: None, 395 docs: None,
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs
index 1dfca819d..150895abb 100644
--- a/crates/ra_ide/src/goto_definition.rs
+++ b/crates/ra_ide/src/goto_definition.rs
@@ -244,6 +244,39 @@ mod tests {
244 } 244 }
245 245
246 #[test] 246 #[test]
247 fn goto_def_for_use_alias() {
248 covers!(ra_ide_db::goto_def_for_use_alias);
249 check_goto(
250 "
251 //- /lib.rs
252 use foo as bar<|>;
253
254
255 //- /foo/lib.rs
256 #[macro_export]
257 macro_rules! foo { () => { () } }",
258 "SOURCE_FILE FileId(2) 0..50",
259 "#[macro_export]\nmacro_rules! foo { () => { () } }\n",
260 );
261 }
262
263 #[test]
264 fn goto_def_for_use_alias_foo_macro() {
265 check_goto(
266 "
267 //- /lib.rs
268 use foo::foo as bar<|>;
269
270 //- /foo/lib.rs
271 #[macro_export]
272 macro_rules! foo { () => { () } }
273 ",
274 "foo MACRO_CALL FileId(2) 0..49 29..32",
275 "#[macro_export]\nmacro_rules! foo { () => { () } }|foo",
276 );
277 }
278
279 #[test]
247 fn goto_def_for_macros_in_use_tree() { 280 fn goto_def_for_macros_in_use_tree() {
248 check_goto( 281 check_goto(
249 " 282 "
@@ -754,14 +787,14 @@ mod tests {
754 #[test] 787 #[test]
755 fn goto_for_type_param() { 788 fn goto_for_type_param() {
756 check_goto( 789 check_goto(
757 " 790 r#"
758 //- /lib.rs 791 //- /lib.rs
759 struct Foo<T> { 792 struct Foo<T: Clone> {
760 t: <|>T, 793 t: <|>T,
761 } 794 }
762 ", 795 "#,
763 "T TYPE_PARAM FileId(1) 11..12", 796 "T TYPE_PARAM FileId(1) 11..19 11..12",
764 "T", 797 "T: Clone|T",
765 ); 798 );
766 } 799 }
767 800
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index 54d318858..06d4f1c63 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -143,7 +143,7 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
143 ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), 143 ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path),
144 ModuleDef::BuiltinType(it) => Some(it.to_string()), 144 ModuleDef::BuiltinType(it) => Some(it.to_string()),
145 }, 145 },
146 Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display_truncated(db, None))), 146 Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display(db))),
147 Definition::TypeParam(_) | Definition::SelfType(_) => { 147 Definition::TypeParam(_) | Definition::SelfType(_) => {
148 // FIXME: Hover for generic param 148 // FIXME: Hover for generic param
149 None 149 None
@@ -208,7 +208,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
208 } 208 }
209 }?; 209 }?;
210 210
211 res.extend(Some(rust_code_markup(&ty.display_truncated(db, None)))); 211 res.extend(Some(rust_code_markup(&ty.display(db))));
212 let range = sema.original_range(&node).range; 212 let range = sema.original_range(&node).range;
213 Some(RangeInfo::new(range, res)) 213 Some(RangeInfo::new(range, res))
214} 214}
@@ -280,6 +280,47 @@ mod tests {
280 } 280 }
281 281
282 #[test] 282 #[test]
283 fn hover_shows_long_type_of_an_expression() {
284 check_hover_result(
285 r#"
286 //- /main.rs
287 struct Scan<A, B, C> {
288 a: A,
289 b: B,
290 c: C,
291 }
292
293 struct FakeIter<I> {
294 inner: I,
295 }
296
297 struct OtherStruct<T> {
298 i: T,
299 }
300
301 enum FakeOption<T> {
302 Some(T),
303 None,
304 }
305
306 fn scan<A, B, C>(a: A, b: B, c: C) -> FakeIter<Scan<OtherStruct<A>, B, C>> {
307 FakeIter { inner: Scan { a, b, c } }
308 }
309
310 fn main() {
311 let num: i32 = 55;
312 let closure = |memo: &mut u32, value: &u32, _another: &mut u32| -> FakeOption<u32> {
313 FakeOption::Some(*memo + value)
314 };
315 let number = 5u32;
316 let mut iter<|> = scan(OtherStruct { i: num }, closure, number);
317 }
318 "#,
319 &["FakeIter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> FakeOption<u32>, u32>>"],
320 );
321 }
322
323 #[test]
283 fn hover_shows_fn_signature() { 324 fn hover_shows_fn_signature() {
284 // Single file with result 325 // Single file with result
285 check_hover_result( 326 check_hover_result(
@@ -405,7 +446,7 @@ mod tests {
405 } 446 }
406 447
407 #[test] 448 #[test]
408 fn hover_omits_default_generic_types() { 449 fn hover_default_generic_types() {
409 check_hover_result( 450 check_hover_result(
410 r#" 451 r#"
411//- /main.rs 452//- /main.rs
@@ -417,7 +458,7 @@ struct Test<K, T = u8> {
417fn main() { 458fn main() {
418 let zz<|> = Test { t: 23, k: 33 }; 459 let zz<|> = Test { t: 23, k: 33 };
419}"#, 460}"#,
420 &["Test<i32>"], 461 &["Test<i32, u8>"],
421 ); 462 );
422 } 463 }
423 464
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs
index 98483df32..b391f903a 100644
--- a/crates/ra_ide/src/inlay_hints.rs
+++ b/crates/ra_ide/src/inlay_hints.rs
@@ -9,6 +9,7 @@ use ra_syntax::{
9}; 9};
10 10
11use crate::{FileId, FunctionSignature}; 11use crate::{FileId, FunctionSignature};
12use stdx::to_lower_snake_case;
12 13
13#[derive(Clone, Debug, PartialEq, Eq)] 14#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct InlayHintsConfig { 15pub struct InlayHintsConfig {
@@ -144,7 +145,7 @@ fn get_param_name_hints(
144 .iter() 145 .iter()
145 .skip(n_params_to_skip) 146 .skip(n_params_to_skip)
146 .zip(args) 147 .zip(args)
147 .filter(|(param, arg)| should_show_param_hint(&fn_signature, param, &arg)) 148 .filter(|(param, arg)| should_show_param_name_hint(sema, &fn_signature, param, &arg))
148 .map(|(param_name, arg)| InlayHint { 149 .map(|(param_name, arg)| InlayHint {
149 range: arg.syntax().text_range(), 150 range: arg.syntax().text_range(),
150 kind: InlayKind::ParameterHint, 151 kind: InlayKind::ParameterHint,
@@ -181,7 +182,7 @@ fn get_bind_pat_hints(
181 182
182fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ty: &Type) -> bool { 183fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ty: &Type) -> bool {
183 if let Some(Adt::Enum(enum_data)) = pat_ty.as_adt() { 184 if let Some(Adt::Enum(enum_data)) = pat_ty.as_adt() {
184 let pat_text = bind_pat.syntax().to_string(); 185 let pat_text = bind_pat.to_string();
185 enum_data 186 enum_data
186 .variants(db) 187 .variants(db)
187 .into_iter() 188 .into_iter()
@@ -198,7 +199,7 @@ fn should_not_display_type_hint(db: &RootDatabase, bind_pat: &ast::BindPat, pat_
198 } 199 }
199 200
200 if let Some(Adt::Struct(s)) = pat_ty.as_adt() { 201 if let Some(Adt::Struct(s)) = pat_ty.as_adt() {
201 if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.syntax().to_string() { 202 if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.to_string() {
202 return true; 203 return true;
203 } 204 }
204 } 205 }
@@ -230,15 +231,16 @@ fn should_not_display_type_hint(db: &RootDatabase, bind_pat: &ast::BindPat, pat_
230 false 231 false
231} 232}
232 233
233fn should_show_param_hint( 234fn should_show_param_name_hint(
235 sema: &Semantics<RootDatabase>,
234 fn_signature: &FunctionSignature, 236 fn_signature: &FunctionSignature,
235 param_name: &str, 237 param_name: &str,
236 argument: &ast::Expr, 238 argument: &ast::Expr,
237) -> bool { 239) -> bool {
240 let param_name = param_name.trim_start_matches('_');
238 if param_name.is_empty() 241 if param_name.is_empty()
239 || is_argument_similar_to_param(argument, param_name) 242 || Some(param_name) == fn_signature.name.as_ref().map(|s| s.trim_start_matches('_'))
240 || Some(param_name.trim_start_matches('_')) 243 || is_argument_similar_to_param_name(sema, argument, param_name)
241 == fn_signature.name.as_ref().map(|s| s.trim_start_matches('_'))
242 { 244 {
243 return false; 245 return false;
244 } 246 }
@@ -254,20 +256,42 @@ fn should_show_param_hint(
254 parameters_len != 1 || !is_obvious_param(param_name) 256 parameters_len != 1 || !is_obvious_param(param_name)
255} 257}
256 258
257fn is_argument_similar_to_param(argument: &ast::Expr, param_name: &str) -> bool { 259fn is_argument_similar_to_param_name(
258 let argument_string = remove_ref(argument.clone()).syntax().to_string(); 260 sema: &Semantics<RootDatabase>,
259 let param_name = param_name.trim_start_matches('_'); 261 argument: &ast::Expr,
260 let argument_string = argument_string.trim_start_matches('_'); 262 param_name: &str,
261 argument_string.starts_with(&param_name) || argument_string.ends_with(&param_name) 263) -> bool {
264 if is_enum_name_similar_to_param_name(sema, argument, param_name) {
265 return true;
266 }
267 match get_string_representation(argument) {
268 None => false,
269 Some(repr) => {
270 let argument_string = repr.trim_start_matches('_');
271 argument_string.starts_with(param_name) || argument_string.ends_with(param_name)
272 }
273 }
274}
275
276fn is_enum_name_similar_to_param_name(
277 sema: &Semantics<RootDatabase>,
278 argument: &ast::Expr,
279 param_name: &str,
280) -> bool {
281 match sema.type_of_expr(argument).and_then(|t| t.as_adt()) {
282 Some(Adt::Enum(e)) => to_lower_snake_case(&e.name(sema.db).to_string()) == param_name,
283 _ => false,
284 }
262} 285}
263 286
264fn remove_ref(expr: ast::Expr) -> ast::Expr { 287fn get_string_representation(expr: &ast::Expr) -> Option<String> {
265 if let ast::Expr::RefExpr(ref_expr) = &expr { 288 match expr {
266 if let Some(inner) = ref_expr.expr() { 289 ast::Expr::MethodCallExpr(method_call_expr) => {
267 return inner; 290 Some(method_call_expr.name_ref()?.to_string())
268 } 291 }
292 ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?),
293 _ => Some(expr.to_string()),
269 } 294 }
270 expr
271} 295}
272 296
273fn is_obvious_param(param_name: &str) -> bool { 297fn is_obvious_param(param_name: &str) -> bool {
@@ -1073,6 +1097,12 @@ struct TestVarContainer {
1073 test_var: i32, 1097 test_var: i32,
1074} 1098}
1075 1099
1100impl TestVarContainer {
1101 fn test_var(&self) -> i32 {
1102 self.test_var
1103 }
1104}
1105
1076struct Test {} 1106struct Test {}
1077 1107
1078impl Test { 1108impl Test {
@@ -1098,10 +1128,15 @@ struct Param {}
1098fn different_order(param: &Param) {} 1128fn different_order(param: &Param) {}
1099fn different_order_mut(param: &mut Param) {} 1129fn different_order_mut(param: &mut Param) {}
1100fn has_underscore(_param: bool) {} 1130fn has_underscore(_param: bool) {}
1131fn enum_matches_param_name(completion_kind: CompletionKind) {}
1101 1132
1102fn twiddle(twiddle: bool) {} 1133fn twiddle(twiddle: bool) {}
1103fn doo(_doo: bool) {} 1134fn doo(_doo: bool) {}
1104 1135
1136enum CompletionKind {
1137 Keyword,
1138}
1139
1105fn main() { 1140fn main() {
1106 let container: TestVarContainer = TestVarContainer { test_var: 42 }; 1141 let container: TestVarContainer = TestVarContainer { test_var: 42 };
1107 let test: Test = Test {}; 1142 let test: Test = Test {};
@@ -1114,18 +1149,21 @@ fn main() {
1114 let test_var: i32 = 55; 1149 let test_var: i32 = 55;
1115 test_processed.no_hints_expected(22, test_var); 1150 test_processed.no_hints_expected(22, test_var);
1116 test_processed.no_hints_expected(33, container.test_var); 1151 test_processed.no_hints_expected(33, container.test_var);
1152 test_processed.no_hints_expected(44, container.test_var());
1117 test_processed.frob(false); 1153 test_processed.frob(false);
1118 1154
1119 twiddle(true); 1155 twiddle(true);
1120 doo(true); 1156 doo(true);
1121 1157
1122 let param_begin: Param = Param {}; 1158 let mut param_begin: Param = Param {};
1123 different_order(&param_begin); 1159 different_order(&param_begin);
1124 different_order(&mut param_begin); 1160 different_order(&mut param_begin);
1125 1161
1126 let param: bool = true; 1162 let param: bool = true;
1127 has_underscore(param); 1163 has_underscore(param);
1128 1164
1165 enum_matches_param_name(CompletionKind::Keyword);
1166
1129 let a: f64 = 7.0; 1167 let a: f64 = 7.0;
1130 let b: f64 = 4.0; 1168 let b: f64 = 4.0;
1131 let _: f64 = a.div_euclid(b); 1169 let _: f64 = a.div_euclid(b);
diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs
index 63fd6b3e4..d3af780c4 100644
--- a/crates/ra_ide/src/join_lines.rs
+++ b/crates/ra_ide/src/join_lines.rs
@@ -569,7 +569,11 @@ fn foo() {
569 let (sel, before) = extract_range(before); 569 let (sel, before) = extract_range(before);
570 let parse = SourceFile::parse(&before); 570 let parse = SourceFile::parse(&before);
571 let result = join_lines(&parse.tree(), sel); 571 let result = join_lines(&parse.tree(), sel);
572 let actual = result.apply(&before); 572 let actual = {
573 let mut actual = before.to_string();
574 result.apply(&mut actual);
575 actual
576 };
573 assert_eq_text!(after, &actual); 577 assert_eq_text!(after, &actual);
574 } 578 }
575 579
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 09f602fe1..915199bd8 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -16,7 +16,6 @@ macro_rules! eprintln {
16} 16}
17 17
18pub mod mock_analysis; 18pub mod mock_analysis;
19mod source_change;
20 19
21mod prime_caches; 20mod prime_caches;
22mod status; 21mod status;
@@ -32,7 +31,6 @@ mod syntax_highlighting;
32mod parent_module; 31mod parent_module;
33mod references; 32mod references;
34mod impls; 33mod impls;
35mod assists;
36mod diagnostics; 34mod diagnostics;
37mod syntax_tree; 35mod syntax_tree;
38mod folding_ranges; 36mod folding_ranges;
@@ -65,7 +63,6 @@ use ra_syntax::{SourceFile, TextRange, TextSize};
65use crate::display::ToNav; 63use crate::display::ToNav;
66 64
67pub use crate::{ 65pub use crate::{
68 assists::{Assist, AssistId},
69 call_hierarchy::CallItem, 66 call_hierarchy::CallItem,
70 completion::{ 67 completion::{
71 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat, 68 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat,
@@ -78,7 +75,6 @@ pub use crate::{
78 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, 75 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
79 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, 76 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
80 runnables::{Runnable, RunnableKind, TestId}, 77 runnables::{Runnable, RunnableKind, TestId},
81 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
82 ssr::SsrError, 78 ssr::SsrError,
83 syntax_highlighting::{ 79 syntax_highlighting::{
84 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange, 80 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange,
@@ -86,6 +82,7 @@ pub use crate::{
86}; 82};
87 83
88pub use hir::Documentation; 84pub use hir::Documentation;
85pub use ra_assists::AssistId;
89pub use ra_db::{ 86pub use ra_db::{
90 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, 87 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId,
91}; 88};
@@ -94,6 +91,7 @@ pub use ra_ide_db::{
94 line_index::{LineCol, LineIndex}, 91 line_index::{LineCol, LineIndex},
95 line_index_utils::translate_offset_with_edit, 92 line_index_utils::translate_offset_with_edit,
96 search::SearchScope, 93 search::SearchScope,
94 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
97 symbol_index::Query, 95 symbol_index::Query,
98 RootDatabase, 96 RootDatabase,
99}; 97};
@@ -135,10 +133,12 @@ pub struct AnalysisHost {
135 db: RootDatabase, 133 db: RootDatabase,
136} 134}
137 135
138impl Default for AnalysisHost { 136#[derive(Debug)]
139 fn default() -> AnalysisHost { 137pub struct Assist {
140 AnalysisHost::new(None) 138 pub id: AssistId,
141 } 139 pub label: String,
140 pub group_label: Option<String>,
141 pub source_change: SourceChange,
142} 142}
143 143
144impl AnalysisHost { 144impl AnalysisHost {
@@ -188,6 +188,12 @@ impl AnalysisHost {
188 } 188 }
189} 189}
190 190
191impl Default for AnalysisHost {
192 fn default() -> AnalysisHost {
193 AnalysisHost::new(None)
194 }
195}
196
191/// Analysis is a snapshot of a world state at a moment in time. It is the main 197/// Analysis is a snapshot of a world state at a moment in time. It is the main
192/// entry point for asking semantic information about the world. When the world 198/// entry point for asking semantic information about the world. When the world
193/// state is advanced using `AnalysisHost::apply_change` method, all existing 199/// state is advanced using `AnalysisHost::apply_change` method, all existing
@@ -296,7 +302,7 @@ impl Analysis {
296 file_id: frange.file_id, 302 file_id: frange.file_id,
297 edit: join_lines::join_lines(&parse.tree(), frange.range), 303 edit: join_lines::join_lines(&parse.tree(), frange.range),
298 }; 304 };
299 SourceChange::source_file_edit("join lines", file_edit) 305 SourceChange::source_file_edit("Join lines", file_edit)
300 }) 306 })
301 } 307 }
302 308
@@ -465,7 +471,17 @@ impl Analysis {
465 /// Computes assists (aka code actions aka intentions) for the given 471 /// Computes assists (aka code actions aka intentions) for the given
466 /// position. 472 /// position.
467 pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> { 473 pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> {
468 self.with_db(|db| assists::assists(db, frange)) 474 self.with_db(|db| {
475 ra_assists::Assist::resolved(db, frange)
476 .into_iter()
477 .map(|assist| Assist {
478 id: assist.assist.id,
479 label: assist.assist.label,
480 group_label: assist.assist.group.map(|it| it.0),
481 source_change: assist.source_change,
482 })
483 .collect()
484 })
469 } 485 }
470 486
471 /// Computes the set of diagnostics for the given file. 487 /// Computes the set of diagnostics for the given file.
@@ -490,7 +506,7 @@ impl Analysis {
490 ) -> Cancelable<Result<SourceChange, SsrError>> { 506 ) -> Cancelable<Result<SourceChange, SsrError>> {
491 self.with_db(|db| { 507 self.with_db(|db| {
492 let edits = ssr::parse_search_replace(query, parse_only, db)?; 508 let edits = ssr::parse_search_replace(query, parse_only, db)?;
493 Ok(SourceChange::source_file_edits("ssr", edits)) 509 Ok(SourceChange::source_file_edits("Structural Search Replace", edits))
494 }) 510 })
495 } 511 }
496 512
diff --git a/crates/ra_ide/src/marks.rs b/crates/ra_ide/src/marks.rs
index bea30fe2a..51ca4dde3 100644
--- a/crates/ra_ide/src/marks.rs
+++ b/crates/ra_ide/src/marks.rs
@@ -11,4 +11,6 @@ test_utils::marks!(
11 self_fulfilling_completion 11 self_fulfilling_completion
12 test_struct_field_completion_in_func_call 12 test_struct_field_completion_in_func_call
13 test_struct_field_completion_in_record_lit 13 test_struct_field_completion_in_record_lit
14 test_rename_struct_field_for_shorthand
15 test_rename_local_for_field_shorthand
14); 16);
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index fd17bc9f2..2cbb82c1a 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -7,14 +7,13 @@ use ra_syntax::{
7 algo::find_node_at_offset, ast, lex_single_valid_syntax_kind, AstNode, SyntaxKind, SyntaxNode, 7 algo::find_node_at_offset, ast, lex_single_valid_syntax_kind, AstNode, SyntaxKind, SyntaxNode,
8}; 8};
9use ra_text_edit::TextEdit; 9use ra_text_edit::TextEdit;
10use test_utils::tested_by;
10 11
11use crate::{ 12use crate::{
12 FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, SourceChange, 13 references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind,
13 SourceFileEdit, TextRange, 14 SourceChange, SourceFileEdit, TextRange,
14}; 15};
15 16
16use super::find_all_refs;
17
18pub(crate) fn rename( 17pub(crate) fn rename(
19 db: &RootDatabase, 18 db: &RootDatabase,
20 position: FilePosition, 19 position: FilePosition,
@@ -52,11 +51,13 @@ fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFil
52 let file_id = reference.file_range.file_id; 51 let file_id = reference.file_range.file_id;
53 let range = match reference.kind { 52 let range = match reference.kind {
54 ReferenceKind::FieldShorthandForField => { 53 ReferenceKind::FieldShorthandForField => {
54 tested_by!(test_rename_struct_field_for_shorthand);
55 replacement_text.push_str(new_name); 55 replacement_text.push_str(new_name);
56 replacement_text.push_str(": "); 56 replacement_text.push_str(": ");
57 TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) 57 TextRange::new(reference.file_range.range.start(), reference.file_range.range.start())
58 } 58 }
59 ReferenceKind::FieldShorthandForLocal => { 59 ReferenceKind::FieldShorthandForLocal => {
60 tested_by!(test_rename_local_for_field_shorthand);
60 replacement_text.push_str(": "); 61 replacement_text.push_str(": ");
61 replacement_text.push_str(new_name); 62 replacement_text.push_str(new_name);
62 TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) 63 TextRange::new(reference.file_range.range.end(), reference.file_range.range.end())
@@ -121,7 +122,7 @@ fn rename_mod(
121 source_file_edits.extend(ref_edits); 122 source_file_edits.extend(ref_edits);
122 } 123 }
123 124
124 Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) 125 Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits))
125} 126}
126 127
127fn rename_reference( 128fn rename_reference(
@@ -140,14 +141,14 @@ fn rename_reference(
140 return None; 141 return None;
141 } 142 }
142 143
143 Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit))) 144 Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edit)))
144} 145}
145 146
146#[cfg(test)] 147#[cfg(test)]
147mod tests { 148mod tests {
148 use insta::assert_debug_snapshot; 149 use insta::assert_debug_snapshot;
149 use ra_text_edit::TextEditBuilder; 150 use ra_text_edit::TextEditBuilder;
150 use test_utils::assert_eq_text; 151 use test_utils::{assert_eq_text, covers};
151 152
152 use crate::{ 153 use crate::{
153 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, 154 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId,
@@ -379,6 +380,7 @@ mod tests {
379 380
380 #[test] 381 #[test]
381 fn test_rename_struct_field_for_shorthand() { 382 fn test_rename_struct_field_for_shorthand() {
383 covers!(test_rename_struct_field_for_shorthand);
382 test_rename( 384 test_rename(
383 r#" 385 r#"
384 struct Foo { 386 struct Foo {
@@ -408,6 +410,7 @@ mod tests {
408 410
409 #[test] 411 #[test]
410 fn test_rename_local_for_field_shorthand() { 412 fn test_rename_local_for_field_shorthand() {
413 covers!(test_rename_local_for_field_shorthand);
411 test_rename( 414 test_rename(
412 r#" 415 r#"
413 struct Foo { 416 struct Foo {
@@ -527,17 +530,17 @@ mod tests {
527 RangeInfo { 530 RangeInfo {
528 range: 4..7, 531 range: 4..7,
529 info: SourceChange { 532 info: SourceChange {
530 label: "rename", 533 label: "Rename",
531 source_file_edits: [ 534 source_file_edits: [
532 SourceFileEdit { 535 SourceFileEdit {
533 file_id: FileId( 536 file_id: FileId(
534 2, 537 2,
535 ), 538 ),
536 edit: TextEdit { 539 edit: TextEdit {
537 atoms: [ 540 indels: [
538 AtomTextEdit { 541 Indel {
539 delete: 4..7,
540 insert: "foo2", 542 insert: "foo2",
543 delete: 4..7,
541 }, 544 },
542 ], 545 ],
543 }, 546 },
@@ -579,17 +582,17 @@ mod tests {
579 RangeInfo { 582 RangeInfo {
580 range: 4..7, 583 range: 4..7,
581 info: SourceChange { 584 info: SourceChange {
582 label: "rename", 585 label: "Rename",
583 source_file_edits: [ 586 source_file_edits: [
584 SourceFileEdit { 587 SourceFileEdit {
585 file_id: FileId( 588 file_id: FileId(
586 1, 589 1,
587 ), 590 ),
588 edit: TextEdit { 591 edit: TextEdit {
589 atoms: [ 592 indels: [
590 AtomTextEdit { 593 Indel {
591 delete: 4..7,
592 insert: "foo2", 594 insert: "foo2",
595 delete: 4..7,
593 }, 596 },
594 ], 597 ],
595 }, 598 },
@@ -662,17 +665,17 @@ mod tests {
662 RangeInfo { 665 RangeInfo {
663 range: 8..11, 666 range: 8..11,
664 info: SourceChange { 667 info: SourceChange {
665 label: "rename", 668 label: "Rename",
666 source_file_edits: [ 669 source_file_edits: [
667 SourceFileEdit { 670 SourceFileEdit {
668 file_id: FileId( 671 file_id: FileId(
669 2, 672 2,
670 ), 673 ),
671 edit: TextEdit { 674 edit: TextEdit {
672 atoms: [ 675 indels: [
673 AtomTextEdit { 676 Indel {
674 delete: 8..11,
675 insert: "foo2", 677 insert: "foo2",
678 delete: 8..11,
676 }, 679 },
677 ], 680 ],
678 }, 681 },
@@ -682,10 +685,10 @@ mod tests {
682 1, 685 1,
683 ), 686 ),
684 edit: TextEdit { 687 edit: TextEdit {
685 atoms: [ 688 indels: [
686 AtomTextEdit { 689 Indel {
687 delete: 27..30,
688 insert: "foo2", 690 insert: "foo2",
691 delete: 27..30,
689 }, 692 },
690 ], 693 ],
691 }, 694 },
@@ -709,6 +712,68 @@ mod tests {
709 "###); 712 "###);
710 } 713 }
711 714
715 #[test]
716 fn test_enum_variant_from_module_1() {
717 test_rename(
718 r#"
719 mod foo {
720 pub enum Foo {
721 Bar<|>,
722 }
723 }
724
725 fn func(f: foo::Foo) {
726 match f {
727 foo::Foo::Bar => {}
728 }
729 }
730 "#,
731 "Baz",
732 r#"
733 mod foo {
734 pub enum Foo {
735 Baz,
736 }
737 }
738
739 fn func(f: foo::Foo) {
740 match f {
741 foo::Foo::Baz => {}
742 }
743 }
744 "#,
745 );
746 }
747
748 #[test]
749 fn test_enum_variant_from_module_2() {
750 test_rename(
751 r#"
752 mod foo {
753 pub struct Foo {
754 pub bar<|>: uint,
755 }
756 }
757
758 fn foo(f: foo::Foo) {
759 let _ = f.bar;
760 }
761 "#,
762 "baz",
763 r#"
764 mod foo {
765 pub struct Foo {
766 pub baz: uint,
767 }
768 }
769
770 fn foo(f: foo::Foo) {
771 let _ = f.baz;
772 }
773 "#,
774 );
775 }
776
712 fn test_rename(text: &str, new_name: &str, expected: &str) { 777 fn test_rename(text: &str, new_name: &str, expected: &str) {
713 let (analysis, position) = single_file_with_position(text); 778 let (analysis, position) = single_file_with_position(text);
714 let source_change = analysis.rename(position, new_name).unwrap(); 779 let source_change = analysis.rename(position, new_name).unwrap();
@@ -717,13 +782,13 @@ mod tests {
717 if let Some(change) = source_change { 782 if let Some(change) = source_change {
718 for edit in change.info.source_file_edits { 783 for edit in change.info.source_file_edits {
719 file_id = Some(edit.file_id); 784 file_id = Some(edit.file_id);
720 for atom in edit.edit.as_atoms() { 785 for indel in edit.edit.as_indels() {
721 text_edit_builder.replace(atom.delete, atom.insert.clone()); 786 text_edit_builder.replace(indel.delete, indel.insert.clone());
722 } 787 }
723 } 788 }
724 } 789 }
725 let result = 790 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string();
726 text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap()); 791 text_edit_builder.finish().apply(&mut result);
727 assert_eq_text!(expected, &*result); 792 assert_eq_text!(expected, &*result);
728 } 793 }
729} 794}
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index 38637c19c..fa8a9d92c 100644
--- a/crates/ra_ide/src/runnables.rs
+++ b/crates/ra_ide/src/runnables.rs
@@ -9,6 +9,7 @@ use ra_syntax::{
9}; 9};
10 10
11use crate::FileId; 11use crate::FileId;
12use ast::DocCommentsOwner;
12use std::fmt::Display; 13use std::fmt::Display;
13 14
14#[derive(Debug)] 15#[derive(Debug)]
@@ -37,6 +38,7 @@ pub enum RunnableKind {
37 Test { test_id: TestId, attr: TestAttr }, 38 Test { test_id: TestId, attr: TestAttr },
38 TestMod { path: String }, 39 TestMod { path: String },
39 Bench { test_id: TestId }, 40 Bench { test_id: TestId },
41 DocTest { test_id: TestId },
40 Bin, 42 Bin,
41} 43}
42 44
@@ -81,6 +83,8 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
81 RunnableKind::Test { test_id, attr } 83 RunnableKind::Test { test_id, attr }
82 } else if fn_def.has_atom_attr("bench") { 84 } else if fn_def.has_atom_attr("bench") {
83 RunnableKind::Bench { test_id } 85 RunnableKind::Bench { test_id }
86 } else if has_doc_test(&fn_def) {
87 RunnableKind::DocTest { test_id }
84 } else { 88 } else {
85 return None; 89 return None;
86 } 90 }
@@ -117,6 +121,10 @@ fn has_test_related_attribute(fn_def: &ast::FnDef) -> bool {
117 .any(|attribute_text| attribute_text.contains("test")) 121 .any(|attribute_text| attribute_text.contains("test"))
118} 122}
119 123
124fn has_doc_test(fn_def: &ast::FnDef) -> bool {
125 fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```"))
126}
127
120fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> { 128fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> {
121 let has_test_function = module 129 let has_test_function = module
122 .item_list()? 130 .item_list()?
@@ -195,6 +203,41 @@ mod tests {
195 } 203 }
196 204
197 #[test] 205 #[test]
206 fn test_runnables_doc_test() {
207 let (analysis, pos) = analysis_and_position(
208 r#"
209 //- /lib.rs
210 <|> //empty
211 fn main() {}
212
213 /// ```
214 /// let x = 5;
215 /// ```
216 fn foo() {}
217 "#,
218 );
219 let runnables = analysis.runnables(pos.file_id).unwrap();
220 assert_debug_snapshot!(&runnables,
221 @r###"
222 [
223 Runnable {
224 range: 1..21,
225 kind: Bin,
226 },
227 Runnable {
228 range: 22..64,
229 kind: DocTest {
230 test_id: Path(
231 "foo",
232 ),
233 },
234 },
235 ]
236 "###
237 );
238 }
239
240 #[test]
198 fn test_runnables_module() { 241 fn test_runnables_module() {
199 let (analysis, pos) = analysis_and_position( 242 let (analysis, pos) = analysis_and_position(
200 r#" 243 r#"
diff --git a/crates/ra_ide/src/source_change.rs b/crates/ra_ide/src/source_change.rs
deleted file mode 100644
index 71b0e8f75..000000000
--- a/crates/ra_ide/src/source_change.rs
+++ /dev/null
@@ -1,119 +0,0 @@
1//! This modules defines type to represent changes to the source code, that flow
2//! from the server to the client.
3//!
4//! It can be viewed as a dual for `AnalysisChange`.
5
6use ra_db::RelativePathBuf;
7use ra_text_edit::TextEdit;
8
9use crate::{FileId, FilePosition, SourceRootId, TextSize};
10
11#[derive(Debug)]
12pub struct SourceChange {
13 pub label: String,
14 pub source_file_edits: Vec<SourceFileEdit>,
15 pub file_system_edits: Vec<FileSystemEdit>,
16 pub cursor_position: Option<FilePosition>,
17}
18
19impl SourceChange {
20 /// Creates a new SourceChange with the given label
21 /// from the edits.
22 pub(crate) fn from_edits<L: Into<String>>(
23 label: L,
24 source_file_edits: Vec<SourceFileEdit>,
25 file_system_edits: Vec<FileSystemEdit>,
26 ) -> Self {
27 SourceChange {
28 label: label.into(),
29 source_file_edits,
30 file_system_edits,
31 cursor_position: None,
32 }
33 }
34
35 /// Creates a new SourceChange with the given label,
36 /// containing only the given `SourceFileEdits`.
37 pub(crate) fn source_file_edits<L: Into<String>>(label: L, edits: Vec<SourceFileEdit>) -> Self {
38 SourceChange {
39 label: label.into(),
40 source_file_edits: edits,
41 file_system_edits: vec![],
42 cursor_position: None,
43 }
44 }
45
46 /// Creates a new SourceChange with the given label,
47 /// containing only the given `FileSystemEdits`.
48 pub(crate) fn file_system_edits<L: Into<String>>(label: L, edits: Vec<FileSystemEdit>) -> Self {
49 SourceChange {
50 label: label.into(),
51 source_file_edits: vec![],
52 file_system_edits: edits,
53 cursor_position: None,
54 }
55 }
56
57 /// Creates a new SourceChange with the given label,
58 /// containing only a single `SourceFileEdit`.
59 pub(crate) fn source_file_edit<L: Into<String>>(label: L, edit: SourceFileEdit) -> Self {
60 SourceChange::source_file_edits(label, vec![edit])
61 }
62
63 /// Creates a new SourceChange with the given label
64 /// from the given `FileId` and `TextEdit`
65 pub(crate) fn source_file_edit_from<L: Into<String>>(
66 label: L,
67 file_id: FileId,
68 edit: TextEdit,
69 ) -> Self {
70 SourceChange::source_file_edit(label, SourceFileEdit { file_id, edit })
71 }
72
73 /// Creates a new SourceChange with the given label
74 /// from the given `FileId` and `TextEdit`
75 pub(crate) fn file_system_edit<L: Into<String>>(label: L, edit: FileSystemEdit) -> Self {
76 SourceChange::file_system_edits(label, vec![edit])
77 }
78
79 /// Sets the cursor position to the given `FilePosition`
80 pub(crate) fn with_cursor(mut self, cursor_position: FilePosition) -> Self {
81 self.cursor_position = Some(cursor_position);
82 self
83 }
84
85 /// Sets the cursor position to the given `FilePosition`
86 pub(crate) fn with_cursor_opt(mut self, cursor_position: Option<FilePosition>) -> Self {
87 self.cursor_position = cursor_position;
88 self
89 }
90}
91
92#[derive(Debug)]
93pub struct SourceFileEdit {
94 pub file_id: FileId,
95 pub edit: TextEdit,
96}
97
98#[derive(Debug)]
99pub enum FileSystemEdit {
100 CreateFile { source_root: SourceRootId, path: RelativePathBuf },
101 MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf },
102}
103
104pub(crate) struct SingleFileChange {
105 pub label: String,
106 pub edit: TextEdit,
107 pub cursor_position: Option<TextSize>,
108}
109
110impl SingleFileChange {
111 pub(crate) fn into_source_change(self, file_id: FileId) -> SourceChange {
112 SourceChange {
113 label: self.label,
114 source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }],
115 file_system_edits: Vec::new(),
116 cursor_position: self.cursor_position.map(|offset| FilePosition { file_id, offset }),
117 }
118 }
119}
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 7b93ff2d2..1873d1d0d 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -1,18 +1,18 @@
1//! structural search replace 1//! structural search replace
2 2
3use crate::source_change::SourceFileEdit; 3use std::{collections::HashMap, iter::once, str::FromStr};
4
4use ra_db::{SourceDatabase, SourceDatabaseExt}; 5use ra_db::{SourceDatabase, SourceDatabaseExt};
5use ra_ide_db::symbol_index::SymbolsDatabase; 6use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
6use ra_ide_db::RootDatabase;
7use ra_syntax::ast::make::try_expr_from_text;
8use ra_syntax::ast::{ 7use ra_syntax::ast::{
9 ArgList, AstToken, CallExpr, Comment, Expr, MethodCallExpr, RecordField, RecordLit, 8 make::try_expr_from_text, ArgList, AstToken, CallExpr, Comment, Expr, MethodCallExpr,
9 RecordField, RecordLit,
10}; 10};
11use ra_syntax::{AstNode, SyntaxElement, SyntaxKind, SyntaxNode}; 11use ra_syntax::{AstNode, SyntaxElement, SyntaxKind, SyntaxNode};
12use ra_text_edit::{TextEdit, TextEditBuilder}; 12use ra_text_edit::{TextEdit, TextEditBuilder};
13use rustc_hash::FxHashMap; 13use rustc_hash::FxHashMap;
14use std::collections::HashMap; 14
15use std::{iter::once, str::FromStr}; 15use crate::SourceFileEdit;
16 16
17#[derive(Debug, PartialEq)] 17#[derive(Debug, PartialEq)]
18pub struct SsrError(String); 18pub struct SsrError(String);
@@ -401,16 +401,22 @@ fn render_replace(
401 ignored_comments: &Vec<Comment>, 401 ignored_comments: &Vec<Comment>,
402 template: &SsrTemplate, 402 template: &SsrTemplate,
403) -> String { 403) -> String {
404 let mut builder = TextEditBuilder::default(); 404 let edit = {
405 for element in template.template.descendants() { 405 let mut builder = TextEditBuilder::default();
406 if let Some(var) = template.placeholders.get(&element) { 406 for element in template.template.descendants() {
407 builder.replace(element.text_range(), binding[var].to_string()) 407 if let Some(var) = template.placeholders.get(&element) {
408 builder.replace(element.text_range(), binding[var].to_string())
409 }
408 } 410 }
409 } 411 for comment in ignored_comments {
410 for comment in ignored_comments { 412 builder.insert(template.template.text_range().end(), comment.syntax().to_string())
411 builder.insert(template.template.text_range().end(), comment.syntax().to_string()) 413 }
412 } 414 builder.finish()
413 builder.finish().apply(&template.template.text().to_string()) 415 };
416
417 let mut text = template.template.text().to_string();
418 edit.apply(&mut text);
419 text
414} 420}
415 421
416#[cfg(test)] 422#[cfg(test)]
@@ -505,7 +511,9 @@ mod tests {
505 ); 511 );
506 512
507 let edit = replace(&matches, &query.template); 513 let edit = replace(&matches, &query.template);
508 assert_eq!(edit.apply(input), "fn main() { bar(1+2); }"); 514 let mut after = input.to_string();
515 edit.apply(&mut after);
516 assert_eq!(after, "fn main() { bar(1+2); }");
509 } 517 }
510 518
511 fn assert_ssr_transform(query: &str, input: &str, result: &str) { 519 fn assert_ssr_transform(query: &str, input: &str, result: &str) {
@@ -513,7 +521,9 @@ mod tests {
513 let code = SourceFile::parse(input).tree(); 521 let code = SourceFile::parse(input).tree();
514 let matches = find(&query.pattern, code.syntax()); 522 let matches = find(&query.pattern, code.syntax());
515 let edit = replace(&matches, &query.template); 523 let edit = replace(&matches, &query.template);
516 assert_eq!(edit.apply(input), result); 524 let mut after = input.to_string();
525 edit.apply(&mut after);
526 assert_eq!(after, result);
517 } 527 }
518 528
519 #[test] 529 #[test]
diff --git a/crates/ra_ide/src/test_utils.rs b/crates/ra_ide/src/test_utils.rs
index f14533e14..48c8fd1f4 100644
--- a/crates/ra_ide/src/test_utils.rs
+++ b/crates/ra_ide/src/test_utils.rs
@@ -13,7 +13,11 @@ pub fn check_action<F: Fn(&SourceFile, TextSize) -> Option<TextEdit>>(
13 let (before_cursor_pos, before) = extract_offset(before); 13 let (before_cursor_pos, before) = extract_offset(before);
14 let file = SourceFile::parse(&before).ok().unwrap(); 14 let file = SourceFile::parse(&before).ok().unwrap();
15 let result = f(&file, before_cursor_pos).expect("code action is not applicable"); 15 let result = f(&file, before_cursor_pos).expect("code action is not applicable");
16 let actual = result.apply(&before); 16 let actual = {
17 let mut actual = before.to_string();
18 result.apply(&mut actual);
19 actual
20 };
17 let actual_cursor_pos = 21 let actual_cursor_pos =
18 result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit"); 22 result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit");
19 let actual = add_cursor(&actual, actual_cursor_pos); 23 let actual = add_cursor(&actual, actual_cursor_pos);
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs
index 2a8b4327f..6f04f0be4 100644
--- a/crates/ra_ide/src/typing.rs
+++ b/crates/ra_ide/src/typing.rs
@@ -17,15 +17,16 @@ mod on_enter;
17 17
18use ra_db::{FilePosition, SourceDatabase}; 18use ra_db::{FilePosition, SourceDatabase};
19use ra_fmt::leading_indent; 19use ra_fmt::leading_indent;
20use ra_ide_db::RootDatabase; 20use ra_ide_db::{source_change::SingleFileChange, RootDatabase};
21use ra_syntax::{ 21use ra_syntax::{
22 algo::find_node_at_offset, 22 algo::find_node_at_offset,
23 ast::{self, AstToken}, 23 ast::{self, AstToken},
24 AstNode, SourceFile, TextRange, TextSize, 24 AstNode, SourceFile, TextRange, TextSize,
25}; 25};
26
26use ra_text_edit::TextEdit; 27use ra_text_edit::TextEdit;
27 28
28use crate::{source_change::SingleFileChange, SourceChange}; 29use crate::SourceChange;
29 30
30pub(crate) use on_enter::on_enter; 31pub(crate) use on_enter::on_enter;
31 32
@@ -142,10 +143,13 @@ mod tests {
142 fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> { 143 fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> {
143 let (offset, before) = extract_offset(before); 144 let (offset, before) = extract_offset(before);
144 let edit = TextEdit::insert(offset, char_typed.to_string()); 145 let edit = TextEdit::insert(offset, char_typed.to_string());
145 let before = edit.apply(&before); 146 let mut before = before.to_string();
147 edit.apply(&mut before);
146 let parse = SourceFile::parse(&before); 148 let parse = SourceFile::parse(&before);
147 on_char_typed_inner(&parse.tree(), offset, char_typed) 149 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {
148 .map(|it| (it.edit.apply(&before), it)) 150 it.edit.apply(&mut before);
151 (before.to_string(), it)
152 })
149 } 153 }
150 154
151 fn type_char(char_typed: char, before: &str, after: &str) { 155 fn type_char(char_typed: char, before: &str, after: &str) {
diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs
index 30c8c5572..78a40cc94 100644
--- a/crates/ra_ide/src/typing/on_enter.rs
+++ b/crates/ra_ide/src/typing/on_enter.rs
@@ -44,7 +44,7 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Sour
44 44
45 Some( 45 Some(
46 SourceChange::source_file_edit( 46 SourceChange::source_file_edit(
47 "on enter", 47 "On enter",
48 SourceFileEdit { edit, file_id: position.file_id }, 48 SourceFileEdit { edit, file_id: position.file_id },
49 ) 49 )
50 .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }), 50 .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }),
@@ -96,7 +96,8 @@ mod tests {
96 let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; 96 let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?;
97 97
98 assert_eq!(result.source_file_edits.len(), 1); 98 assert_eq!(result.source_file_edits.len(), 1);
99 let actual = result.source_file_edits[0].edit.apply(&before); 99 let mut actual = before.to_string();
100 result.source_file_edits[0].edit.apply(&mut actual);
100 let actual = add_cursor(&actual, result.cursor_position.unwrap().offset); 101 let actual = add_cursor(&actual, result.cursor_position.unwrap().offset);
101 Some(actual) 102 Some(actual)
102 } 103 }