aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src
diff options
context:
space:
mode:
authorBenjamin Coenen <[email protected]>2020-05-06 08:57:00 +0100
committerBenjamin Coenen <[email protected]>2020-05-06 08:57:00 +0100
commitc4d128e454448191c4b21d8e151c673e4c42376e (patch)
tree17cf5bbf429642c52708cd0d3c1d8885b63543f0 /crates/ra_ide/src
parent0bf02f5ccac99c91f10ef46bb06ff2ea316c382c (diff)
parent30eb458b4fa8adcecd8cbf731bd1cfa9a7a8b88b (diff)
Merge branch 'master' of github.com:rust-analyzer/rust-analyzer into fix_4311
Diffstat (limited to 'crates/ra_ide/src')
-rw-r--r--crates/ra_ide/src/assists.rs19
-rw-r--r--crates/ra_ide/src/completion/complete_qualified_path.rs2
-rw-r--r--crates/ra_ide/src/completion/complete_trait_impl.rs46
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs4
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs4
-rw-r--r--crates/ra_ide/src/diagnostics.rs36
-rw-r--r--crates/ra_ide/src/join_lines.rs6
-rw-r--r--crates/ra_ide/src/references/rename.rs42
-rw-r--r--crates/ra_ide/src/runnables.rs43
-rw-r--r--crates/ra_ide/src/source_change.rs4
-rw-r--r--crates/ra_ide/src/ssr.rs32
-rw-r--r--crates/ra_ide/src/test_utils.rs6
-rw-r--r--crates/ra_ide/src/typing.rs9
-rw-r--r--crates/ra_ide/src/typing/on_enter.rs5
14 files changed, 169 insertions, 89 deletions
diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs
index 2b5d11681..389339a03 100644
--- a/crates/ra_ide/src/assists.rs
+++ b/crates/ra_ide/src/assists.rs
@@ -1,6 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use ra_assists::{resolved_assists, AssistAction, AssistLabel}; 3use ra_assists::{resolved_assists, AssistAction};
4use ra_db::{FilePosition, FileRange}; 4use ra_db::{FilePosition, FileRange};
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6 6
@@ -21,27 +21,22 @@ pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> {
21 .into_iter() 21 .into_iter()
22 .map(|assist| { 22 .map(|assist| {
23 let file_id = frange.file_id; 23 let file_id = frange.file_id;
24 let assist_label = &assist.label;
25 Assist { 24 Assist {
26 id: assist_label.id, 25 id: assist.label.id,
27 label: assist_label.label.clone(), 26 label: assist.label.label.clone(),
28 group_label: assist.group_label.map(|it| it.0), 27 group_label: assist.label.group.map(|it| it.0),
29 source_change: action_to_edit(assist.action, file_id, assist_label), 28 source_change: action_to_edit(assist.action, file_id, assist.label.label.clone()),
30 } 29 }
31 }) 30 })
32 .collect() 31 .collect()
33} 32}
34 33
35fn action_to_edit( 34fn action_to_edit(action: AssistAction, file_id: FileId, label: String) -> SourceChange {
36 action: AssistAction,
37 file_id: FileId,
38 assist_label: &AssistLabel,
39) -> SourceChange {
40 let file_id = match action.file { 35 let file_id = match action.file {
41 ra_assists::AssistFile::TargetFile(it) => it, 36 ra_assists::AssistFile::TargetFile(it) => it,
42 _ => file_id, 37 _ => file_id,
43 }; 38 };
44 let file_edit = SourceFileEdit { file_id, edit: action.edit }; 39 let file_edit = SourceFileEdit { file_id, edit: action.edit };
45 SourceChange::source_file_edit(assist_label.label.clone(), file_edit) 40 SourceChange::source_file_edit(label, file_edit)
46 .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id })) 41 .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id }))
47} 42}
diff --git a/crates/ra_ide/src/completion/complete_qualified_path.rs b/crates/ra_ide/src/completion/complete_qualified_path.rs
index d9ea92ef8..7fcd22525 100644
--- a/crates/ra_ide/src/completion/complete_qualified_path.rs
+++ b/crates/ra_ide/src/completion/complete_qualified_path.rs
@@ -84,7 +84,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
84 }); 84 });
85 85
86 // Iterate assoc types separately 86 // Iterate assoc types separately
87 ty.iterate_impl_items(ctx.db, krate, |item| { 87 ty.iterate_assoc_items(ctx.db, krate, |item| {
88 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)) {
89 return None; 89 return None;
90 } 90 }
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/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..383b23ac4 100644
--- a/crates/ra_ide/src/completion/completion_item.rs
+++ b/crates/ra_ide/src/completion/completion_item.rs
@@ -62,8 +62,8 @@ impl fmt::Debug for CompletionItem {
62 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 62 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63 let mut s = f.debug_struct("CompletionItem"); 63 let mut s = f.debug_struct("CompletionItem");
64 s.field("label", &self.label()).field("source_range", &self.source_range()); 64 s.field("label", &self.label()).field("source_range", &self.source_range());
65 if self.text_edit().as_atoms().len() == 1 { 65 if self.text_edit().as_indels().len() == 1 {
66 let atom = &self.text_edit().as_atoms()[0]; 66 let atom = &self.text_edit().as_indels()[0];
67 s.field("delete", &atom.delete); 67 s.field("delete", &atom.delete);
68 s.field("insert", &atom.insert); 68 s.field("insert", &atom.insert);
69 } else { 69 } 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/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/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index 916edaef2..0398d53bc 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -122,7 +122,7 @@ fn rename_mod(
122 source_file_edits.extend(ref_edits); 122 source_file_edits.extend(ref_edits);
123 } 123 }
124 124
125 Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) 125 Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits))
126} 126}
127 127
128fn rename_reference( 128fn rename_reference(
@@ -141,7 +141,7 @@ fn rename_reference(
141 return None; 141 return None;
142 } 142 }
143 143
144 Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit))) 144 Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edit)))
145} 145}
146 146
147#[cfg(test)] 147#[cfg(test)]
@@ -530,17 +530,17 @@ mod tests {
530 RangeInfo { 530 RangeInfo {
531 range: 4..7, 531 range: 4..7,
532 info: SourceChange { 532 info: SourceChange {
533 label: "rename", 533 label: "Rename",
534 source_file_edits: [ 534 source_file_edits: [
535 SourceFileEdit { 535 SourceFileEdit {
536 file_id: FileId( 536 file_id: FileId(
537 2, 537 2,
538 ), 538 ),
539 edit: TextEdit { 539 edit: TextEdit {
540 atoms: [ 540 indels: [
541 AtomTextEdit { 541 Indel {
542 delete: 4..7,
543 insert: "foo2", 542 insert: "foo2",
543 delete: 4..7,
544 }, 544 },
545 ], 545 ],
546 }, 546 },
@@ -582,17 +582,17 @@ mod tests {
582 RangeInfo { 582 RangeInfo {
583 range: 4..7, 583 range: 4..7,
584 info: SourceChange { 584 info: SourceChange {
585 label: "rename", 585 label: "Rename",
586 source_file_edits: [ 586 source_file_edits: [
587 SourceFileEdit { 587 SourceFileEdit {
588 file_id: FileId( 588 file_id: FileId(
589 1, 589 1,
590 ), 590 ),
591 edit: TextEdit { 591 edit: TextEdit {
592 atoms: [ 592 indels: [
593 AtomTextEdit { 593 Indel {
594 delete: 4..7,
595 insert: "foo2", 594 insert: "foo2",
595 delete: 4..7,
596 }, 596 },
597 ], 597 ],
598 }, 598 },
@@ -665,17 +665,17 @@ mod tests {
665 RangeInfo { 665 RangeInfo {
666 range: 8..11, 666 range: 8..11,
667 info: SourceChange { 667 info: SourceChange {
668 label: "rename", 668 label: "Rename",
669 source_file_edits: [ 669 source_file_edits: [
670 SourceFileEdit { 670 SourceFileEdit {
671 file_id: FileId( 671 file_id: FileId(
672 2, 672 2,
673 ), 673 ),
674 edit: TextEdit { 674 edit: TextEdit {
675 atoms: [ 675 indels: [
676 AtomTextEdit { 676 Indel {
677 delete: 8..11,
678 insert: "foo2", 677 insert: "foo2",
678 delete: 8..11,
679 }, 679 },
680 ], 680 ],
681 }, 681 },
@@ -685,10 +685,10 @@ mod tests {
685 1, 685 1,
686 ), 686 ),
687 edit: TextEdit { 687 edit: TextEdit {
688 atoms: [ 688 indels: [
689 AtomTextEdit { 689 Indel {
690 delete: 27..30,
691 insert: "foo2", 690 insert: "foo2",
691 delete: 27..30,
692 }, 692 },
693 ], 693 ],
694 }, 694 },
@@ -720,13 +720,13 @@ mod tests {
720 if let Some(change) = source_change { 720 if let Some(change) = source_change {
721 for edit in change.info.source_file_edits { 721 for edit in change.info.source_file_edits {
722 file_id = Some(edit.file_id); 722 file_id = Some(edit.file_id);
723 for atom in edit.edit.as_atoms() { 723 for indel in edit.edit.as_indels() {
724 text_edit_builder.replace(atom.delete, atom.insert.clone()); 724 text_edit_builder.replace(indel.delete, indel.insert.clone());
725 } 725 }
726 } 726 }
727 } 727 }
728 let result = 728 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string();
729 text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap()); 729 text_edit_builder.finish().apply(&mut result);
730 assert_eq_text!(expected, &*result); 730 assert_eq_text!(expected, &*result);
731 } 731 }
732} 732}
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
index 71b0e8f75..10afd7825 100644
--- a/crates/ra_ide/src/source_change.rs
+++ b/crates/ra_ide/src/source_change.rs
@@ -35,8 +35,10 @@ impl SourceChange {
35 /// Creates a new SourceChange with the given label, 35 /// Creates a new SourceChange with the given label,
36 /// containing only the given `SourceFileEdits`. 36 /// containing only the given `SourceFileEdits`.
37 pub(crate) fn source_file_edits<L: Into<String>>(label: L, edits: Vec<SourceFileEdit>) -> Self { 37 pub(crate) fn source_file_edits<L: Into<String>>(label: L, edits: Vec<SourceFileEdit>) -> Self {
38 let label = label.into();
39 assert!(label.starts_with(char::is_uppercase));
38 SourceChange { 40 SourceChange {
39 label: label.into(), 41 label: label,
40 source_file_edits: edits, 42 source_file_edits: edits,
41 file_system_edits: vec![], 43 file_system_edits: vec![],
42 cursor_position: None, 44 cursor_position: None,
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 7b93ff2d2..8bf52d0fa 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -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..a03da4693 100644
--- a/crates/ra_ide/src/typing.rs
+++ b/crates/ra_ide/src/typing.rs
@@ -142,10 +142,13 @@ mod tests {
142 fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> { 142 fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> {
143 let (offset, before) = extract_offset(before); 143 let (offset, before) = extract_offset(before);
144 let edit = TextEdit::insert(offset, char_typed.to_string()); 144 let edit = TextEdit::insert(offset, char_typed.to_string());
145 let before = edit.apply(&before); 145 let mut before = before.to_string();
146 edit.apply(&mut before);
146 let parse = SourceFile::parse(&before); 147 let parse = SourceFile::parse(&before);
147 on_char_typed_inner(&parse.tree(), offset, char_typed) 148 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {
148 .map(|it| (it.edit.apply(&before), it)) 149 it.edit.apply(&mut before);
150 (before.to_string(), it)
151 })
149 } 152 }
150 153
151 fn type_char(char_typed: char, before: &str, after: &str) { 154 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 }