aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/assist_ctx.rs71
-rw-r--r--crates/ra_assists/src/doc_tests.rs39
-rw-r--r--crates/ra_assists/src/handlers/add_custom_impl.rs7
-rw-r--r--crates/ra_assists/src/handlers/add_derive.rs6
-rw-r--r--crates/ra_assists/src/handlers/add_explicit_type.rs4
-rw-r--r--crates/ra_assists/src/handlers/add_from_impl_for_enum.rs4
-rw-r--r--crates/ra_assists/src/handlers/add_function.rs44
-rw-r--r--crates/ra_assists/src/handlers/add_impl.rs61
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs16
-rw-r--r--crates/ra_assists/src/handlers/add_new.rs7
-rw-r--r--crates/ra_assists/src/handlers/apply_demorgan.rs5
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs5
-rw-r--r--crates/ra_assists/src/handlers/change_return_type_to_result.rs971
-rw-r--r--crates/ra_assists/src/handlers/change_visibility.rs34
-rw-r--r--crates/ra_assists/src/handlers/early_return.rs161
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs7
-rw-r--r--crates/ra_assists/src/handlers/flip_binexpr.rs5
-rw-r--r--crates/ra_assists/src/handlers/flip_comma.rs5
-rw-r--r--crates/ra_assists/src/handlers/flip_trait_bound.rs6
-rw-r--r--crates/ra_assists/src/handlers/inline_local_variable.rs4
-rw-r--r--crates/ra_assists/src/handlers/introduce_variable.rs6
-rw-r--r--crates/ra_assists/src/handlers/invert_if.rs5
-rw-r--r--crates/ra_assists/src/handlers/merge_imports.rs5
-rw-r--r--crates/ra_assists/src/handlers/merge_match_arms.rs5
-rw-r--r--crates/ra_assists/src/handlers/move_bounds.rs57
-rw-r--r--crates/ra_assists/src/handlers/move_guard.rs9
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs18
-rw-r--r--crates/ra_assists/src/handlers/remove_dbg.rs6
-rw-r--r--crates/ra_assists/src/handlers/remove_mut.rs3
-rw-r--r--crates/ra_assists/src/handlers/reorder_fields.rs6
-rw-r--r--crates/ra_assists/src/handlers/replace_if_let_with_match.rs49
-rw-r--r--crates/ra_assists/src/handlers/replace_let_with_if_let.rs6
-rw-r--r--crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs4
-rw-r--r--crates/ra_assists/src/handlers/replace_unwrap_with_match.rs40
-rw-r--r--crates/ra_assists/src/handlers/split_import.rs6
-rw-r--r--crates/ra_assists/src/handlers/unwrap_block.rs6
-rw-r--r--crates/ra_assists/src/lib.rs195
-rw-r--r--crates/ra_assists/src/tests.rs164
-rw-r--r--crates/ra_assists/src/tests/generated.rs (renamed from crates/ra_assists/src/doc_tests/generated.rs)89
-rw-r--r--crates/ra_db/src/fixture.rs46
-rw-r--r--crates/ra_hir_ty/src/_match.rs29
-rw-r--r--crates/ra_ide/src/assists.rs42
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs3
-rw-r--r--crates/ra_ide/src/lib.rs38
-rw-r--r--crates/ra_ide/src/ssr.rs14
-rw-r--r--crates/ra_ide/src/typing.rs5
-rw-r--r--crates/ra_ide_db/src/lib.rs1
-rw-r--r--crates/ra_ide_db/src/source_change.rs (renamed from crates/ra_ide/src/source_change.rs)31
-rw-r--r--crates/ra_text_edit/src/lib.rs6
-rw-r--r--crates/test_utils/src/lib.rs104
-rw-r--r--docs/user/assists.md12
-rw-r--r--docs/user/readme.adoc2
-rw-r--r--editors/code/package.json2
-rw-r--r--xtask/src/codegen.rs2
-rw-r--r--xtask/src/codegen/gen_assists_docs.rs4
-rw-r--r--xtask/tests/tidy-tests/cli.rs32
-rw-r--r--xtask/tests/tidy.rs (renamed from xtask/tests/tidy-tests/main.rs)39
57 files changed, 1834 insertions, 719 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index 83dd270c6..871671de2 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -1,8 +1,11 @@
1//! This module defines `AssistCtx` -- the API surface that is exposed to assists. 1//! This module defines `AssistCtx` -- the API surface that is exposed to assists.
2use hir::Semantics; 2use hir::Semantics;
3use ra_db::FileRange; 3use ra_db::{FileId, FileRange};
4use ra_fmt::{leading_indent, reindent}; 4use ra_fmt::{leading_indent, reindent};
5use ra_ide_db::RootDatabase; 5use ra_ide_db::{
6 source_change::{SingleFileChange, SourceChange},
7 RootDatabase,
8};
6use ra_syntax::{ 9use ra_syntax::{
7 algo::{self, find_covering_element, find_node_at_offset, SyntaxRewriter}, 10 algo::{self, find_covering_element, find_node_at_offset, SyntaxRewriter},
8 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize, 11 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
@@ -10,7 +13,7 @@ use ra_syntax::{
10}; 13};
11use ra_text_edit::TextEditBuilder; 14use ra_text_edit::TextEditBuilder;
12 15
13use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist}; 16use crate::{AssistId, AssistLabel, GroupLabel, ResolvedAssist};
14 17
15#[derive(Clone, Debug)] 18#[derive(Clone, Debug)]
16pub(crate) struct Assist(pub(crate) Vec<AssistInfo>); 19pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);
@@ -19,16 +22,16 @@ pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);
19pub(crate) struct AssistInfo { 22pub(crate) struct AssistInfo {
20 pub(crate) label: AssistLabel, 23 pub(crate) label: AssistLabel,
21 pub(crate) group_label: Option<GroupLabel>, 24 pub(crate) group_label: Option<GroupLabel>,
22 pub(crate) action: Option<AssistAction>, 25 pub(crate) source_change: Option<SourceChange>,
23} 26}
24 27
25impl AssistInfo { 28impl AssistInfo {
26 fn new(label: AssistLabel) -> AssistInfo { 29 fn new(label: AssistLabel) -> AssistInfo {
27 AssistInfo { label, group_label: None, action: None } 30 AssistInfo { label, group_label: None, source_change: None }
28 } 31 }
29 32
30 fn resolved(self, action: AssistAction) -> AssistInfo { 33 fn resolved(self, source_change: SourceChange) -> AssistInfo {
31 AssistInfo { action: Some(action), ..self } 34 AssistInfo { source_change: Some(source_change), ..self }
32 } 35 }
33 36
34 fn with_group(self, group_label: GroupLabel) -> AssistInfo { 37 fn with_group(self, group_label: GroupLabel) -> AssistInfo {
@@ -37,7 +40,7 @@ impl AssistInfo {
37 40
38 pub(crate) fn into_resolved(self) -> Option<ResolvedAssist> { 41 pub(crate) fn into_resolved(self) -> Option<ResolvedAssist> {
39 let label = self.label; 42 let label = self.label;
40 self.action.map(|action| ResolvedAssist { label, action }) 43 self.source_change.map(|source_change| ResolvedAssist { label, source_change })
41 } 44 }
42} 45}
43 46
@@ -94,18 +97,19 @@ impl<'a> AssistCtx<'a> {
94 self, 97 self,
95 id: AssistId, 98 id: AssistId,
96 label: impl Into<String>, 99 label: impl Into<String>,
100 target: TextRange,
97 f: impl FnOnce(&mut ActionBuilder), 101 f: impl FnOnce(&mut ActionBuilder),
98 ) -> Option<Assist> { 102 ) -> Option<Assist> {
99 let label = AssistLabel::new(id, label.into(), None); 103 let label = AssistLabel::new(id, label.into(), None, target);
100 104 let change_label = label.label.clone();
101 let mut info = AssistInfo::new(label); 105 let mut info = AssistInfo::new(label);
102 if self.should_compute_edit { 106 if self.should_compute_edit {
103 let action = { 107 let source_change = {
104 let mut edit = ActionBuilder::new(&self); 108 let mut edit = ActionBuilder::new(&self);
105 f(&mut edit); 109 f(&mut edit);
106 edit.build() 110 edit.build(change_label)
107 }; 111 };
108 info = info.resolved(action) 112 info = info.resolved(source_change)
109 }; 113 };
110 114
111 Some(Assist(vec![info])) 115 Some(Assist(vec![info]))
@@ -152,18 +156,19 @@ impl<'a> AssistGroup<'a> {
152 &mut self, 156 &mut self,
153 id: AssistId, 157 id: AssistId,
154 label: impl Into<String>, 158 label: impl Into<String>,
159 target: TextRange,
155 f: impl FnOnce(&mut ActionBuilder), 160 f: impl FnOnce(&mut ActionBuilder),
156 ) { 161 ) {
157 let label = AssistLabel::new(id, label.into(), Some(self.group.clone())); 162 let label = AssistLabel::new(id, label.into(), Some(self.group.clone()), target);
158 163 let change_label = label.label.clone();
159 let mut info = AssistInfo::new(label).with_group(self.group.clone()); 164 let mut info = AssistInfo::new(label).with_group(self.group.clone());
160 if self.ctx.should_compute_edit { 165 if self.ctx.should_compute_edit {
161 let action = { 166 let source_change = {
162 let mut edit = ActionBuilder::new(&self.ctx); 167 let mut edit = ActionBuilder::new(&self.ctx);
163 f(&mut edit); 168 f(&mut edit);
164 edit.build() 169 edit.build(change_label)
165 }; 170 };
166 info = info.resolved(action) 171 info = info.resolved(source_change)
167 }; 172 };
168 173
169 self.assists.push(info) 174 self.assists.push(info)
@@ -181,8 +186,7 @@ impl<'a> AssistGroup<'a> {
181pub(crate) struct ActionBuilder<'a, 'b> { 186pub(crate) struct ActionBuilder<'a, 'b> {
182 edit: TextEditBuilder, 187 edit: TextEditBuilder,
183 cursor_position: Option<TextSize>, 188 cursor_position: Option<TextSize>,
184 target: Option<TextRange>, 189 file: FileId,
185 file: AssistFile,
186 ctx: &'a AssistCtx<'b>, 190 ctx: &'a AssistCtx<'b>,
187} 191}
188 192
@@ -191,8 +195,7 @@ impl<'a, 'b> ActionBuilder<'a, 'b> {
191 Self { 195 Self {
192 edit: TextEditBuilder::default(), 196 edit: TextEditBuilder::default(),
193 cursor_position: None, 197 cursor_position: None,
194 target: None, 198 file: ctx.frange.file_id,
195 file: AssistFile::default(),
196 ctx, 199 ctx,
197 } 200 }
198 } 201 }
@@ -237,14 +240,6 @@ impl<'a, 'b> ActionBuilder<'a, 'b> {
237 self.cursor_position = Some(offset) 240 self.cursor_position = Some(offset)
238 } 241 }
239 242
240 /// Specify that the assist should be active withing the `target` range.
241 ///
242 /// Target ranges are used to sort assists: the smaller the target range,
243 /// the more specific assist is, and so it should be sorted first.
244 pub(crate) fn target(&mut self, target: TextRange) {
245 self.target = Some(target)
246 }
247
248 /// Get access to the raw `TextEditBuilder`. 243 /// Get access to the raw `TextEditBuilder`.
249 pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { 244 pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
250 &mut self.edit 245 &mut self.edit
@@ -259,16 +254,16 @@ impl<'a, 'b> ActionBuilder<'a, 'b> {
259 algo::diff(&node, &new).into_text_edit(&mut self.edit) 254 algo::diff(&node, &new).into_text_edit(&mut self.edit)
260 } 255 }
261 256
262 pub(crate) fn set_file(&mut self, assist_file: AssistFile) { 257 pub(crate) fn set_file(&mut self, assist_file: FileId) {
263 self.file = assist_file 258 self.file = assist_file;
264 } 259 }
265 260
266 fn build(self) -> AssistAction { 261 fn build(self, change_label: String) -> SourceChange {
267 AssistAction { 262 let edit = self.edit.finish();
268 edit: self.edit.finish(), 263 if edit.is_empty() && self.cursor_position.is_none() {
269 cursor_position: self.cursor_position, 264 panic!("Only call `add_assist` if the assist can be applied")
270 target: self.target,
271 file: self.file,
272 } 265 }
266 SingleFileChange { label: change_label, edit, cursor_position: self.cursor_position }
267 .into_source_change(self.file)
273 } 268 }
274} 269}
diff --git a/crates/ra_assists/src/doc_tests.rs b/crates/ra_assists/src/doc_tests.rs
deleted file mode 100644
index f627f31dc..000000000
--- a/crates/ra_assists/src/doc_tests.rs
+++ /dev/null
@@ -1,39 +0,0 @@
1//! Each assist definition has a special comment, which specifies docs and
2//! example.
3//!
4//! We collect all the example and write the as tests in this module.
5
6mod generated;
7
8use ra_db::FileRange;
9use test_utils::{assert_eq_text, extract_range_or_offset};
10
11use crate::resolved_assists;
12
13fn check(assist_id: &str, before: &str, after: &str) {
14 let (selection, before) = extract_range_or_offset(before);
15 let (db, file_id) = crate::helpers::with_single_file(&before);
16 let frange = FileRange { file_id, range: selection.into() };
17
18 let assist = resolved_assists(&db, frange)
19 .into_iter()
20 .find(|assist| assist.label.id.0 == assist_id)
21 .unwrap_or_else(|| {
22 panic!(
23 "\n\nAssist is not applicable: {}\nAvailable assists: {}",
24 assist_id,
25 resolved_assists(&db, frange)
26 .into_iter()
27 .map(|assist| assist.label.id.0)
28 .collect::<Vec<_>>()
29 .join(", ")
30 )
31 });
32
33 let actual = {
34 let mut actual = before.clone();
35 assist.action.edit.apply(&mut actual);
36 actual
37 };
38 assert_eq_text!(after, &actual);
39}
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs
index 4ea26a550..869d4dc04 100644
--- a/crates/ra_assists/src/handlers/add_custom_impl.rs
+++ b/crates/ra_assists/src/handlers/add_custom_impl.rs
@@ -48,9 +48,8 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> {
48 let label = 48 let label =
49 format!("Add custom impl '{}' for '{}'", trait_token.text().as_str(), annotated_name); 49 format!("Add custom impl '{}' for '{}'", trait_token.text().as_str(), annotated_name);
50 50
51 ctx.add_assist(AssistId("add_custom_impl"), label, |edit| { 51 let target = attr.syntax().text_range();
52 edit.target(attr.syntax().text_range()); 52 ctx.add_assist(AssistId("add_custom_impl"), label, target, |edit| {
53
54 let new_attr_input = input 53 let new_attr_input = input
55 .syntax() 54 .syntax()
56 .descendants_with_tokens() 55 .descendants_with_tokens()
@@ -95,7 +94,7 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> {
95 94
96#[cfg(test)] 95#[cfg(test)]
97mod tests { 96mod tests {
98 use crate::helpers::{check_assist, check_assist_not_applicable}; 97 use crate::tests::{check_assist, check_assist_not_applicable};
99 98
100 use super::*; 99 use super::*;
101 100
diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/add_derive.rs
index 6254eb7c4..2a6bb1cae 100644
--- a/crates/ra_assists/src/handlers/add_derive.rs
+++ b/crates/ra_assists/src/handlers/add_derive.rs
@@ -27,7 +27,8 @@ use crate::{Assist, AssistCtx, AssistId};
27pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> { 27pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> {
28 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; 28 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
29 let node_start = derive_insertion_offset(&nominal)?; 29 let node_start = derive_insertion_offset(&nominal)?;
30 ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", |edit| { 30 let target = nominal.syntax().text_range();
31 ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", target, |edit| {
31 let derive_attr = nominal 32 let derive_attr = nominal
32 .attrs() 33 .attrs()
33 .filter_map(|x| x.as_simple_call()) 34 .filter_map(|x| x.as_simple_call())
@@ -41,7 +42,6 @@ pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> {
41 } 42 }
42 Some(tt) => tt.syntax().text_range().end() - TextSize::of(')'), 43 Some(tt) => tt.syntax().text_range().end() - TextSize::of(')'),
43 }; 44 };
44 edit.target(nominal.syntax().text_range());
45 edit.set_cursor(offset) 45 edit.set_cursor(offset)
46 }) 46 })
47} 47}
@@ -58,7 +58,7 @@ fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option<TextSize> {
58#[cfg(test)] 58#[cfg(test)]
59mod tests { 59mod tests {
60 use super::*; 60 use super::*;
61 use crate::helpers::{check_assist, check_assist_target}; 61 use crate::tests::{check_assist, check_assist_target};
62 62
63 #[test] 63 #[test]
64 fn add_derive_new() { 64 fn add_derive_new() {
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs
index bc313782b..a59ec16b2 100644
--- a/crates/ra_assists/src/handlers/add_explicit_type.rs
+++ b/crates/ra_assists/src/handlers/add_explicit_type.rs
@@ -62,8 +62,8 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> {
62 ctx.add_assist( 62 ctx.add_assist(
63 AssistId("add_explicit_type"), 63 AssistId("add_explicit_type"),
64 format!("Insert explicit type '{}'", new_type_string), 64 format!("Insert explicit type '{}'", new_type_string),
65 pat_range,
65 |edit| { 66 |edit| {
66 edit.target(pat_range);
67 if let Some(ascribed_ty) = ascribed_ty { 67 if let Some(ascribed_ty) = ascribed_ty {
68 edit.replace(ascribed_ty.syntax().text_range(), new_type_string); 68 edit.replace(ascribed_ty.syntax().text_range(), new_type_string);
69 } else { 69 } else {
@@ -77,7 +77,7 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> {
77mod tests { 77mod tests {
78 use super::*; 78 use super::*;
79 79
80 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 80 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
81 81
82 #[test] 82 #[test]
83 fn add_explicit_type_target() { 83 fn add_explicit_type_target() {
diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
index 49deb6701..81deb3dfa 100644
--- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
+++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
@@ -47,9 +47,11 @@ pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> {
47 return None; 47 return None;
48 } 48 }
49 49
50 let target = variant.syntax().text_range();
50 ctx.add_assist( 51 ctx.add_assist(
51 AssistId("add_from_impl_for_enum"), 52 AssistId("add_from_impl_for_enum"),
52 "Add From impl for this enum variant", 53 "Add From impl for this enum variant",
54 target,
53 |edit| { 55 |edit| {
54 let start_offset = variant.parent_enum().syntax().text_range().end(); 56 let start_offset = variant.parent_enum().syntax().text_range().end();
55 let mut buf = String::new(); 57 let mut buf = String::new();
@@ -97,7 +99,7 @@ fn existing_from_impl(
97mod tests { 99mod tests {
98 use super::*; 100 use super::*;
99 101
100 use crate::helpers::{check_assist, check_assist_not_applicable}; 102 use crate::tests::{check_assist, check_assist_not_applicable};
101 use test_utils::covers; 103 use test_utils::covers;
102 104
103 #[test] 105 #[test]
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs
index 6c7456579..278079665 100644
--- a/crates/ra_assists/src/handlers/add_function.rs
+++ b/crates/ra_assists/src/handlers/add_function.rs
@@ -3,9 +3,10 @@ use ra_syntax::{
3 SyntaxKind, SyntaxNode, TextSize, 3 SyntaxKind, SyntaxNode, TextSize,
4}; 4};
5 5
6use crate::{Assist, AssistCtx, AssistFile, AssistId}; 6use crate::{Assist, AssistCtx, AssistId};
7use ast::{edit::IndentLevel, ArgListOwner, ModuleItemOwner}; 7use ast::{edit::IndentLevel, ArgListOwner, ModuleItemOwner};
8use hir::HirDisplay; 8use hir::HirDisplay;
9use ra_db::FileId;
9use rustc_hash::{FxHashMap, FxHashSet}; 10use rustc_hash::{FxHashMap, FxHashSet};
10 11
11// Assist: add_function 12// Assist: add_function
@@ -57,14 +58,12 @@ pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> {
57 58
58 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; 59 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
59 60
60 ctx.add_assist(AssistId("add_function"), "Add function", |edit| { 61 let target = call.syntax().text_range();
61 edit.target(call.syntax().text_range()); 62 ctx.add_assist(AssistId("add_function"), "Add function", target, |edit| {
62 63 let function_template = function_builder.render();
63 if let Some(function_template) = function_builder.render() { 64 edit.set_file(function_template.file);
64 edit.set_file(function_template.file); 65 edit.set_cursor(function_template.cursor_offset);
65 edit.set_cursor(function_template.cursor_offset); 66 edit.insert(function_template.insert_offset, function_template.fn_def.to_string());
66 edit.insert(function_template.insert_offset, function_template.fn_def.to_string());
67 }
68 }) 67 })
69} 68}
70 69
@@ -72,7 +71,7 @@ struct FunctionTemplate {
72 insert_offset: TextSize, 71 insert_offset: TextSize,
73 cursor_offset: TextSize, 72 cursor_offset: TextSize,
74 fn_def: ast::SourceFile, 73 fn_def: ast::SourceFile,
75 file: AssistFile, 74 file: FileId,
76} 75}
77 76
78struct FunctionBuilder { 77struct FunctionBuilder {
@@ -80,7 +79,7 @@ struct FunctionBuilder {
80 fn_name: ast::Name, 79 fn_name: ast::Name,
81 type_params: Option<ast::TypeParamList>, 80 type_params: Option<ast::TypeParamList>,
82 params: ast::ParamList, 81 params: ast::ParamList,
83 file: AssistFile, 82 file: FileId,
84 needs_pub: bool, 83 needs_pub: bool,
85} 84}
86 85
@@ -94,7 +93,7 @@ impl FunctionBuilder {
94 target_module: Option<hir::InFile<hir::ModuleSource>>, 93 target_module: Option<hir::InFile<hir::ModuleSource>>,
95 ) -> Option<Self> { 94 ) -> Option<Self> {
96 let needs_pub = target_module.is_some(); 95 let needs_pub = target_module.is_some();
97 let mut file = AssistFile::default(); 96 let mut file = ctx.frange.file_id;
98 let target = if let Some(target_module) = target_module { 97 let target = if let Some(target_module) = target_module {
99 let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, target_module)?; 98 let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, target_module)?;
100 file = in_file; 99 file = in_file;
@@ -107,7 +106,7 @@ impl FunctionBuilder {
107 Some(Self { target, fn_name, type_params, params, file, needs_pub }) 106 Some(Self { target, fn_name, type_params, params, file, needs_pub })
108 } 107 }
109 108
110 fn render(self) -> Option<FunctionTemplate> { 109 fn render(self) -> FunctionTemplate {
111 let placeholder_expr = ast::make::expr_todo(); 110 let placeholder_expr = ast::make::expr_todo();
112 let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr)); 111 let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr));
113 let mut fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body); 112 let mut fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body);
@@ -133,15 +132,11 @@ impl FunctionBuilder {
133 } 132 }
134 }; 133 };
135 134
136 let cursor_offset_from_fn_start = fn_def 135 let placeholder_expr =
137 .syntax() 136 fn_def.syntax().descendants().find_map(ast::MacroCall::cast).unwrap();
138 .descendants() 137 let cursor_offset_from_fn_start = placeholder_expr.syntax().text_range().start();
139 .find_map(ast::MacroCall::cast)?
140 .syntax()
141 .text_range()
142 .start();
143 let cursor_offset = insert_offset + cursor_offset_from_fn_start; 138 let cursor_offset = insert_offset + cursor_offset_from_fn_start;
144 Some(FunctionTemplate { insert_offset, cursor_offset, fn_def, file: self.file }) 139 FunctionTemplate { insert_offset, cursor_offset, fn_def, file: self.file }
145 } 140 }
146} 141}
147 142
@@ -259,9 +254,8 @@ fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFu
259fn next_space_for_fn_in_module( 254fn next_space_for_fn_in_module(
260 db: &dyn hir::db::AstDatabase, 255 db: &dyn hir::db::AstDatabase,
261 module: hir::InFile<hir::ModuleSource>, 256 module: hir::InFile<hir::ModuleSource>,
262) -> Option<(AssistFile, GeneratedFunctionTarget)> { 257) -> Option<(FileId, GeneratedFunctionTarget)> {
263 let file = module.file_id.original_file(db); 258 let file = module.file_id.original_file(db);
264 let assist_file = AssistFile::TargetFile(file);
265 let assist_item = match module.value { 259 let assist_item = match module.value {
266 hir::ModuleSource::SourceFile(it) => { 260 hir::ModuleSource::SourceFile(it) => {
267 if let Some(last_item) = it.items().last() { 261 if let Some(last_item) = it.items().last() {
@@ -278,12 +272,12 @@ fn next_space_for_fn_in_module(
278 } 272 }
279 } 273 }
280 }; 274 };
281 Some((assist_file, assist_item)) 275 Some((file, assist_item))
282} 276}
283 277
284#[cfg(test)] 278#[cfg(test)]
285mod tests { 279mod tests {
286 use crate::helpers::{check_assist, check_assist_not_applicable}; 280 use crate::tests::{check_assist, check_assist_not_applicable};
287 281
288 use super::*; 282 use super::*;
289 283
diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs
index d26f8b93d..557344ebb 100644
--- a/crates/ra_assists/src/handlers/add_impl.rs
+++ b/crates/ra_assists/src/handlers/add_impl.rs
@@ -28,39 +28,46 @@ use crate::{Assist, AssistCtx, AssistId};
28pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> { 28pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> {
29 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; 29 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
30 let name = nominal.name()?; 30 let name = nominal.name()?;
31 ctx.add_assist(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), |edit| { 31 let target = nominal.syntax().text_range();
32 edit.target(nominal.syntax().text_range()); 32 ctx.add_assist(
33 let type_params = nominal.type_param_list(); 33 AssistId("add_impl"),
34 let start_offset = nominal.syntax().text_range().end(); 34 format!("Implement {}", name.text().as_str()),
35 let mut buf = String::new(); 35 target,
36 buf.push_str("\n\nimpl"); 36 |edit| {
37 if let Some(type_params) = &type_params { 37 let type_params = nominal.type_param_list();
38 format_to!(buf, "{}", type_params.syntax()); 38 let start_offset = nominal.syntax().text_range().end();
39 } 39 let mut buf = String::new();
40 buf.push_str(" "); 40 buf.push_str("\n\nimpl");
41 buf.push_str(name.text().as_str()); 41 if let Some(type_params) = &type_params {
42 if let Some(type_params) = type_params { 42 format_to!(buf, "{}", type_params.syntax());
43 let lifetime_params = type_params 43 }
44 .lifetime_params() 44 buf.push_str(" ");
45 .filter_map(|it| it.lifetime_token()) 45 buf.push_str(name.text().as_str());
46 .map(|it| it.text().clone()); 46 if let Some(type_params) = type_params {
47 let type_params = 47 let lifetime_params = type_params
48 type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone()); 48 .lifetime_params()
49 .filter_map(|it| it.lifetime_token())
50 .map(|it| it.text().clone());
51 let type_params = type_params
52 .type_params()
53 .filter_map(|it| it.name())
54 .map(|it| it.text().clone());
49 55
50 let generic_params = lifetime_params.chain(type_params).sep_by(", "); 56 let generic_params = lifetime_params.chain(type_params).sep_by(", ");
51 format_to!(buf, "<{}>", generic_params) 57 format_to!(buf, "<{}>", generic_params)
52 } 58 }
53 buf.push_str(" {\n"); 59 buf.push_str(" {\n");
54 edit.set_cursor(start_offset + TextSize::of(&buf)); 60 edit.set_cursor(start_offset + TextSize::of(&buf));
55 buf.push_str("\n}"); 61 buf.push_str("\n}");
56 edit.insert(start_offset, buf); 62 edit.insert(start_offset, buf);
57 }) 63 },
64 )
58} 65}
59 66
60#[cfg(test)] 67#[cfg(test)]
61mod tests { 68mod tests {
62 use super::*; 69 use super::*;
63 use crate::helpers::{check_assist, check_assist_target}; 70 use crate::tests::{check_assist, check_assist_target};
64 71
65 #[test] 72 #[test]
66 fn test_add_impl() { 73 fn test_add_impl() {
diff --git a/crates/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
index e47feda71..7df786590 100644
--- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
@@ -107,10 +107,10 @@ fn add_missing_impl_members_inner(
107 label: &'static str, 107 label: &'static str,
108) -> Option<Assist> { 108) -> Option<Assist> {
109 let _p = ra_prof::profile("add_missing_impl_members_inner"); 109 let _p = ra_prof::profile("add_missing_impl_members_inner");
110 let impl_node = ctx.find_node_at_offset::<ast::ImplDef>()?; 110 let impl_def = ctx.find_node_at_offset::<ast::ImplDef>()?;
111 let impl_item_list = impl_node.item_list()?; 111 let impl_item_list = impl_def.item_list()?;
112 112
113 let trait_ = resolve_target_trait(&ctx.sema, &impl_node)?; 113 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
114 114
115 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> { 115 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
116 match item { 116 match item {
@@ -121,7 +121,7 @@ fn add_missing_impl_members_inner(
121 .map(|it| it.text().clone()) 121 .map(|it| it.text().clone())
122 }; 122 };
123 123
124 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_node) 124 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
125 .iter() 125 .iter()
126 .map(|i| match i { 126 .map(|i| match i {
127 hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value), 127 hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value),
@@ -143,13 +143,13 @@ fn add_missing_impl_members_inner(
143 } 143 }
144 144
145 let sema = ctx.sema; 145 let sema = ctx.sema;
146 146 let target = impl_def.syntax().text_range();
147 ctx.add_assist(AssistId(assist_id), label, |edit| { 147 ctx.add_assist(AssistId(assist_id), label, target, |edit| {
148 let n_existing_items = impl_item_list.assoc_items().count(); 148 let n_existing_items = impl_item_list.assoc_items().count();
149 let source_scope = sema.scope_for_def(trait_); 149 let source_scope = sema.scope_for_def(trait_);
150 let target_scope = sema.scope(impl_item_list.syntax()); 150 let target_scope = sema.scope(impl_item_list.syntax());
151 let ast_transform = QualifyPaths::new(&target_scope, &source_scope) 151 let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
152 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_node)); 152 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def));
153 let items = missing_items 153 let items = missing_items
154 .into_iter() 154 .into_iter()
155 .map(|it| ast_transform::apply(&*ast_transform, it)) 155 .map(|it| ast_transform::apply(&*ast_transform, it))
@@ -181,7 +181,7 @@ fn add_body(fn_def: ast::FnDef) -> ast::FnDef {
181 181
182#[cfg(test)] 182#[cfg(test)]
183mod tests { 183mod tests {
184 use crate::helpers::{check_assist, check_assist_not_applicable}; 184 use crate::tests::{check_assist, check_assist_not_applicable};
185 185
186 use super::*; 186 use super::*;
187 187
diff --git a/crates/ra_assists/src/handlers/add_new.rs b/crates/ra_assists/src/handlers/add_new.rs
index e8a36c7de..1c3f8435a 100644
--- a/crates/ra_assists/src/handlers/add_new.rs
+++ b/crates/ra_assists/src/handlers/add_new.rs
@@ -41,9 +41,8 @@ pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> {
41 // Return early if we've found an existing new fn 41 // Return early if we've found an existing new fn
42 let impl_def = find_struct_impl(&ctx, &strukt)?; 42 let impl_def = find_struct_impl(&ctx, &strukt)?;
43 43
44 ctx.add_assist(AssistId("add_new"), "Add default constructor", |edit| { 44 let target = strukt.syntax().text_range();
45 edit.target(strukt.syntax().text_range()); 45 ctx.add_assist(AssistId("add_new"), "Add default constructor", target, |edit| {
46
47 let mut buf = String::with_capacity(512); 46 let mut buf = String::with_capacity(512);
48 47
49 if impl_def.is_some() { 48 if impl_def.is_some() {
@@ -178,7 +177,7 @@ fn has_new_fn(imp: &ast::ImplDef) -> bool {
178 177
179#[cfg(test)] 178#[cfg(test)]
180mod tests { 179mod tests {
181 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 180 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
182 181
183 use super::*; 182 use super::*;
184 183
diff --git a/crates/ra_assists/src/handlers/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs
index 260b9e073..a5b26e5b9 100644
--- a/crates/ra_assists/src/handlers/apply_demorgan.rs
+++ b/crates/ra_assists/src/handlers/apply_demorgan.rs
@@ -39,8 +39,7 @@ pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option<Assist> {
39 let rhs_range = rhs.syntax().text_range(); 39 let rhs_range = rhs.syntax().text_range();
40 let not_rhs = invert_boolean_expression(rhs); 40 let not_rhs = invert_boolean_expression(rhs);
41 41
42 ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", |edit| { 42 ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| {
43 edit.target(op_range);
44 edit.replace(op_range, opposite_op); 43 edit.replace(op_range, opposite_op);
45 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); 44 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
46 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); 45 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
@@ -60,7 +59,7 @@ fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> {
60mod tests { 59mod tests {
61 use super::*; 60 use super::*;
62 61
63 use crate::helpers::{check_assist, check_assist_not_applicable}; 62 use crate::tests::{check_assist, check_assist_not_applicable};
64 63
65 #[test] 64 #[test]
66 fn demorgan_turns_and_into_or() { 65 fn demorgan_turns_and_into_or() {
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
index db6c4d2fa..2224b9714 100644
--- a/crates/ra_assists/src/handlers/auto_import.rs
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -48,8 +48,7 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> {
48 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range; 48 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range;
49 let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message()); 49 let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message());
50 for import in proposed_imports { 50 for import in proposed_imports {
51 group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { 51 group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), range, |edit| {
52 edit.target(range);
53 insert_use_statement(&auto_import_assets.syntax_under_caret, &import, edit); 52 insert_use_statement(&auto_import_assets.syntax_under_caret, &import, edit);
54 }); 53 });
55 } 54 }
@@ -277,7 +276,7 @@ impl ImportCandidate {
277#[cfg(test)] 276#[cfg(test)]
278mod tests { 277mod tests {
279 use super::*; 278 use super::*;
280 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 279 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
281 280
282 #[test] 281 #[test]
283 fn applicable_when_found_an_import() { 282 fn applicable_when_found_an_import() {
diff --git a/crates/ra_assists/src/handlers/change_return_type_to_result.rs b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
new file mode 100644
index 000000000..1e8d986cd
--- /dev/null
+++ b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
@@ -0,0 +1,971 @@
1use ra_syntax::{
2 ast, AstNode,
3 SyntaxKind::{COMMENT, WHITESPACE},
4 SyntaxNode, TextSize,
5};
6
7use crate::{Assist, AssistCtx, AssistId};
8use ast::{BlockExpr, Expr, LoopBodyOwner};
9
10// Assist: change_return_type_to_result
11//
12// Change the function's return type to Result.
13//
14// ```
15// fn foo() -> i32<|> { 42i32 }
16// ```
17// ->
18// ```
19// fn foo() -> Result<i32, > { Ok(42i32) }
20// ```
21pub(crate) fn change_return_type_to_result(ctx: AssistCtx) -> Option<Assist> {
22 let fn_def = ctx.find_node_at_offset::<ast::FnDef>();
23 let fn_def = &mut fn_def?;
24 let ret_type = &fn_def.ret_type()?.type_ref()?;
25 if ret_type.syntax().text().to_string().starts_with("Result<") {
26 return None;
27 }
28
29 let block_expr = &fn_def.body()?;
30 let cursor_in_ret_type =
31 fn_def.ret_type()?.syntax().text_range().contains_range(ctx.frange.range);
32 if !cursor_in_ret_type {
33 return None;
34 }
35
36 ctx.add_assist(
37 AssistId("change_return_type_to_result"),
38 "Change return type to Result",
39 ret_type.syntax().text_range(),
40 |edit| {
41 let mut tail_return_expr_collector = TailReturnCollector::new();
42 tail_return_expr_collector.collect_jump_exprs(block_expr, false);
43 tail_return_expr_collector.collect_tail_exprs(block_expr);
44
45 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap {
46 edit.replace_node_and_indent(&ret_expr_arg, format!("Ok({})", ret_expr_arg));
47 }
48 edit.replace_node_and_indent(ret_type.syntax(), format!("Result<{}, >", ret_type));
49
50 if let Some(node_start) = result_insertion_offset(&ret_type) {
51 edit.set_cursor(node_start + TextSize::of(&format!("Result<{}, ", ret_type)));
52 }
53 },
54 )
55}
56
57struct TailReturnCollector {
58 exprs_to_wrap: Vec<SyntaxNode>,
59}
60
61impl TailReturnCollector {
62 fn new() -> Self {
63 Self { exprs_to_wrap: vec![] }
64 }
65 /// Collect all`return` expression
66 fn collect_jump_exprs(&mut self, block_expr: &BlockExpr, collect_break: bool) {
67 let statements = block_expr.statements();
68 for stmt in statements {
69 let expr = match &stmt {
70 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
71 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
72 };
73 if let Some(expr) = &expr {
74 self.handle_exprs(expr, collect_break);
75 }
76 }
77
78 // Browse tail expressions for each block
79 if let Some(expr) = block_expr.expr() {
80 if let Some(last_exprs) = get_tail_expr_from_block(&expr) {
81 for last_expr in last_exprs {
82 let last_expr = match last_expr {
83 NodeType::Node(expr) | NodeType::Leaf(expr) => expr,
84 };
85
86 if let Some(last_expr) = Expr::cast(last_expr.clone()) {
87 self.handle_exprs(&last_expr, collect_break);
88 } else if let Some(expr_stmt) = ast::Stmt::cast(last_expr) {
89 let expr_stmt = match &expr_stmt {
90 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
91 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
92 };
93 if let Some(expr) = &expr_stmt {
94 self.handle_exprs(expr, collect_break);
95 }
96 }
97 }
98 }
99 }
100 }
101
102 fn handle_exprs(&mut self, expr: &Expr, collect_break: bool) {
103 match expr {
104 Expr::BlockExpr(block_expr) => {
105 self.collect_jump_exprs(&block_expr, collect_break);
106 }
107 Expr::ReturnExpr(ret_expr) => {
108 if let Some(ret_expr_arg) = &ret_expr.expr() {
109 self.exprs_to_wrap.push(ret_expr_arg.syntax().clone());
110 }
111 }
112 Expr::BreakExpr(break_expr) if collect_break => {
113 if let Some(break_expr_arg) = &break_expr.expr() {
114 self.exprs_to_wrap.push(break_expr_arg.syntax().clone());
115 }
116 }
117 Expr::IfExpr(if_expr) => {
118 for block in if_expr.blocks() {
119 self.collect_jump_exprs(&block, collect_break);
120 }
121 }
122 Expr::LoopExpr(loop_expr) => {
123 if let Some(block_expr) = loop_expr.loop_body() {
124 self.collect_jump_exprs(&block_expr, collect_break);
125 }
126 }
127 Expr::ForExpr(for_expr) => {
128 if let Some(block_expr) = for_expr.loop_body() {
129 self.collect_jump_exprs(&block_expr, collect_break);
130 }
131 }
132 Expr::WhileExpr(while_expr) => {
133 if let Some(block_expr) = while_expr.loop_body() {
134 self.collect_jump_exprs(&block_expr, collect_break);
135 }
136 }
137 Expr::MatchExpr(match_expr) => {
138 if let Some(arm_list) = match_expr.match_arm_list() {
139 arm_list.arms().filter_map(|match_arm| match_arm.expr()).for_each(|expr| {
140 self.handle_exprs(&expr, collect_break);
141 });
142 }
143 }
144 _ => {}
145 }
146 }
147
148 fn collect_tail_exprs(&mut self, block: &BlockExpr) {
149 if let Some(expr) = block.expr() {
150 self.handle_exprs(&expr, true);
151 self.fetch_tail_exprs(&expr);
152 }
153 }
154
155 fn fetch_tail_exprs(&mut self, expr: &Expr) {
156 if let Some(exprs) = get_tail_expr_from_block(expr) {
157 for node_type in &exprs {
158 match node_type {
159 NodeType::Leaf(expr) => {
160 self.exprs_to_wrap.push(expr.clone());
161 }
162 NodeType::Node(expr) => match &Expr::cast(expr.clone()) {
163 Some(last_expr) => {
164 self.fetch_tail_exprs(last_expr);
165 }
166 None => {
167 self.exprs_to_wrap.push(expr.clone());
168 }
169 },
170 }
171 }
172 }
173 }
174}
175
176#[derive(Debug)]
177enum NodeType {
178 Leaf(SyntaxNode),
179 Node(SyntaxNode),
180}
181
182/// Get a tail expression inside a block
183fn get_tail_expr_from_block(expr: &Expr) -> Option<Vec<NodeType>> {
184 match expr {
185 Expr::IfExpr(if_expr) => {
186 let mut nodes = vec![];
187 for block in if_expr.blocks() {
188 if let Some(block_expr) = block.expr() {
189 if let Some(tail_exprs) = get_tail_expr_from_block(&block_expr) {
190 nodes.extend(tail_exprs);
191 }
192 } else if let Some(last_expr) = block.syntax().last_child() {
193 nodes.push(NodeType::Node(last_expr));
194 } else {
195 nodes.push(NodeType::Node(block.syntax().clone()));
196 }
197 }
198 Some(nodes)
199 }
200 Expr::LoopExpr(loop_expr) => {
201 loop_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
202 }
203 Expr::ForExpr(for_expr) => {
204 for_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
205 }
206 Expr::WhileExpr(while_expr) => {
207 while_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
208 }
209 Expr::BlockExpr(block_expr) => {
210 block_expr.expr().map(|lc| vec![NodeType::Node(lc.syntax().clone())])
211 }
212 Expr::MatchExpr(match_expr) => {
213 let arm_list = match_expr.match_arm_list()?;
214 let arms: Vec<NodeType> = arm_list
215 .arms()
216 .filter_map(|match_arm| match_arm.expr())
217 .map(|expr| match expr {
218 Expr::ReturnExpr(ret_expr) => NodeType::Node(ret_expr.syntax().clone()),
219 Expr::BreakExpr(break_expr) => NodeType::Node(break_expr.syntax().clone()),
220 _ => match expr.syntax().last_child() {
221 Some(last_expr) => NodeType::Node(last_expr),
222 None => NodeType::Node(expr.syntax().clone()),
223 },
224 })
225 .collect();
226
227 Some(arms)
228 }
229 Expr::BreakExpr(expr) => expr.expr().map(|e| vec![NodeType::Leaf(e.syntax().clone())]),
230 Expr::ReturnExpr(ret_expr) => Some(vec![NodeType::Node(ret_expr.syntax().clone())]),
231 Expr::CallExpr(call_expr) => Some(vec![NodeType::Leaf(call_expr.syntax().clone())]),
232 Expr::Literal(lit_expr) => Some(vec![NodeType::Leaf(lit_expr.syntax().clone())]),
233 Expr::TupleExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
234 Expr::ArrayExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
235 Expr::ParenExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
236 Expr::PathExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
237 Expr::Label(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
238 Expr::RecordLit(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
239 Expr::IndexExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
240 Expr::MethodCallExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
241 Expr::AwaitExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
242 Expr::CastExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
243 Expr::RefExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
244 Expr::PrefixExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
245 Expr::RangeExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
246 Expr::BinExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
247 Expr::MacroCall(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
248 Expr::BoxExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
249 _ => None,
250 }
251}
252
253fn result_insertion_offset(ret_type: &ast::TypeRef) -> Option<TextSize> {
254 let non_ws_child = ret_type
255 .syntax()
256 .children_with_tokens()
257 .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
258 Some(non_ws_child.text_range().start())
259}
260
261#[cfg(test)]
262mod tests {
263
264 use crate::tests::{check_assist, check_assist_not_applicable};
265
266 use super::*;
267
268 #[test]
269 fn change_return_type_to_result_simple() {
270 check_assist(
271 change_return_type_to_result,
272 r#"fn foo() -> i3<|>2 {
273 let test = "test";
274 return 42i32;
275 }"#,
276 r#"fn foo() -> Result<i32, <|>> {
277 let test = "test";
278 return Ok(42i32);
279 }"#,
280 );
281 }
282
283 #[test]
284 fn change_return_type_to_result_simple_return_type() {
285 check_assist(
286 change_return_type_to_result,
287 r#"fn foo() -> i32<|> {
288 let test = "test";
289 return 42i32;
290 }"#,
291 r#"fn foo() -> Result<i32, <|>> {
292 let test = "test";
293 return Ok(42i32);
294 }"#,
295 );
296 }
297
298 #[test]
299 fn change_return_type_to_result_simple_return_type_bad_cursor() {
300 check_assist_not_applicable(
301 change_return_type_to_result,
302 r#"fn foo() -> i32 {
303 let test = "test";<|>
304 return 42i32;
305 }"#,
306 );
307 }
308
309 #[test]
310 fn change_return_type_to_result_simple_with_cursor() {
311 check_assist(
312 change_return_type_to_result,
313 r#"fn foo() -> <|>i32 {
314 let test = "test";
315 return 42i32;
316 }"#,
317 r#"fn foo() -> Result<i32, <|>> {
318 let test = "test";
319 return Ok(42i32);
320 }"#,
321 );
322 }
323
324 #[test]
325 fn change_return_type_to_result_simple_with_tail() {
326 check_assist(
327 change_return_type_to_result,
328 r#"fn foo() -><|> i32 {
329 let test = "test";
330 42i32
331 }"#,
332 r#"fn foo() -> Result<i32, <|>> {
333 let test = "test";
334 Ok(42i32)
335 }"#,
336 );
337 }
338
339 #[test]
340 fn change_return_type_to_result_simple_with_tail_only() {
341 check_assist(
342 change_return_type_to_result,
343 r#"fn foo() -> i32<|> {
344 42i32
345 }"#,
346 r#"fn foo() -> Result<i32, <|>> {
347 Ok(42i32)
348 }"#,
349 );
350 }
351 #[test]
352 fn change_return_type_to_result_simple_with_tail_block_like() {
353 check_assist(
354 change_return_type_to_result,
355 r#"fn foo() -> i32<|> {
356 if true {
357 42i32
358 } else {
359 24i32
360 }
361 }"#,
362 r#"fn foo() -> Result<i32, <|>> {
363 if true {
364 Ok(42i32)
365 } else {
366 Ok(24i32)
367 }
368 }"#,
369 );
370 }
371
372 #[test]
373 fn change_return_type_to_result_simple_with_nested_if() {
374 check_assist(
375 change_return_type_to_result,
376 r#"fn foo() -> i32<|> {
377 if true {
378 if false {
379 1
380 } else {
381 2
382 }
383 } else {
384 24i32
385 }
386 }"#,
387 r#"fn foo() -> Result<i32, <|>> {
388 if true {
389 if false {
390 Ok(1)
391 } else {
392 Ok(2)
393 }
394 } else {
395 Ok(24i32)
396 }
397 }"#,
398 );
399 }
400
401 #[test]
402 fn change_return_type_to_result_simple_with_await() {
403 check_assist(
404 change_return_type_to_result,
405 r#"async fn foo() -> i<|>32 {
406 if true {
407 if false {
408 1.await
409 } else {
410 2.await
411 }
412 } else {
413 24i32.await
414 }
415 }"#,
416 r#"async fn foo() -> Result<i32, <|>> {
417 if true {
418 if false {
419 Ok(1.await)
420 } else {
421 Ok(2.await)
422 }
423 } else {
424 Ok(24i32.await)
425 }
426 }"#,
427 );
428 }
429
430 #[test]
431 fn change_return_type_to_result_simple_with_array() {
432 check_assist(
433 change_return_type_to_result,
434 r#"fn foo() -> [i32;<|> 3] {
435 [1, 2, 3]
436 }"#,
437 r#"fn foo() -> Result<[i32; 3], <|>> {
438 Ok([1, 2, 3])
439 }"#,
440 );
441 }
442
443 #[test]
444 fn change_return_type_to_result_simple_with_cast() {
445 check_assist(
446 change_return_type_to_result,
447 r#"fn foo() -<|>> i32 {
448 if true {
449 if false {
450 1 as i32
451 } else {
452 2 as i32
453 }
454 } else {
455 24 as i32
456 }
457 }"#,
458 r#"fn foo() -> Result<i32, <|>> {
459 if true {
460 if false {
461 Ok(1 as i32)
462 } else {
463 Ok(2 as i32)
464 }
465 } else {
466 Ok(24 as i32)
467 }
468 }"#,
469 );
470 }
471
472 #[test]
473 fn change_return_type_to_result_simple_with_tail_block_like_match() {
474 check_assist(
475 change_return_type_to_result,
476 r#"fn foo() -> i32<|> {
477 let my_var = 5;
478 match my_var {
479 5 => 42i32,
480 _ => 24i32,
481 }
482 }"#,
483 r#"fn foo() -> Result<i32, <|>> {
484 let my_var = 5;
485 match my_var {
486 5 => Ok(42i32),
487 _ => Ok(24i32),
488 }
489 }"#,
490 );
491 }
492
493 #[test]
494 fn change_return_type_to_result_simple_with_loop_with_tail() {
495 check_assist(
496 change_return_type_to_result,
497 r#"fn foo() -> i32<|> {
498 let my_var = 5;
499 loop {
500 println!("test");
501 5
502 }
503
504 my_var
505 }"#,
506 r#"fn foo() -> Result<i32, <|>> {
507 let my_var = 5;
508 loop {
509 println!("test");
510 5
511 }
512
513 Ok(my_var)
514 }"#,
515 );
516 }
517
518 #[test]
519 fn change_return_type_to_result_simple_with_loop_in_let_stmt() {
520 check_assist(
521 change_return_type_to_result,
522 r#"fn foo() -> i32<|> {
523 let my_var = let x = loop {
524 break 1;
525 };
526
527 my_var
528 }"#,
529 r#"fn foo() -> Result<i32, <|>> {
530 let my_var = let x = loop {
531 break 1;
532 };
533
534 Ok(my_var)
535 }"#,
536 );
537 }
538
539 #[test]
540 fn change_return_type_to_result_simple_with_tail_block_like_match_return_expr() {
541 check_assist(
542 change_return_type_to_result,
543 r#"fn foo() -> i32<|> {
544 let my_var = 5;
545 let res = match my_var {
546 5 => 42i32,
547 _ => return 24i32,
548 };
549
550 res
551 }"#,
552 r#"fn foo() -> Result<i32, <|>> {
553 let my_var = 5;
554 let res = match my_var {
555 5 => 42i32,
556 _ => return Ok(24i32),
557 };
558
559 Ok(res)
560 }"#,
561 );
562
563 check_assist(
564 change_return_type_to_result,
565 r#"fn foo() -> i32<|> {
566 let my_var = 5;
567 let res = if my_var == 5 {
568 42i32
569 } else {
570 return 24i32;
571 };
572
573 res
574 }"#,
575 r#"fn foo() -> Result<i32, <|>> {
576 let my_var = 5;
577 let res = if my_var == 5 {
578 42i32
579 } else {
580 return Ok(24i32);
581 };
582
583 Ok(res)
584 }"#,
585 );
586 }
587
588 #[test]
589 fn change_return_type_to_result_simple_with_tail_block_like_match_deeper() {
590 check_assist(
591 change_return_type_to_result,
592 r#"fn foo() -> i32<|> {
593 let my_var = 5;
594 match my_var {
595 5 => {
596 if true {
597 42i32
598 } else {
599 25i32
600 }
601 },
602 _ => {
603 let test = "test";
604 if test == "test" {
605 return bar();
606 }
607 53i32
608 },
609 }
610 }"#,
611 r#"fn foo() -> Result<i32, <|>> {
612 let my_var = 5;
613 match my_var {
614 5 => {
615 if true {
616 Ok(42i32)
617 } else {
618 Ok(25i32)
619 }
620 },
621 _ => {
622 let test = "test";
623 if test == "test" {
624 return Ok(bar());
625 }
626 Ok(53i32)
627 },
628 }
629 }"#,
630 );
631 }
632
633 #[test]
634 fn change_return_type_to_result_simple_with_tail_block_like_early_return() {
635 check_assist(
636 change_return_type_to_result,
637 r#"fn foo() -> i<|>32 {
638 let test = "test";
639 if test == "test" {
640 return 24i32;
641 }
642 53i32
643 }"#,
644 r#"fn foo() -> Result<i32, <|>> {
645 let test = "test";
646 if test == "test" {
647 return Ok(24i32);
648 }
649 Ok(53i32)
650 }"#,
651 );
652 }
653
654 #[test]
655 fn change_return_type_to_result_simple_with_closure() {
656 check_assist(
657 change_return_type_to_result,
658 r#"fn foo(the_field: u32) -><|> u32 {
659 let true_closure = || {
660 return true;
661 };
662 if the_field < 5 {
663 let mut i = 0;
664
665
666 if true_closure() {
667 return 99;
668 } else {
669 return 0;
670 }
671 }
672
673 the_field
674 }"#,
675 r#"fn foo(the_field: u32) -> Result<u32, <|>> {
676 let true_closure = || {
677 return true;
678 };
679 if the_field < 5 {
680 let mut i = 0;
681
682
683 if true_closure() {
684 return Ok(99);
685 } else {
686 return Ok(0);
687 }
688 }
689
690 Ok(the_field)
691 }"#,
692 );
693
694 check_assist(
695 change_return_type_to_result,
696 r#"fn foo(the_field: u32) -> u32<|> {
697 let true_closure = || {
698 return true;
699 };
700 if the_field < 5 {
701 let mut i = 0;
702
703
704 if true_closure() {
705 return 99;
706 } else {
707 return 0;
708 }
709 }
710 let t = None;
711
712 t.unwrap_or_else(|| the_field)
713 }"#,
714 r#"fn foo(the_field: u32) -> Result<u32, <|>> {
715 let true_closure = || {
716 return true;
717 };
718 if the_field < 5 {
719 let mut i = 0;
720
721
722 if true_closure() {
723 return Ok(99);
724 } else {
725 return Ok(0);
726 }
727 }
728 let t = None;
729
730 Ok(t.unwrap_or_else(|| the_field))
731 }"#,
732 );
733 }
734
735 #[test]
736 fn change_return_type_to_result_simple_with_weird_forms() {
737 check_assist(
738 change_return_type_to_result,
739 r#"fn foo() -> i32<|> {
740 let test = "test";
741 if test == "test" {
742 return 24i32;
743 }
744 let mut i = 0;
745 loop {
746 if i == 1 {
747 break 55;
748 }
749 i += 1;
750 }
751 }"#,
752 r#"fn foo() -> Result<i32, <|>> {
753 let test = "test";
754 if test == "test" {
755 return Ok(24i32);
756 }
757 let mut i = 0;
758 loop {
759 if i == 1 {
760 break Ok(55);
761 }
762 i += 1;
763 }
764 }"#,
765 );
766
767 check_assist(
768 change_return_type_to_result,
769 r#"fn foo() -> i32<|> {
770 let test = "test";
771 if test == "test" {
772 return 24i32;
773 }
774 let mut i = 0;
775 loop {
776 loop {
777 if i == 1 {
778 break 55;
779 }
780 i += 1;
781 }
782 }
783 }"#,
784 r#"fn foo() -> Result<i32, <|>> {
785 let test = "test";
786 if test == "test" {
787 return Ok(24i32);
788 }
789 let mut i = 0;
790 loop {
791 loop {
792 if i == 1 {
793 break Ok(55);
794 }
795 i += 1;
796 }
797 }
798 }"#,
799 );
800
801 check_assist(
802 change_return_type_to_result,
803 r#"fn foo() -> i3<|>2 {
804 let test = "test";
805 let other = 5;
806 if test == "test" {
807 let res = match other {
808 5 => 43,
809 _ => return 56,
810 };
811 }
812 let mut i = 0;
813 loop {
814 loop {
815 if i == 1 {
816 break 55;
817 }
818 i += 1;
819 }
820 }
821 }"#,
822 r#"fn foo() -> Result<i32, <|>> {
823 let test = "test";
824 let other = 5;
825 if test == "test" {
826 let res = match other {
827 5 => 43,
828 _ => return Ok(56),
829 };
830 }
831 let mut i = 0;
832 loop {
833 loop {
834 if i == 1 {
835 break Ok(55);
836 }
837 i += 1;
838 }
839 }
840 }"#,
841 );
842
843 check_assist(
844 change_return_type_to_result,
845 r#"fn foo(the_field: u32) -> u32<|> {
846 if the_field < 5 {
847 let mut i = 0;
848 loop {
849 if i > 5 {
850 return 55u32;
851 }
852 i += 3;
853 }
854
855 match i {
856 5 => return 99,
857 _ => return 0,
858 };
859 }
860
861 the_field
862 }"#,
863 r#"fn foo(the_field: u32) -> Result<u32, <|>> {
864 if the_field < 5 {
865 let mut i = 0;
866 loop {
867 if i > 5 {
868 return Ok(55u32);
869 }
870 i += 3;
871 }
872
873 match i {
874 5 => return Ok(99),
875 _ => return Ok(0),
876 };
877 }
878
879 Ok(the_field)
880 }"#,
881 );
882
883 check_assist(
884 change_return_type_to_result,
885 r#"fn foo(the_field: u32) -> u3<|>2 {
886 if the_field < 5 {
887 let mut i = 0;
888
889 match i {
890 5 => return 99,
891 _ => return 0,
892 }
893 }
894
895 the_field
896 }"#,
897 r#"fn foo(the_field: u32) -> Result<u32, <|>> {
898 if the_field < 5 {
899 let mut i = 0;
900
901 match i {
902 5 => return Ok(99),
903 _ => return Ok(0),
904 }
905 }
906
907 Ok(the_field)
908 }"#,
909 );
910
911 check_assist(
912 change_return_type_to_result,
913 r#"fn foo(the_field: u32) -> u32<|> {
914 if the_field < 5 {
915 let mut i = 0;
916
917 if i == 5 {
918 return 99
919 } else {
920 return 0
921 }
922 }
923
924 the_field
925 }"#,
926 r#"fn foo(the_field: u32) -> Result<u32, <|>> {
927 if the_field < 5 {
928 let mut i = 0;
929
930 if i == 5 {
931 return Ok(99)
932 } else {
933 return Ok(0)
934 }
935 }
936
937 Ok(the_field)
938 }"#,
939 );
940
941 check_assist(
942 change_return_type_to_result,
943 r#"fn foo(the_field: u32) -> <|>u32 {
944 if the_field < 5 {
945 let mut i = 0;
946
947 if i == 5 {
948 return 99;
949 } else {
950 return 0;
951 }
952 }
953
954 the_field
955 }"#,
956 r#"fn foo(the_field: u32) -> Result<u32, <|>> {
957 if the_field < 5 {
958 let mut i = 0;
959
960 if i == 5 {
961 return Ok(99);
962 } else {
963 return Ok(0);
964 }
965 }
966
967 Ok(the_field)
968 }"#,
969 );
970 }
971}
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs
index 1cd532e80..489db83e6 100644
--- a/crates/ra_assists/src/handlers/change_visibility.rs
+++ b/crates/ra_assists/src/handlers/change_visibility.rs
@@ -66,11 +66,15 @@ fn add_vis(ctx: AssistCtx) -> Option<Assist> {
66 return None; 66 return None;
67 }; 67 };
68 68
69 ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub(crate)", |edit| { 69 ctx.add_assist(
70 edit.target(target); 70 AssistId("change_visibility"),
71 edit.insert(offset, "pub(crate) "); 71 "Change visibility to pub(crate)",
72 edit.set_cursor(offset); 72 target,
73 }) 73 |edit| {
74 edit.insert(offset, "pub(crate) ");
75 edit.set_cursor(offset);
76 },
77 )
74} 78}
75 79
76fn vis_offset(node: &SyntaxNode) -> TextSize { 80fn vis_offset(node: &SyntaxNode) -> TextSize {
@@ -86,22 +90,28 @@ fn vis_offset(node: &SyntaxNode) -> TextSize {
86 90
87fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> { 91fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> {
88 if vis.syntax().text() == "pub" { 92 if vis.syntax().text() == "pub" {
93 let target = vis.syntax().text_range();
89 return ctx.add_assist( 94 return ctx.add_assist(
90 AssistId("change_visibility"), 95 AssistId("change_visibility"),
91 "Change Visibility to pub(crate)", 96 "Change Visibility to pub(crate)",
97 target,
92 |edit| { 98 |edit| {
93 edit.target(vis.syntax().text_range());
94 edit.replace(vis.syntax().text_range(), "pub(crate)"); 99 edit.replace(vis.syntax().text_range(), "pub(crate)");
95 edit.set_cursor(vis.syntax().text_range().start()) 100 edit.set_cursor(vis.syntax().text_range().start())
96 }, 101 },
97 ); 102 );
98 } 103 }
99 if vis.syntax().text() == "pub(crate)" { 104 if vis.syntax().text() == "pub(crate)" {
100 return ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub", |edit| { 105 let target = vis.syntax().text_range();
101 edit.target(vis.syntax().text_range()); 106 return ctx.add_assist(
102 edit.replace(vis.syntax().text_range(), "pub"); 107 AssistId("change_visibility"),
103 edit.set_cursor(vis.syntax().text_range().start()); 108 "Change visibility to pub",
104 }); 109 target,
110 |edit| {
111 edit.replace(vis.syntax().text_range(), "pub");
112 edit.set_cursor(vis.syntax().text_range().start());
113 },
114 );
105 } 115 }
106 None 116 None
107} 117}
@@ -110,7 +120,7 @@ fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> {
110mod tests { 120mod tests {
111 use test_utils::covers; 121 use test_utils::covers;
112 122
113 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 123 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
114 124
115 use super::*; 125 use super::*;
116 126
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs
index eede2fe91..4bd6040b2 100644
--- a/crates/ra_assists/src/handlers/early_return.rs
+++ b/crates/ra_assists/src/handlers/early_return.rs
@@ -95,94 +95,99 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
95 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; 95 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
96 let cursor_position = ctx.frange.range.start(); 96 let cursor_position = ctx.frange.range.start();
97 97
98 ctx.add_assist(AssistId("convert_to_guarded_return"), "Convert to guarded return", |edit| { 98 let target = if_expr.syntax().text_range();
99 let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); 99 ctx.add_assist(
100 let new_block = match if_let_pat { 100 AssistId("convert_to_guarded_return"),
101 None => { 101 "Convert to guarded return",
102 // If. 102 target,
103 let new_expr = { 103 |edit| {
104 let then_branch = 104 let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
105 make::block_expr(once(make::expr_stmt(early_expression).into()), None); 105 let new_block = match if_let_pat {
106 let cond = invert_boolean_expression(cond_expr); 106 None => {
107 let e = make::expr_if(make::condition(cond, None), then_branch); 107 // If.
108 if_indent_level.increase_indent(e) 108 let new_expr = {
109 }; 109 let then_branch =
110 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) 110 make::block_expr(once(make::expr_stmt(early_expression).into()), None);
111 } 111 let cond = invert_boolean_expression(cond_expr);
112 Some((path, bound_ident)) => { 112 let e = make::expr_if(make::condition(cond, None), then_branch);
113 // If-let. 113 if_indent_level.increase_indent(e)
114 let match_expr = {
115 let happy_arm = {
116 let pat = make::tuple_struct_pat(
117 path,
118 once(make::bind_pat(make::name("it")).into()),
119 );
120 let expr = {
121 let name_ref = make::name_ref("it");
122 let segment = make::path_segment(name_ref);
123 let path = make::path_unqualified(segment);
124 make::expr_path(path)
125 };
126 make::match_arm(once(pat.into()), expr)
127 }; 114 };
115 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
116 }
117 Some((path, bound_ident)) => {
118 // If-let.
119 let match_expr = {
120 let happy_arm = {
121 let pat = make::tuple_struct_pat(
122 path,
123 once(make::bind_pat(make::name("it")).into()),
124 );
125 let expr = {
126 let name_ref = make::name_ref("it");
127 let segment = make::path_segment(name_ref);
128 let path = make::path_unqualified(segment);
129 make::expr_path(path)
130 };
131 make::match_arm(once(pat.into()), expr)
132 };
128 133
129 let sad_arm = make::match_arm( 134 let sad_arm = make::match_arm(
130 // FIXME: would be cool to use `None` or `Err(_)` if appropriate 135 // FIXME: would be cool to use `None` or `Err(_)` if appropriate
131 once(make::placeholder_pat().into()), 136 once(make::placeholder_pat().into()),
132 early_expression, 137 early_expression,
133 ); 138 );
134 139
135 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm])) 140 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
136 }; 141 };
137 142
138 let let_stmt = make::let_stmt( 143 let let_stmt = make::let_stmt(
139 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), 144 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
140 Some(match_expr), 145 Some(match_expr),
146 );
147 let let_stmt = if_indent_level.increase_indent(let_stmt);
148 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
149 }
150 };
151 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
152 edit.set_cursor(cursor_position);
153
154 fn replace(
155 new_expr: &SyntaxNode,
156 then_block: &ast::BlockExpr,
157 parent_block: &ast::BlockExpr,
158 if_expr: &ast::IfExpr,
159 ) -> SyntaxNode {
160 let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone());
161 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
162 let end_of_then =
163 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
164 end_of_then.prev_sibling_or_token().unwrap()
165 } else {
166 end_of_then
167 };
168 let mut then_statements = new_expr.children_with_tokens().chain(
169 then_block_items
170 .syntax()
171 .children_with_tokens()
172 .skip(1)
173 .take_while(|i| *i != end_of_then),
141 ); 174 );
142 let let_stmt = if_indent_level.increase_indent(let_stmt); 175 replace_children(
143 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) 176 &parent_block.syntax(),
177 RangeInclusive::new(
178 if_expr.clone().syntax().clone().into(),
179 if_expr.syntax().clone().into(),
180 ),
181 &mut then_statements,
182 )
144 } 183 }
145 }; 184 },
146 edit.target(if_expr.syntax().text_range()); 185 )
147 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
148 edit.set_cursor(cursor_position);
149
150 fn replace(
151 new_expr: &SyntaxNode,
152 then_block: &ast::BlockExpr,
153 parent_block: &ast::BlockExpr,
154 if_expr: &ast::IfExpr,
155 ) -> SyntaxNode {
156 let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone());
157 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
158 let end_of_then =
159 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
160 end_of_then.prev_sibling_or_token().unwrap()
161 } else {
162 end_of_then
163 };
164 let mut then_statements = new_expr.children_with_tokens().chain(
165 then_block_items
166 .syntax()
167 .children_with_tokens()
168 .skip(1)
169 .take_while(|i| *i != end_of_then),
170 );
171 replace_children(
172 &parent_block.syntax(),
173 RangeInclusive::new(
174 if_expr.clone().syntax().clone().into(),
175 if_expr.syntax().clone().into(),
176 ),
177 &mut then_statements,
178 )
179 }
180 })
181} 186}
182 187
183#[cfg(test)] 188#[cfg(test)]
184mod tests { 189mod tests {
185 use crate::helpers::{check_assist, check_assist_not_applicable}; 190 use crate::tests::{check_assist, check_assist_not_applicable};
186 191
187 use super::*; 192 use super::*;
188 193
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
index 8d1af9933..7c8f8bdf2 100644
--- a/crates/ra_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ra_assists/src/handlers/fill_match_arms.rs
@@ -92,10 +92,9 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
92 return None; 92 return None;
93 } 93 }
94 94
95 ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| { 95 let target = match_expr.syntax().text_range();
96 ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", target, |edit| {
96 let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms); 97 let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms);
97
98 edit.target(match_expr.syntax().text_range());
99 edit.set_cursor(expr.syntax().text_range().start()); 98 edit.set_cursor(expr.syntax().text_range().start());
100 edit.replace_ast(match_arm_list, new_arm_list); 99 edit.replace_ast(match_arm_list, new_arm_list);
101 }) 100 })
@@ -168,7 +167,7 @@ fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> O
168 167
169#[cfg(test)] 168#[cfg(test)]
170mod tests { 169mod tests {
171 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 170 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
172 171
173 use super::fill_match_arms; 172 use super::fill_match_arms;
174 173
diff --git a/crates/ra_assists/src/handlers/flip_binexpr.rs b/crates/ra_assists/src/handlers/flip_binexpr.rs
index 8030efb35..cb7264d7b 100644
--- a/crates/ra_assists/src/handlers/flip_binexpr.rs
+++ b/crates/ra_assists/src/handlers/flip_binexpr.rs
@@ -33,8 +33,7 @@ pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option<Assist> {
33 return None; 33 return None;
34 } 34 }
35 35
36 ctx.add_assist(AssistId("flip_binexpr"), "Flip binary expression", |edit| { 36 ctx.add_assist(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| {
37 edit.target(op_range);
38 if let FlipAction::FlipAndReplaceOp(new_op) = action { 37 if let FlipAction::FlipAndReplaceOp(new_op) = action {
39 edit.replace(op_range, new_op); 38 edit.replace(op_range, new_op);
40 } 39 }
@@ -69,7 +68,7 @@ impl From<BinOp> for FlipAction {
69mod tests { 68mod tests {
70 use super::*; 69 use super::*;
71 70
72 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 71 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
73 72
74 #[test] 73 #[test]
75 fn flip_binexpr_target_is_the_op() { 74 fn flip_binexpr_target_is_the_op() {
diff --git a/crates/ra_assists/src/handlers/flip_comma.rs b/crates/ra_assists/src/handlers/flip_comma.rs
index 1dacf29f8..24982ae22 100644
--- a/crates/ra_assists/src/handlers/flip_comma.rs
+++ b/crates/ra_assists/src/handlers/flip_comma.rs
@@ -28,8 +28,7 @@ pub(crate) fn flip_comma(ctx: AssistCtx) -> Option<Assist> {
28 return None; 28 return None;
29 } 29 }
30 30
31 ctx.add_assist(AssistId("flip_comma"), "Flip comma", |edit| { 31 ctx.add_assist(AssistId("flip_comma"), "Flip comma", comma.text_range(), |edit| {
32 edit.target(comma.text_range());
33 edit.replace(prev.text_range(), next.to_string()); 32 edit.replace(prev.text_range(), next.to_string());
34 edit.replace(next.text_range(), prev.to_string()); 33 edit.replace(next.text_range(), prev.to_string());
35 }) 34 })
@@ -39,7 +38,7 @@ pub(crate) fn flip_comma(ctx: AssistCtx) -> Option<Assist> {
39mod tests { 38mod tests {
40 use super::*; 39 use super::*;
41 40
42 use crate::helpers::{check_assist, check_assist_target}; 41 use crate::tests::{check_assist, check_assist_target};
43 42
44 #[test] 43 #[test]
45 fn flip_comma_works_for_function_parameters() { 44 fn flip_comma_works_for_function_parameters() {
diff --git a/crates/ra_assists/src/handlers/flip_trait_bound.rs b/crates/ra_assists/src/handlers/flip_trait_bound.rs
index f56769624..6a3b2df67 100644
--- a/crates/ra_assists/src/handlers/flip_trait_bound.rs
+++ b/crates/ra_assists/src/handlers/flip_trait_bound.rs
@@ -32,8 +32,8 @@ pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> {
32 non_trivia_sibling(plus.clone().into(), Direction::Next)?, 32 non_trivia_sibling(plus.clone().into(), Direction::Next)?,
33 ); 33 );
34 34
35 ctx.add_assist(AssistId("flip_trait_bound"), "Flip trait bounds", |edit| { 35 let target = plus.text_range();
36 edit.target(plus.text_range()); 36 ctx.add_assist(AssistId("flip_trait_bound"), "Flip trait bounds", target, |edit| {
37 edit.replace(before.text_range(), after.to_string()); 37 edit.replace(before.text_range(), after.to_string());
38 edit.replace(after.text_range(), before.to_string()); 38 edit.replace(after.text_range(), before.to_string());
39 }) 39 })
@@ -43,7 +43,7 @@ pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> {
43mod tests { 43mod tests {
44 use super::*; 44 use super::*;
45 45
46 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 46 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
47 47
48 #[test] 48 #[test]
49 fn flip_trait_bound_assist_available() { 49 fn flip_trait_bound_assist_available() {
diff --git a/crates/ra_assists/src/handlers/inline_local_variable.rs b/crates/ra_assists/src/handlers/inline_local_variable.rs
index 60ec536a7..e5765c845 100644
--- a/crates/ra_assists/src/handlers/inline_local_variable.rs
+++ b/crates/ra_assists/src/handlers/inline_local_variable.rs
@@ -106,9 +106,11 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> {
106 let init_str = initializer_expr.syntax().text().to_string(); 106 let init_str = initializer_expr.syntax().text().to_string();
107 let init_in_paren = format!("({})", &init_str); 107 let init_in_paren = format!("({})", &init_str);
108 108
109 let target = bind_pat.syntax().text_range();
109 ctx.add_assist( 110 ctx.add_assist(
110 AssistId("inline_local_variable"), 111 AssistId("inline_local_variable"),
111 "Inline variable", 112 "Inline variable",
113 target,
112 move |edit: &mut ActionBuilder| { 114 move |edit: &mut ActionBuilder| {
113 edit.delete(delete_range); 115 edit.delete(delete_range);
114 for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { 116 for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) {
@@ -125,7 +127,7 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> {
125mod tests { 127mod tests {
126 use test_utils::covers; 128 use test_utils::covers;
127 129
128 use crate::helpers::{check_assist, check_assist_not_applicable}; 130 use crate::tests::{check_assist, check_assist_not_applicable};
129 131
130 use super::*; 132 use super::*;
131 133
diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/introduce_variable.rs
index 39c656305..3c340ff3b 100644
--- a/crates/ra_assists/src/handlers/introduce_variable.rs
+++ b/crates/ra_assists/src/handlers/introduce_variable.rs
@@ -42,7 +42,8 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> {
42 if indent.kind() != WHITESPACE { 42 if indent.kind() != WHITESPACE {
43 return None; 43 return None;
44 } 44 }
45 ctx.add_assist(AssistId("introduce_variable"), "Extract into variable", move |edit| { 45 let target = expr.syntax().text_range();
46 ctx.add_assist(AssistId("introduce_variable"), "Extract into variable", target, move |edit| {
46 let mut buf = String::new(); 47 let mut buf = String::new();
47 48
48 let cursor_offset = if wrap_in_block { 49 let cursor_offset = if wrap_in_block {
@@ -79,7 +80,6 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> {
79 buf.push_str(text); 80 buf.push_str(text);
80 } 81 }
81 82
82 edit.target(expr.syntax().text_range());
83 edit.replace(expr.syntax().text_range(), "var_name".to_string()); 83 edit.replace(expr.syntax().text_range(), "var_name".to_string());
84 edit.insert(anchor_stmt.text_range().start(), buf); 84 edit.insert(anchor_stmt.text_range().start(), buf);
85 if wrap_in_block { 85 if wrap_in_block {
@@ -136,7 +136,7 @@ fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> {
136mod tests { 136mod tests {
137 use test_utils::covers; 137 use test_utils::covers;
138 138
139 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 139 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
140 140
141 use super::*; 141 use super::*;
142 142
diff --git a/crates/ra_assists/src/handlers/invert_if.rs b/crates/ra_assists/src/handlers/invert_if.rs
index 682e08512..b16271443 100644
--- a/crates/ra_assists/src/handlers/invert_if.rs
+++ b/crates/ra_assists/src/handlers/invert_if.rs
@@ -47,8 +47,7 @@ pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> {
47 let else_node = else_block.syntax(); 47 let else_node = else_block.syntax();
48 let else_range = else_node.text_range(); 48 let else_range = else_node.text_range();
49 let then_range = then_node.text_range(); 49 let then_range = then_node.text_range();
50 return ctx.add_assist(AssistId("invert_if"), "Invert if", |edit| { 50 return ctx.add_assist(AssistId("invert_if"), "Invert if", if_range, |edit| {
51 edit.target(if_range);
52 edit.replace(cond_range, flip_cond.syntax().text()); 51 edit.replace(cond_range, flip_cond.syntax().text());
53 edit.replace(else_range, then_node.text()); 52 edit.replace(else_range, then_node.text());
54 edit.replace(then_range, else_node.text()); 53 edit.replace(then_range, else_node.text());
@@ -62,7 +61,7 @@ pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> {
62mod tests { 61mod tests {
63 use super::*; 62 use super::*;
64 63
65 use crate::helpers::{check_assist, check_assist_not_applicable}; 64 use crate::tests::{check_assist, check_assist_not_applicable};
66 65
67 #[test] 66 #[test]
68 fn invert_if_remove_inequality() { 67 fn invert_if_remove_inequality() {
diff --git a/crates/ra_assists/src/handlers/merge_imports.rs b/crates/ra_assists/src/handlers/merge_imports.rs
index 4be1238f1..de74d83d8 100644
--- a/crates/ra_assists/src/handlers/merge_imports.rs
+++ b/crates/ra_assists/src/handlers/merge_imports.rs
@@ -52,7 +52,8 @@ pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> {
52 } 52 }
53 }; 53 };
54 54
55 ctx.add_assist(AssistId("merge_imports"), "Merge imports", |edit| { 55 let target = tree.syntax().text_range();
56 ctx.add_assist(AssistId("merge_imports"), "Merge imports", target, |edit| {
56 edit.rewrite(rewriter); 57 edit.rewrite(rewriter);
57 // FIXME: we only need because our diff is imprecise 58 // FIXME: we only need because our diff is imprecise
58 edit.set_cursor(offset); 59 edit.set_cursor(offset);
@@ -125,7 +126,7 @@ fn first_path(path: &ast::Path) -> ast::Path {
125 126
126#[cfg(test)] 127#[cfg(test)]
127mod tests { 128mod tests {
128 use crate::helpers::check_assist; 129 use crate::tests::check_assist;
129 130
130 use super::*; 131 use super::*;
131 132
diff --git a/crates/ra_assists/src/handlers/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs
index 5a77d3dbc..7c4d9d55d 100644
--- a/crates/ra_assists/src/handlers/merge_match_arms.rs
+++ b/crates/ra_assists/src/handlers/merge_match_arms.rs
@@ -70,7 +70,7 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> {
70 return None; 70 return None;
71 } 71 }
72 72
73 ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| { 73 ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| {
74 let pats = if arms_to_merge.iter().any(contains_placeholder) { 74 let pats = if arms_to_merge.iter().any(contains_placeholder) {
75 "_".into() 75 "_".into()
76 } else { 76 } else {
@@ -87,7 +87,6 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> {
87 let start = arms_to_merge.first().unwrap().syntax().text_range().start(); 87 let start = arms_to_merge.first().unwrap().syntax().text_range().start();
88 let end = arms_to_merge.last().unwrap().syntax().text_range().end(); 88 let end = arms_to_merge.last().unwrap().syntax().text_range().end();
89 89
90 edit.target(current_text_range);
91 edit.set_cursor(match cursor_pos { 90 edit.set_cursor(match cursor_pos {
92 CursorPos::InExpr(back_offset) => start + TextSize::of(&arm) - back_offset, 91 CursorPos::InExpr(back_offset) => start + TextSize::of(&arm) - back_offset,
93 CursorPos::InPat(offset) => offset, 92 CursorPos::InPat(offset) => offset,
@@ -105,7 +104,7 @@ fn contains_placeholder(a: &ast::MatchArm) -> bool {
105 104
106#[cfg(test)] 105#[cfg(test)]
107mod tests { 106mod tests {
108 use crate::helpers::{check_assist, check_assist_not_applicable}; 107 use crate::tests::{check_assist, check_assist_not_applicable};
109 108
110 use super::*; 109 use super::*;
111 110
diff --git a/crates/ra_assists/src/handlers/move_bounds.rs b/crates/ra_assists/src/handlers/move_bounds.rs
index 0f26884dc..44e50cb6e 100644
--- a/crates/ra_assists/src/handlers/move_bounds.rs
+++ b/crates/ra_assists/src/handlers/move_bounds.rs
@@ -49,30 +49,37 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> {
49 } 49 }
50 }; 50 };
51 51
52 ctx.add_assist(AssistId("move_bounds_to_where_clause"), "Move to where clause", |edit| { 52 let target = type_param_list.syntax().text_range();
53 let new_params = type_param_list 53 ctx.add_assist(
54 .type_params() 54 AssistId("move_bounds_to_where_clause"),
55 .filter(|it| it.type_bound_list().is_some()) 55 "Move to where clause",
56 .map(|type_param| { 56 target,
57 let without_bounds = type_param.remove_bounds(); 57 |edit| {
58 (type_param, without_bounds) 58 let new_params = type_param_list
59 }); 59 .type_params()
60 60 .filter(|it| it.type_bound_list().is_some())
61 let new_type_param_list = type_param_list.replace_descendants(new_params); 61 .map(|type_param| {
62 edit.replace_ast(type_param_list.clone(), new_type_param_list); 62 let without_bounds = type_param.remove_bounds();
63 63 (type_param, without_bounds)
64 let where_clause = { 64 });
65 let predicates = type_param_list.type_params().filter_map(build_predicate); 65
66 make::where_clause(predicates) 66 let new_type_param_list = type_param_list.replace_descendants(new_params);
67 }; 67 edit.replace_ast(type_param_list.clone(), new_type_param_list);
68 68
69 let to_insert = match anchor.prev_sibling_or_token() { 69 let where_clause = {
70 Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()), 70 let predicates = type_param_list.type_params().filter_map(build_predicate);
71 _ => format!(" {}", where_clause.syntax()), 71 make::where_clause(predicates)
72 }; 72 };
73 edit.insert(anchor.text_range().start(), to_insert); 73
74 edit.target(type_param_list.syntax().text_range()); 74 let to_insert = match anchor.prev_sibling_or_token() {
75 }) 75 Some(ref elem) if elem.kind() == WHITESPACE => {
76 format!("{} ", where_clause.syntax())
77 }
78 _ => format!(" {}", where_clause.syntax()),
79 };
80 edit.insert(anchor.text_range().start(), to_insert);
81 },
82 )
76} 83}
77 84
78fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { 85fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {
@@ -89,7 +96,7 @@ fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {
89mod tests { 96mod tests {
90 use super::*; 97 use super::*;
91 98
92 use crate::helpers::check_assist; 99 use crate::tests::check_assist;
93 100
94 #[test] 101 #[test]
95 fn move_bounds_to_where_clause_fn() { 102 fn move_bounds_to_where_clause_fn() {
diff --git a/crates/ra_assists/src/handlers/move_guard.rs b/crates/ra_assists/src/handlers/move_guard.rs
index b084dd9ee..29bc9a9ff 100644
--- a/crates/ra_assists/src/handlers/move_guard.rs
+++ b/crates/ra_assists/src/handlers/move_guard.rs
@@ -40,8 +40,8 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> {
40 let arm_expr = match_arm.expr()?; 40 let arm_expr = match_arm.expr()?;
41 let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); 41 let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text());
42 42
43 ctx.add_assist(AssistId("move_guard_to_arm_body"), "Move guard to arm body", |edit| { 43 let target = guard.syntax().text_range();
44 edit.target(guard.syntax().text_range()); 44 ctx.add_assist(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| {
45 let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { 45 let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) {
46 Some(tok) => { 46 Some(tok) => {
47 if ast::Whitespace::cast(tok.clone()).is_some() { 47 if ast::Whitespace::cast(tok.clone()).is_some() {
@@ -108,11 +108,12 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> {
108 108
109 let buf = format!(" if {}", cond.syntax().text()); 109 let buf = format!(" if {}", cond.syntax().text());
110 110
111 let target = if_expr.syntax().text_range();
111 ctx.add_assist( 112 ctx.add_assist(
112 AssistId("move_arm_cond_to_match_guard"), 113 AssistId("move_arm_cond_to_match_guard"),
113 "Move condition to match guard", 114 "Move condition to match guard",
115 target,
114 |edit| { 116 |edit| {
115 edit.target(if_expr.syntax().text_range());
116 let then_only_expr = then_block.statements().next().is_none(); 117 let then_only_expr = then_block.statements().next().is_none();
117 118
118 match &then_block.expr() { 119 match &then_block.expr() {
@@ -132,7 +133,7 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> {
132mod tests { 133mod tests {
133 use super::*; 134 use super::*;
134 135
135 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 136 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
136 137
137 #[test] 138 #[test]
138 fn move_guard_to_arm_body_target() { 139 fn move_guard_to_arm_body_target() {
diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs
index 567400b9c..155c679b4 100644
--- a/crates/ra_assists/src/handlers/raw_string.rs
+++ b/crates/ra_assists/src/handlers/raw_string.rs
@@ -25,8 +25,8 @@ use crate::{Assist, AssistCtx, AssistId};
25pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> { 25pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> {
26 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; 26 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
27 let value = token.value()?; 27 let value = token.value()?;
28 ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", |edit| { 28 let target = token.syntax().text_range();
29 edit.target(token.syntax().text_range()); 29 ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| {
30 let max_hash_streak = count_hashes(&value); 30 let max_hash_streak = count_hashes(&value);
31 let mut hashes = String::with_capacity(max_hash_streak + 1); 31 let mut hashes = String::with_capacity(max_hash_streak + 1);
32 for _ in 0..hashes.capacity() { 32 for _ in 0..hashes.capacity() {
@@ -54,8 +54,8 @@ pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> {
54pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> { 54pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
55 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; 55 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
56 let value = token.value()?; 56 let value = token.value()?;
57 ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", |edit| { 57 let target = token.syntax().text_range();
58 edit.target(token.syntax().text_range()); 58 ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| {
59 // parse inside string to escape `"` 59 // parse inside string to escape `"`
60 let escaped = value.escape_default().to_string(); 60 let escaped = value.escape_default().to_string();
61 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); 61 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
@@ -79,8 +79,8 @@ pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
79// ``` 79// ```
80pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> { 80pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> {
81 let token = ctx.find_token_at_offset(RAW_STRING)?; 81 let token = ctx.find_token_at_offset(RAW_STRING)?;
82 ctx.add_assist(AssistId("add_hash"), "Add # to raw string", |edit| { 82 let target = token.text_range();
83 edit.target(token.text_range()); 83 ctx.add_assist(AssistId("add_hash"), "Add # to raw string", target, |edit| {
84 edit.insert(token.text_range().start() + TextSize::of('r'), "#"); 84 edit.insert(token.text_range().start() + TextSize::of('r'), "#");
85 edit.insert(token.text_range().end(), "#"); 85 edit.insert(token.text_range().end(), "#");
86 }) 86 })
@@ -108,8 +108,8 @@ pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> {
108 // no hash to remove 108 // no hash to remove
109 return None; 109 return None;
110 } 110 }
111 ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", |edit| { 111 let target = token.text_range();
112 edit.target(token.text_range()); 112 ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| {
113 let result = &text[2..text.len() - 1]; 113 let result = &text[2..text.len() - 1];
114 let result = if result.starts_with('\"') { 114 let result = if result.starts_with('\"') {
115 // FIXME: this logic is wrong, not only the last has has to handled specially 115 // FIXME: this logic is wrong, not only the last has has to handled specially
@@ -138,7 +138,7 @@ fn count_hashes(s: &str) -> usize {
138#[cfg(test)] 138#[cfg(test)]
139mod test { 139mod test {
140 use super::*; 140 use super::*;
141 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 141 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
142 142
143 #[test] 143 #[test]
144 fn make_raw_string_target() { 144 fn make_raw_string_target() {
diff --git a/crates/ra_assists/src/handlers/remove_dbg.rs b/crates/ra_assists/src/handlers/remove_dbg.rs
index 4e5eb4350..e6e02f2ae 100644
--- a/crates/ra_assists/src/handlers/remove_dbg.rs
+++ b/crates/ra_assists/src/handlers/remove_dbg.rs
@@ -57,8 +57,8 @@ pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> {
57 text.slice(without_parens).to_string() 57 text.slice(without_parens).to_string()
58 }; 58 };
59 59
60 ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", |edit| { 60 let target = macro_call.syntax().text_range();
61 edit.target(macro_call.syntax().text_range()); 61 ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", target, |edit| {
62 edit.replace(macro_range, macro_content); 62 edit.replace(macro_range, macro_content);
63 edit.set_cursor(cursor_pos); 63 edit.set_cursor(cursor_pos);
64 }) 64 })
@@ -90,7 +90,7 @@ fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<b
90#[cfg(test)] 90#[cfg(test)]
91mod tests { 91mod tests {
92 use super::*; 92 use super::*;
93 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 93 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
94 94
95 #[test] 95 #[test]
96 fn test_remove_dbg() { 96 fn test_remove_dbg() {
diff --git a/crates/ra_assists/src/handlers/remove_mut.rs b/crates/ra_assists/src/handlers/remove_mut.rs
index e598023b2..9f72f879d 100644
--- a/crates/ra_assists/src/handlers/remove_mut.rs
+++ b/crates/ra_assists/src/handlers/remove_mut.rs
@@ -25,7 +25,8 @@ pub(crate) fn remove_mut(ctx: AssistCtx) -> Option<Assist> {
25 _ => mut_token.text_range().end(), 25 _ => mut_token.text_range().end(),
26 }; 26 };
27 27
28 ctx.add_assist(AssistId("remove_mut"), "Remove `mut` keyword", |edit| { 28 let target = mut_token.text_range();
29 ctx.add_assist(AssistId("remove_mut"), "Remove `mut` keyword", target, |edit| {
29 edit.set_cursor(delete_from); 30 edit.set_cursor(delete_from);
30 edit.delete(TextRange::new(delete_from, delete_to)); 31 edit.delete(TextRange::new(delete_from, delete_to));
31 }) 32 })
diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs
index 5cbb98d73..0b930dea2 100644
--- a/crates/ra_assists/src/handlers/reorder_fields.rs
+++ b/crates/ra_assists/src/handlers/reorder_fields.rs
@@ -50,11 +50,11 @@ fn reorder<R: AstNode>(ctx: AssistCtx) -> Option<Assist> {
50 return None; 50 return None;
51 } 51 }
52 52
53 ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", |edit| { 53 let target = record.syntax().text_range();
54 ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", target, |edit| {
54 for (old, new) in fields.iter().zip(&sorted_fields) { 55 for (old, new) in fields.iter().zip(&sorted_fields) {
55 algo::diff(old, new).into_text_edit(edit.text_edit_builder()); 56 algo::diff(old, new).into_text_edit(edit.text_edit_builder());
56 } 57 }
57 edit.target(record.syntax().text_range())
58 }) 58 })
59} 59}
60 60
@@ -109,7 +109,7 @@ fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> Option<HashMap<String,
109 109
110#[cfg(test)] 110#[cfg(test)]
111mod tests { 111mod tests {
112 use crate::helpers::{check_assist, check_assist_not_applicable}; 112 use crate::tests::{check_assist, check_assist_not_applicable};
113 113
114 use super::*; 114 use super::*;
115 115
diff --git a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
index 9841f6980..2eb8348f8 100644
--- a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
@@ -44,37 +44,42 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
44 }; 44 };
45 45
46 let sema = ctx.sema; 46 let sema = ctx.sema;
47 ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", move |edit| { 47 let target = if_expr.syntax().text_range();
48 let match_expr = { 48 ctx.add_assist(
49 let then_arm = { 49 AssistId("replace_if_let_with_match"),
50 let then_expr = unwrap_trivial_block(then_block); 50 "Replace with match",
51 make::match_arm(vec![pat.clone()], then_expr) 51 target,
52 move |edit| {
53 let match_expr = {
54 let then_arm = {
55 let then_expr = unwrap_trivial_block(then_block);
56 make::match_arm(vec![pat.clone()], then_expr)
57 };
58 let else_arm = {
59 let pattern = sema
60 .type_of_pat(&pat)
61 .and_then(|ty| TryEnum::from_ty(sema, &ty))
62 .map(|it| it.sad_pattern())
63 .unwrap_or_else(|| make::placeholder_pat().into());
64 let else_expr = unwrap_trivial_block(else_block);
65 make::match_arm(vec![pattern], else_expr)
66 };
67 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
52 }; 68 };
53 let else_arm = {
54 let pattern = sema
55 .type_of_pat(&pat)
56 .and_then(|ty| TryEnum::from_ty(sema, &ty))
57 .map(|it| it.sad_pattern())
58 .unwrap_or_else(|| make::placeholder_pat().into());
59 let else_expr = unwrap_trivial_block(else_block);
60 make::match_arm(vec![pattern], else_expr)
61 };
62 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
63 };
64 69
65 let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr); 70 let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr);
66 71
67 edit.target(if_expr.syntax().text_range()); 72 edit.set_cursor(if_expr.syntax().text_range().start());
68 edit.set_cursor(if_expr.syntax().text_range().start()); 73 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
69 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr); 74 },
70 }) 75 )
71} 76}
72 77
73#[cfg(test)] 78#[cfg(test)]
74mod tests { 79mod tests {
75 use super::*; 80 use super::*;
76 81
77 use crate::helpers::{check_assist, check_assist_target}; 82 use crate::tests::{check_assist, check_assist_target};
78 83
79 #[test] 84 #[test]
80 fn test_replace_if_let_with_match_unwraps_simple_expressions() { 85 fn test_replace_if_let_with_match_unwraps_simple_expressions() {
diff --git a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
index 0cf23b754..a5509a567 100644
--- a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
@@ -47,7 +47,8 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> {
47 let ty = ctx.sema.type_of_expr(&init)?; 47 let ty = ctx.sema.type_of_expr(&init)?;
48 let happy_variant = TryEnum::from_ty(ctx.sema, &ty).map(|it| it.happy_case()); 48 let happy_variant = TryEnum::from_ty(ctx.sema, &ty).map(|it| it.happy_case());
49 49
50 ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", |edit| { 50 let target = let_kw.text_range();
51 ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| {
51 let with_placeholder: ast::Pat = match happy_variant { 52 let with_placeholder: ast::Pat = match happy_variant {
52 None => make::placeholder_pat().into(), 53 None => make::placeholder_pat().into(),
53 Some(var_name) => make::tuple_struct_pat( 54 Some(var_name) => make::tuple_struct_pat(
@@ -67,14 +68,13 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> {
67 let stmt = stmt.replace_descendant(placeholder.into(), original_pat); 68 let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
68 69
69 edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt)); 70 edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
70 edit.target(let_kw.text_range());
71 edit.set_cursor(target_offset); 71 edit.set_cursor(target_offset);
72 }) 72 })
73} 73}
74 74
75#[cfg(test)] 75#[cfg(test)]
76mod tests { 76mod tests {
77 use crate::helpers::check_assist; 77 use crate::tests::check_assist;
78 78
79 use super::*; 79 use super::*;
80 80
diff --git a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
index ff2463c77..fd41da64b 100644
--- a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -33,9 +33,11 @@ pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist>
33 return None; 33 return None;
34 } 34 }
35 35
36 let target = path.syntax().text_range();
36 ctx.add_assist( 37 ctx.add_assist(
37 AssistId("replace_qualified_name_with_use"), 38 AssistId("replace_qualified_name_with_use"),
38 "Replace qualified path with use", 39 "Replace qualified path with use",
40 target,
39 |edit| { 41 |edit| {
40 let path_to_import = hir_path.mod_path().clone(); 42 let path_to_import = hir_path.mod_path().clone();
41 insert_use_statement(path.syntax(), &path_to_import, edit); 43 insert_use_statement(path.syntax(), &path_to_import, edit);
@@ -74,7 +76,7 @@ fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
74 76
75#[cfg(test)] 77#[cfg(test)]
76mod tests { 78mod tests {
77 use crate::helpers::{check_assist, check_assist_not_applicable}; 79 use crate::tests::{check_assist, check_assist_not_applicable};
78 80
79 use super::*; 81 use super::*;
80 82
diff --git a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
index 62d4ea522..c6b73da67 100644
--- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
@@ -38,32 +38,38 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
38 let caller = method_call.expr()?; 38 let caller = method_call.expr()?;
39 let ty = ctx.sema.type_of_expr(&caller)?; 39 let ty = ctx.sema.type_of_expr(&caller)?;
40 let happy_variant = TryEnum::from_ty(ctx.sema, &ty)?.happy_case(); 40 let happy_variant = TryEnum::from_ty(ctx.sema, &ty)?.happy_case();
41 let target = method_call.syntax().text_range();
42 ctx.add_assist(
43 AssistId("replace_unwrap_with_match"),
44 "Replace unwrap with match",
45 target,
46 |edit| {
47 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
48 let it = make::bind_pat(make::name("a")).into();
49 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
41 50
42 ctx.add_assist(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", |edit| { 51 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
43 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); 52 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
44 let it = make::bind_pat(make::name("a")).into();
45 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
46 53
47 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); 54 let unreachable_call = make::unreachable_macro_call().into();
48 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); 55 let err_arm =
56 make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
49 57
50 let unreachable_call = make::unreachable_macro_call().into(); 58 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
51 let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); 59 let match_expr = make::expr_match(caller.clone(), match_arm_list);
60 let match_expr =
61 IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr);
52 62
53 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); 63 edit.set_cursor(caller.syntax().text_range().start());
54 let match_expr = make::expr_match(caller.clone(), match_arm_list); 64 edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
55 let match_expr = IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr); 65 },
56 66 )
57 edit.target(method_call.syntax().text_range());
58 edit.set_cursor(caller.syntax().text_range().start());
59 edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
60 })
61} 67}
62 68
63#[cfg(test)] 69#[cfg(test)]
64mod tests { 70mod tests {
65 use super::*; 71 use super::*;
66 use crate::helpers::{check_assist, check_assist_target}; 72 use crate::tests::{check_assist, check_assist_target};
67 73
68 #[test] 74 #[test]
69 fn test_replace_result_unwrap_with_match() { 75 fn test_replace_result_unwrap_with_match() {
diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs
index f25826796..d49563974 100644
--- a/crates/ra_assists/src/handlers/split_import.rs
+++ b/crates/ra_assists/src/handlers/split_import.rs
@@ -28,8 +28,8 @@ pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> {
28 } 28 }
29 let cursor = ctx.frange.range.start(); 29 let cursor = ctx.frange.range.start();
30 30
31 ctx.add_assist(AssistId("split_import"), "Split import", |edit| { 31 let target = colon_colon.text_range();
32 edit.target(colon_colon.text_range()); 32 ctx.add_assist(AssistId("split_import"), "Split import", target, |edit| {
33 edit.replace_ast(use_tree, new_tree); 33 edit.replace_ast(use_tree, new_tree);
34 edit.set_cursor(cursor); 34 edit.set_cursor(cursor);
35 }) 35 })
@@ -37,7 +37,7 @@ pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> {
37 37
38#[cfg(test)] 38#[cfg(test)]
39mod tests { 39mod tests {
40 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 40 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
41 41
42 use super::*; 42 use super::*;
43 43
diff --git a/crates/ra_assists/src/handlers/unwrap_block.rs b/crates/ra_assists/src/handlers/unwrap_block.rs
index 859c70ad8..6df927abb 100644
--- a/crates/ra_assists/src/handlers/unwrap_block.rs
+++ b/crates/ra_assists/src/handlers/unwrap_block.rs
@@ -57,9 +57,9 @@ pub(crate) fn unwrap_block(ctx: AssistCtx) -> Option<Assist> {
57 } 57 }
58 }; 58 };
59 59
60 ctx.add_assist(AssistId("unwrap_block"), "Unwrap block", |edit| { 60 let target = expr_to_unwrap.syntax().text_range();
61 ctx.add_assist(AssistId("unwrap_block"), "Unwrap block", target, |edit| {
61 edit.set_cursor(expr.syntax().text_range().start()); 62 edit.set_cursor(expr.syntax().text_range().start());
62 edit.target(expr_to_unwrap.syntax().text_range());
63 63
64 let pat_start: &[_] = &[' ', '{', '\n']; 64 let pat_start: &[_] = &[' ', '{', '\n'];
65 let expr_to_unwrap = expr_to_unwrap.to_string(); 65 let expr_to_unwrap = expr_to_unwrap.to_string();
@@ -89,7 +89,7 @@ fn extract_expr(cursor_range: TextRange, block: ast::BlockExpr) -> Option<ast::E
89 89
90#[cfg(test)] 90#[cfg(test)]
91mod tests { 91mod tests {
92 use crate::helpers::{check_assist, check_assist_not_applicable}; 92 use crate::tests::{check_assist, check_assist_not_applicable};
93 93
94 use super::*; 94 use super::*;
95 95
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 0f94f5ee8..0473fd8c2 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -13,15 +13,14 @@ macro_rules! eprintln {
13mod assist_ctx; 13mod assist_ctx;
14mod marks; 14mod marks;
15#[cfg(test)] 15#[cfg(test)]
16mod doc_tests; 16mod tests;
17pub mod utils; 17pub mod utils;
18pub mod ast_transform; 18pub mod ast_transform;
19 19
20use hir::Semantics; 20use hir::Semantics;
21use ra_db::{FileId, FileRange}; 21use ra_db::FileRange;
22use ra_ide_db::RootDatabase; 22use ra_ide_db::{source_change::SourceChange, RootDatabase};
23use ra_syntax::{TextRange, TextSize}; 23use ra_syntax::TextRange;
24use ra_text_edit::TextEdit;
25 24
26pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; 25pub(crate) use crate::assist_ctx::{Assist, AssistCtx};
27 26
@@ -36,44 +35,31 @@ pub struct AssistLabel {
36 /// Short description of the assist, as shown in the UI. 35 /// Short description of the assist, as shown in the UI.
37 pub label: String, 36 pub label: String,
38 pub group: Option<GroupLabel>, 37 pub group: Option<GroupLabel>,
38 /// Target ranges are used to sort assists: the smaller the target range,
39 /// the more specific assist is, and so it should be sorted first.
40 pub target: TextRange,
39} 41}
40 42
41#[derive(Clone, Debug)] 43#[derive(Clone, Debug)]
42pub struct GroupLabel(pub String); 44pub struct GroupLabel(pub String);
43 45
44impl AssistLabel { 46impl AssistLabel {
45 pub(crate) fn new(id: AssistId, label: String, group: Option<GroupLabel>) -> AssistLabel { 47 pub(crate) fn new(
48 id: AssistId,
49 label: String,
50 group: Option<GroupLabel>,
51 target: TextRange,
52 ) -> AssistLabel {
46 // FIXME: make fields private, so that this invariant can't be broken 53 // FIXME: make fields private, so that this invariant can't be broken
47 assert!(label.starts_with(|c: char| c.is_uppercase())); 54 assert!(label.starts_with(|c: char| c.is_uppercase()));
48 AssistLabel { id, label, group } 55 AssistLabel { id, label, group, target }
49 } 56 }
50} 57}
51 58
52#[derive(Debug, Clone)] 59#[derive(Debug, Clone)]
53pub struct AssistAction {
54 pub edit: TextEdit,
55 pub cursor_position: Option<TextSize>,
56 // FIXME: This belongs to `AssistLabel`
57 pub target: Option<TextRange>,
58 pub file: AssistFile,
59}
60
61#[derive(Debug, Clone)]
62pub struct ResolvedAssist { 60pub struct ResolvedAssist {
63 pub label: AssistLabel, 61 pub label: AssistLabel,
64 pub action: AssistAction, 62 pub source_change: SourceChange,
65}
66
67#[derive(Debug, Clone, Copy)]
68pub enum AssistFile {
69 CurrentFile,
70 TargetFile(FileId),
71}
72
73impl Default for AssistFile {
74 fn default() -> Self {
75 Self::CurrentFile
76 }
77} 63}
78 64
79/// Return all the assists applicable at the given position. 65/// Return all the assists applicable at the given position.
@@ -104,7 +90,7 @@ pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssi
104 .flat_map(|it| it.0) 90 .flat_map(|it| it.0)
105 .map(|it| it.into_resolved().unwrap()) 91 .map(|it| it.into_resolved().unwrap())
106 .collect::<Vec<_>>(); 92 .collect::<Vec<_>>();
107 a.sort_by_key(|it| it.action.target.map_or(TextSize::from(!0u32), |it| it.len())); 93 a.sort_by_key(|it| it.label.target.len());
108 a 94 a
109} 95}
110 96
@@ -143,6 +129,7 @@ mod handlers {
143 mod replace_qualified_name_with_use; 129 mod replace_qualified_name_with_use;
144 mod replace_unwrap_with_match; 130 mod replace_unwrap_with_match;
145 mod split_import; 131 mod split_import;
132 mod change_return_type_to_result;
146 mod add_from_impl_for_enum; 133 mod add_from_impl_for_enum;
147 mod reorder_fields; 134 mod reorder_fields;
148 mod unwrap_block; 135 mod unwrap_block;
@@ -159,6 +146,7 @@ mod handlers {
159 add_new::add_new, 146 add_new::add_new,
160 apply_demorgan::apply_demorgan, 147 apply_demorgan::apply_demorgan,
161 auto_import::auto_import, 148 auto_import::auto_import,
149 change_return_type_to_result::change_return_type_to_result,
162 change_visibility::change_visibility, 150 change_visibility::change_visibility,
163 early_return::convert_to_guarded_return, 151 early_return::convert_to_guarded_return,
164 fill_match_arms::fill_match_arms, 152 fill_match_arms::fill_match_arms,
@@ -194,150 +182,3 @@ mod handlers {
194 ] 182 ]
195 } 183 }
196} 184}
197
198#[cfg(test)]
199mod helpers {
200 use std::sync::Arc;
201
202 use hir::Semantics;
203 use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
204 use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
205 use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset};
206
207 use crate::{handlers::Handler, AssistCtx, AssistFile};
208
209 pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
210 let (mut db, file_id) = RootDatabase::with_single_file(text);
211 // FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`,
212 // but it looks like this might need specialization? :(
213 db.set_local_roots(Arc::new(vec![db.file_source_root(file_id)]));
214 (db, file_id)
215 }
216
217 pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
218 check(assist, ra_fixture_before, ExpectedResult::After(ra_fixture_after));
219 }
220
221 // FIXME: instead of having a separate function here, maybe use
222 // `extract_ranges` and mark the target as `<target> </target>` in the
223 // fixuture?
224 pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) {
225 check(assist, ra_fixture, ExpectedResult::Target(target));
226 }
227
228 pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
229 check(assist, ra_fixture, ExpectedResult::NotApplicable);
230 }
231
232 enum ExpectedResult<'a> {
233 NotApplicable,
234 After(&'a str),
235 Target(&'a str),
236 }
237
238 fn check(assist: Handler, before: &str, expected: ExpectedResult) {
239 let (text_without_caret, file_with_caret_id, range_or_offset, db) =
240 if before.contains("//-") {
241 let (mut db, position) = RootDatabase::with_position(before);
242 db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)]));
243 (
244 db.file_text(position.file_id).as_ref().to_owned(),
245 position.file_id,
246 RangeOrOffset::Offset(position.offset),
247 db,
248 )
249 } else {
250 let (range_or_offset, text_without_caret) = extract_range_or_offset(before);
251 let (db, file_id) = with_single_file(&text_without_caret);
252 (text_without_caret, file_id, range_or_offset, db)
253 };
254
255 let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
256
257 let sema = Semantics::new(&db);
258 let assist_ctx = AssistCtx::new(&sema, frange, true);
259
260 match (assist(assist_ctx), expected) {
261 (Some(assist), ExpectedResult::After(after)) => {
262 let action = assist.0[0].action.clone().unwrap();
263
264 let mut actual = if let AssistFile::TargetFile(file_id) = action.file {
265 db.file_text(file_id).as_ref().to_owned()
266 } else {
267 text_without_caret
268 };
269 action.edit.apply(&mut actual);
270
271 match action.cursor_position {
272 None => {
273 if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {
274 let off = action
275 .edit
276 .apply_to_offset(before_cursor_pos)
277 .expect("cursor position is affected by the edit");
278 actual = add_cursor(&actual, off)
279 }
280 }
281 Some(off) => actual = add_cursor(&actual, off),
282 };
283
284 assert_eq_text!(after, &actual);
285 }
286 (Some(assist), ExpectedResult::Target(target)) => {
287 let action = assist.0[0].action.clone().unwrap();
288 let range = action.target.expect("expected target on action");
289 assert_eq_text!(&text_without_caret[range], target);
290 }
291 (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),
292 (None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => {
293 panic!("code action is not applicable")
294 }
295 (None, ExpectedResult::NotApplicable) => (),
296 };
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use ra_db::FileRange;
303 use ra_syntax::TextRange;
304 use test_utils::{extract_offset, extract_range};
305
306 use crate::{helpers, resolved_assists};
307
308 #[test]
309 fn assist_order_field_struct() {
310 let before = "struct Foo { <|>bar: u32 }";
311 let (before_cursor_pos, before) = extract_offset(before);
312 let (db, file_id) = helpers::with_single_file(&before);
313 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
314 let assists = resolved_assists(&db, frange);
315 let mut assists = assists.iter();
316
317 assert_eq!(
318 assists.next().expect("expected assist").label.label,
319 "Change visibility to pub(crate)"
320 );
321 assert_eq!(assists.next().expect("expected assist").label.label, "Add `#[derive]`");
322 }
323
324 #[test]
325 fn assist_order_if_expr() {
326 let before = "
327 pub fn test_some_range(a: int) -> bool {
328 if let 2..6 = <|>5<|> {
329 true
330 } else {
331 false
332 }
333 }";
334 let (range, before) = extract_range(before);
335 let (db, file_id) = helpers::with_single_file(&before);
336 let frange = FileRange { file_id, range };
337 let assists = resolved_assists(&db, frange);
338 let mut assists = assists.iter();
339
340 assert_eq!(assists.next().expect("expected assist").label.label, "Extract into variable");
341 assert_eq!(assists.next().expect("expected assist").label.label, "Replace with match");
342 }
343}
diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs
new file mode 100644
index 000000000..17e3ece9f
--- /dev/null
+++ b/crates/ra_assists/src/tests.rs
@@ -0,0 +1,164 @@
1mod generated;
2
3use std::sync::Arc;
4
5use hir::Semantics;
6use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
7use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
8use ra_syntax::TextRange;
9use test_utils::{
10 add_cursor, assert_eq_text, extract_offset, extract_range, extract_range_or_offset,
11 RangeOrOffset,
12};
13
14use crate::{handlers::Handler, resolved_assists, AssistCtx};
15
16pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
17 let (mut db, file_id) = RootDatabase::with_single_file(text);
18 // FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`,
19 // but it looks like this might need specialization? :(
20 db.set_local_roots(Arc::new(vec![db.file_source_root(file_id)]));
21 (db, file_id)
22}
23
24pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
25 check(assist, ra_fixture_before, ExpectedResult::After(ra_fixture_after));
26}
27
28// FIXME: instead of having a separate function here, maybe use
29// `extract_ranges` and mark the target as `<target> </target>` in the
30// fixuture?
31pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) {
32 check(assist, ra_fixture, ExpectedResult::Target(target));
33}
34
35pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
36 check(assist, ra_fixture, ExpectedResult::NotApplicable);
37}
38
39fn check_doc_test(assist_id: &str, before: &str, after: &str) {
40 let (selection, before) = extract_range_or_offset(before);
41 let (db, file_id) = crate::tests::with_single_file(&before);
42 let frange = FileRange { file_id, range: selection.into() };
43
44 let mut assist = resolved_assists(&db, frange)
45 .into_iter()
46 .find(|assist| assist.label.id.0 == assist_id)
47 .unwrap_or_else(|| {
48 panic!(
49 "\n\nAssist is not applicable: {}\nAvailable assists: {}",
50 assist_id,
51 resolved_assists(&db, frange)
52 .into_iter()
53 .map(|assist| assist.label.id.0)
54 .collect::<Vec<_>>()
55 .join(", ")
56 )
57 });
58
59 let actual = {
60 let change = assist.source_change.source_file_edits.pop().unwrap();
61 let mut actual = before.clone();
62 change.edit.apply(&mut actual);
63 actual
64 };
65 assert_eq_text!(after, &actual);
66}
67
68enum ExpectedResult<'a> {
69 NotApplicable,
70 After(&'a str),
71 Target(&'a str),
72}
73
74fn check(assist: Handler, before: &str, expected: ExpectedResult) {
75 let (text_without_caret, file_with_caret_id, range_or_offset, db) = if before.contains("//-") {
76 let (mut db, position) = RootDatabase::with_position(before);
77 db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)]));
78 (
79 db.file_text(position.file_id).as_ref().to_owned(),
80 position.file_id,
81 RangeOrOffset::Offset(position.offset),
82 db,
83 )
84 } else {
85 let (range_or_offset, text_without_caret) = extract_range_or_offset(before);
86 let (db, file_id) = with_single_file(&text_without_caret);
87 (text_without_caret, file_id, range_or_offset, db)
88 };
89
90 let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
91
92 let sema = Semantics::new(&db);
93 let assist_ctx = AssistCtx::new(&sema, frange, true);
94
95 match (assist(assist_ctx), expected) {
96 (Some(assist), ExpectedResult::After(after)) => {
97 let mut action = assist.0[0].source_change.clone().unwrap();
98 let change = action.source_file_edits.pop().unwrap();
99
100 let mut actual = db.file_text(change.file_id).as_ref().to_owned();
101 change.edit.apply(&mut actual);
102
103 match action.cursor_position {
104 None => {
105 if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {
106 let off = change
107 .edit
108 .apply_to_offset(before_cursor_pos)
109 .expect("cursor position is affected by the edit");
110 actual = add_cursor(&actual, off)
111 }
112 }
113 Some(off) => actual = add_cursor(&actual, off.offset),
114 };
115
116 assert_eq_text!(after, &actual);
117 }
118 (Some(assist), ExpectedResult::Target(target)) => {
119 let range = assist.0[0].label.target;
120 assert_eq_text!(&text_without_caret[range], target);
121 }
122 (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),
123 (None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => {
124 panic!("code action is not applicable")
125 }
126 (None, ExpectedResult::NotApplicable) => (),
127 };
128}
129
130#[test]
131fn assist_order_field_struct() {
132 let before = "struct Foo { <|>bar: u32 }";
133 let (before_cursor_pos, before) = extract_offset(before);
134 let (db, file_id) = with_single_file(&before);
135 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
136 let assists = resolved_assists(&db, frange);
137 let mut assists = assists.iter();
138
139 assert_eq!(
140 assists.next().expect("expected assist").label.label,
141 "Change visibility to pub(crate)"
142 );
143 assert_eq!(assists.next().expect("expected assist").label.label, "Add `#[derive]`");
144}
145
146#[test]
147fn assist_order_if_expr() {
148 let before = "
149 pub fn test_some_range(a: int) -> bool {
150 if let 2..6 = <|>5<|> {
151 true
152 } else {
153 false
154 }
155 }";
156 let (range, before) = extract_range(before);
157 let (db, file_id) = with_single_file(&before);
158 let frange = FileRange { file_id, range };
159 let assists = resolved_assists(&db, frange);
160 let mut assists = assists.iter();
161
162 assert_eq!(assists.next().expect("expected assist").label.label, "Extract into variable");
163 assert_eq!(assists.next().expect("expected assist").label.label, "Replace with match");
164}
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/tests/generated.rs
index 6696cc832..972dbd251 100644
--- a/crates/ra_assists/src/doc_tests/generated.rs
+++ b/crates/ra_assists/src/tests/generated.rs
@@ -1,10 +1,10 @@
1//! Generated file, do not edit by hand, see `xtask/src/codegen` 1//! Generated file, do not edit by hand, see `xtask/src/codegen`
2 2
3use super::check; 3use super::check_doc_test;
4 4
5#[test] 5#[test]
6fn doctest_add_custom_impl() { 6fn doctest_add_custom_impl() {
7 check( 7 check_doc_test(
8 "add_custom_impl", 8 "add_custom_impl",
9 r#####" 9 r#####"
10#[derive(Deb<|>ug, Display)] 10#[derive(Deb<|>ug, Display)]
@@ -23,7 +23,7 @@ impl Debug for S {
23 23
24#[test] 24#[test]
25fn doctest_add_derive() { 25fn doctest_add_derive() {
26 check( 26 check_doc_test(
27 "add_derive", 27 "add_derive",
28 r#####" 28 r#####"
29struct Point { 29struct Point {
@@ -43,7 +43,7 @@ struct Point {
43 43
44#[test] 44#[test]
45fn doctest_add_explicit_type() { 45fn doctest_add_explicit_type() {
46 check( 46 check_doc_test(
47 "add_explicit_type", 47 "add_explicit_type",
48 r#####" 48 r#####"
49fn main() { 49fn main() {
@@ -60,7 +60,7 @@ fn main() {
60 60
61#[test] 61#[test]
62fn doctest_add_function() { 62fn doctest_add_function() {
63 check( 63 check_doc_test(
64 "add_function", 64 "add_function",
65 r#####" 65 r#####"
66struct Baz; 66struct Baz;
@@ -87,7 +87,7 @@ fn bar(arg: &str, baz: Baz) {
87 87
88#[test] 88#[test]
89fn doctest_add_hash() { 89fn doctest_add_hash() {
90 check( 90 check_doc_test(
91 "add_hash", 91 "add_hash",
92 r#####" 92 r#####"
93fn main() { 93fn main() {
@@ -104,7 +104,7 @@ fn main() {
104 104
105#[test] 105#[test]
106fn doctest_add_impl() { 106fn doctest_add_impl() {
107 check( 107 check_doc_test(
108 "add_impl", 108 "add_impl",
109 r#####" 109 r#####"
110struct Ctx<T: Clone> { 110struct Ctx<T: Clone> {
@@ -125,7 +125,7 @@ impl<T: Clone> Ctx<T> {
125 125
126#[test] 126#[test]
127fn doctest_add_impl_default_members() { 127fn doctest_add_impl_default_members() {
128 check( 128 check_doc_test(
129 "add_impl_default_members", 129 "add_impl_default_members",
130 r#####" 130 r#####"
131trait Trait { 131trait Trait {
@@ -159,7 +159,7 @@ impl Trait for () {
159 159
160#[test] 160#[test]
161fn doctest_add_impl_missing_members() { 161fn doctest_add_impl_missing_members() {
162 check( 162 check_doc_test(
163 "add_impl_missing_members", 163 "add_impl_missing_members",
164 r#####" 164 r#####"
165trait Trait<T> { 165trait Trait<T> {
@@ -191,7 +191,7 @@ impl Trait<u32> for () {
191 191
192#[test] 192#[test]
193fn doctest_add_new() { 193fn doctest_add_new() {
194 check( 194 check_doc_test(
195 "add_new", 195 "add_new",
196 r#####" 196 r#####"
197struct Ctx<T: Clone> { 197struct Ctx<T: Clone> {
@@ -213,7 +213,7 @@ impl<T: Clone> Ctx<T> {
213 213
214#[test] 214#[test]
215fn doctest_apply_demorgan() { 215fn doctest_apply_demorgan() {
216 check( 216 check_doc_test(
217 "apply_demorgan", 217 "apply_demorgan",
218 r#####" 218 r#####"
219fn main() { 219fn main() {
@@ -230,7 +230,7 @@ fn main() {
230 230
231#[test] 231#[test]
232fn doctest_auto_import() { 232fn doctest_auto_import() {
233 check( 233 check_doc_test(
234 "auto_import", 234 "auto_import",
235 r#####" 235 r#####"
236fn main() { 236fn main() {
@@ -250,8 +250,21 @@ pub mod std { pub mod collections { pub struct HashMap { } } }
250} 250}
251 251
252#[test] 252#[test]
253fn doctest_change_return_type_to_result() {
254 check_doc_test(
255 "change_return_type_to_result",
256 r#####"
257fn foo() -> i32<|> { 42i32 }
258"#####,
259 r#####"
260fn foo() -> Result<i32, > { Ok(42i32) }
261"#####,
262 )
263}
264
265#[test]
253fn doctest_change_visibility() { 266fn doctest_change_visibility() {
254 check( 267 check_doc_test(
255 "change_visibility", 268 "change_visibility",
256 r#####" 269 r#####"
257<|>fn frobnicate() {} 270<|>fn frobnicate() {}
@@ -264,7 +277,7 @@ pub(crate) fn frobnicate() {}
264 277
265#[test] 278#[test]
266fn doctest_convert_to_guarded_return() { 279fn doctest_convert_to_guarded_return() {
267 check( 280 check_doc_test(
268 "convert_to_guarded_return", 281 "convert_to_guarded_return",
269 r#####" 282 r#####"
270fn main() { 283fn main() {
@@ -288,7 +301,7 @@ fn main() {
288 301
289#[test] 302#[test]
290fn doctest_fill_match_arms() { 303fn doctest_fill_match_arms() {
291 check( 304 check_doc_test(
292 "fill_match_arms", 305 "fill_match_arms",
293 r#####" 306 r#####"
294enum Action { Move { distance: u32 }, Stop } 307enum Action { Move { distance: u32 }, Stop }
@@ -314,7 +327,7 @@ fn handle(action: Action) {
314 327
315#[test] 328#[test]
316fn doctest_flip_binexpr() { 329fn doctest_flip_binexpr() {
317 check( 330 check_doc_test(
318 "flip_binexpr", 331 "flip_binexpr",
319 r#####" 332 r#####"
320fn main() { 333fn main() {
@@ -331,7 +344,7 @@ fn main() {
331 344
332#[test] 345#[test]
333fn doctest_flip_comma() { 346fn doctest_flip_comma() {
334 check( 347 check_doc_test(
335 "flip_comma", 348 "flip_comma",
336 r#####" 349 r#####"
337fn main() { 350fn main() {
@@ -348,7 +361,7 @@ fn main() {
348 361
349#[test] 362#[test]
350fn doctest_flip_trait_bound() { 363fn doctest_flip_trait_bound() {
351 check( 364 check_doc_test(
352 "flip_trait_bound", 365 "flip_trait_bound",
353 r#####" 366 r#####"
354fn foo<T: Clone +<|> Copy>() { } 367fn foo<T: Clone +<|> Copy>() { }
@@ -361,7 +374,7 @@ fn foo<T: Copy + Clone>() { }
361 374
362#[test] 375#[test]
363fn doctest_inline_local_variable() { 376fn doctest_inline_local_variable() {
364 check( 377 check_doc_test(
365 "inline_local_variable", 378 "inline_local_variable",
366 r#####" 379 r#####"
367fn main() { 380fn main() {
@@ -379,7 +392,7 @@ fn main() {
379 392
380#[test] 393#[test]
381fn doctest_introduce_variable() { 394fn doctest_introduce_variable() {
382 check( 395 check_doc_test(
383 "introduce_variable", 396 "introduce_variable",
384 r#####" 397 r#####"
385fn main() { 398fn main() {
@@ -397,7 +410,7 @@ fn main() {
397 410
398#[test] 411#[test]
399fn doctest_invert_if() { 412fn doctest_invert_if() {
400 check( 413 check_doc_test(
401 "invert_if", 414 "invert_if",
402 r#####" 415 r#####"
403fn main() { 416fn main() {
@@ -414,7 +427,7 @@ fn main() {
414 427
415#[test] 428#[test]
416fn doctest_make_raw_string() { 429fn doctest_make_raw_string() {
417 check( 430 check_doc_test(
418 "make_raw_string", 431 "make_raw_string",
419 r#####" 432 r#####"
420fn main() { 433fn main() {
@@ -431,7 +444,7 @@ fn main() {
431 444
432#[test] 445#[test]
433fn doctest_make_usual_string() { 446fn doctest_make_usual_string() {
434 check( 447 check_doc_test(
435 "make_usual_string", 448 "make_usual_string",
436 r#####" 449 r#####"
437fn main() { 450fn main() {
@@ -448,7 +461,7 @@ fn main() {
448 461
449#[test] 462#[test]
450fn doctest_merge_imports() { 463fn doctest_merge_imports() {
451 check( 464 check_doc_test(
452 "merge_imports", 465 "merge_imports",
453 r#####" 466 r#####"
454use std::<|>fmt::Formatter; 467use std::<|>fmt::Formatter;
@@ -462,7 +475,7 @@ use std::{fmt::Formatter, io};
462 475
463#[test] 476#[test]
464fn doctest_merge_match_arms() { 477fn doctest_merge_match_arms() {
465 check( 478 check_doc_test(
466 "merge_match_arms", 479 "merge_match_arms",
467 r#####" 480 r#####"
468enum Action { Move { distance: u32 }, Stop } 481enum Action { Move { distance: u32 }, Stop }
@@ -488,7 +501,7 @@ fn handle(action: Action) {
488 501
489#[test] 502#[test]
490fn doctest_move_arm_cond_to_match_guard() { 503fn doctest_move_arm_cond_to_match_guard() {
491 check( 504 check_doc_test(
492 "move_arm_cond_to_match_guard", 505 "move_arm_cond_to_match_guard",
493 r#####" 506 r#####"
494enum Action { Move { distance: u32 }, Stop } 507enum Action { Move { distance: u32 }, Stop }
@@ -515,7 +528,7 @@ fn handle(action: Action) {
515 528
516#[test] 529#[test]
517fn doctest_move_bounds_to_where_clause() { 530fn doctest_move_bounds_to_where_clause() {
518 check( 531 check_doc_test(
519 "move_bounds_to_where_clause", 532 "move_bounds_to_where_clause",
520 r#####" 533 r#####"
521fn apply<T, U, <|>F: FnOnce(T) -> U>(f: F, x: T) -> U { 534fn apply<T, U, <|>F: FnOnce(T) -> U>(f: F, x: T) -> U {
@@ -532,7 +545,7 @@ fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
532 545
533#[test] 546#[test]
534fn doctest_move_guard_to_arm_body() { 547fn doctest_move_guard_to_arm_body() {
535 check( 548 check_doc_test(
536 "move_guard_to_arm_body", 549 "move_guard_to_arm_body",
537 r#####" 550 r#####"
538enum Action { Move { distance: u32 }, Stop } 551enum Action { Move { distance: u32 }, Stop }
@@ -559,7 +572,7 @@ fn handle(action: Action) {
559 572
560#[test] 573#[test]
561fn doctest_remove_dbg() { 574fn doctest_remove_dbg() {
562 check( 575 check_doc_test(
563 "remove_dbg", 576 "remove_dbg",
564 r#####" 577 r#####"
565fn main() { 578fn main() {
@@ -576,7 +589,7 @@ fn main() {
576 589
577#[test] 590#[test]
578fn doctest_remove_hash() { 591fn doctest_remove_hash() {
579 check( 592 check_doc_test(
580 "remove_hash", 593 "remove_hash",
581 r#####" 594 r#####"
582fn main() { 595fn main() {
@@ -593,7 +606,7 @@ fn main() {
593 606
594#[test] 607#[test]
595fn doctest_remove_mut() { 608fn doctest_remove_mut() {
596 check( 609 check_doc_test(
597 "remove_mut", 610 "remove_mut",
598 r#####" 611 r#####"
599impl Walrus { 612impl Walrus {
@@ -610,7 +623,7 @@ impl Walrus {
610 623
611#[test] 624#[test]
612fn doctest_reorder_fields() { 625fn doctest_reorder_fields() {
613 check( 626 check_doc_test(
614 "reorder_fields", 627 "reorder_fields",
615 r#####" 628 r#####"
616struct Foo {foo: i32, bar: i32}; 629struct Foo {foo: i32, bar: i32};
@@ -625,7 +638,7 @@ const test: Foo = Foo {foo: 1, bar: 0}
625 638
626#[test] 639#[test]
627fn doctest_replace_if_let_with_match() { 640fn doctest_replace_if_let_with_match() {
628 check( 641 check_doc_test(
629 "replace_if_let_with_match", 642 "replace_if_let_with_match",
630 r#####" 643 r#####"
631enum Action { Move { distance: u32 }, Stop } 644enum Action { Move { distance: u32 }, Stop }
@@ -653,7 +666,7 @@ fn handle(action: Action) {
653 666
654#[test] 667#[test]
655fn doctest_replace_let_with_if_let() { 668fn doctest_replace_let_with_if_let() {
656 check( 669 check_doc_test(
657 "replace_let_with_if_let", 670 "replace_let_with_if_let",
658 r#####" 671 r#####"
659enum Option<T> { Some(T), None } 672enum Option<T> { Some(T), None }
@@ -679,7 +692,7 @@ fn compute() -> Option<i32> { None }
679 692
680#[test] 693#[test]
681fn doctest_replace_qualified_name_with_use() { 694fn doctest_replace_qualified_name_with_use() {
682 check( 695 check_doc_test(
683 "replace_qualified_name_with_use", 696 "replace_qualified_name_with_use",
684 r#####" 697 r#####"
685fn process(map: std::collections::<|>HashMap<String, String>) {} 698fn process(map: std::collections::<|>HashMap<String, String>) {}
@@ -694,7 +707,7 @@ fn process(map: HashMap<String, String>) {}
694 707
695#[test] 708#[test]
696fn doctest_replace_unwrap_with_match() { 709fn doctest_replace_unwrap_with_match() {
697 check( 710 check_doc_test(
698 "replace_unwrap_with_match", 711 "replace_unwrap_with_match",
699 r#####" 712 r#####"
700enum Result<T, E> { Ok(T), Err(E) } 713enum Result<T, E> { Ok(T), Err(E) }
@@ -718,7 +731,7 @@ fn main() {
718 731
719#[test] 732#[test]
720fn doctest_split_import() { 733fn doctest_split_import() {
721 check( 734 check_doc_test(
722 "split_import", 735 "split_import",
723 r#####" 736 r#####"
724use std::<|>collections::HashMap; 737use std::<|>collections::HashMap;
@@ -731,7 +744,7 @@ use std::{collections::HashMap};
731 744
732#[test] 745#[test]
733fn doctest_unwrap_block() { 746fn doctest_unwrap_block() {
734 check( 747 check_doc_test(
735 "unwrap_block", 748 "unwrap_block",
736 r#####" 749 r#####"
737fn foo() { 750fn foo() {
diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs
index 8248684ee..51d4c493e 100644
--- a/crates/ra_db/src/fixture.rs
+++ b/crates/ra_db/src/fixture.rs
@@ -1,4 +1,48 @@
1//! FIXME: write short doc here 1//! Fixtures are strings containing rust source code with optional metadata.
2//! A fixture without metadata is parsed into a single source file.
3//! Use this to test functionality local to one file.
4//!
5//! Example:
6//! ```
7//! r#"
8//! fn main() {
9//! println!("Hello World")
10//! }
11//! "#
12//! ```
13//!
14//! Metadata can be added to a fixture after a `//-` comment.
15//! The basic form is specifying filenames,
16//! which is also how to define multiple files in a single test fixture
17//!
18//! Example:
19//! ```
20//! "
21//! //- /main.rs
22//! mod foo;
23//! fn main() {
24//! foo::bar();
25//! }
26//!
27//! //- /foo.rs
28//! pub fn bar() {}
29//! "
30//! ```
31//!
32//! Metadata allows specifying all settings and variables
33//! that are available in a real rust project:
34//! - crate names via `crate:cratename`
35//! - dependencies via `deps:dep1,dep2`
36//! - configuration settings via `cfg:dbg=false,opt_level=2`
37//! - environment variables via `env:PATH=/bin,RUST_LOG=debug`
38//!
39//! Example:
40//! ```
41//! "
42//! //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo
43//! fn insert_source_code_here() {}
44//! "
45//! ```
2 46
3use std::str::FromStr; 47use std::str::FromStr;
4use std::sync::Arc; 48use std::sync::Arc;
diff --git a/crates/ra_hir_ty/src/_match.rs b/crates/ra_hir_ty/src/_match.rs
index 779e78574..149f65042 100644
--- a/crates/ra_hir_ty/src/_match.rs
+++ b/crates/ra_hir_ty/src/_match.rs
@@ -573,14 +573,20 @@ pub(crate) fn is_useful(
573 matrix: &Matrix, 573 matrix: &Matrix,
574 v: &PatStack, 574 v: &PatStack,
575) -> MatchCheckResult<Usefulness> { 575) -> MatchCheckResult<Usefulness> {
576 // Handle the special case of enums with no variants. In that case, no match 576 // Handle two special cases:
577 // arm is useful. 577 // - enum with no variants
578 if let Ty::Apply(ApplicationTy { ctor: TypeCtor::Adt(AdtId::EnumId(enum_id)), .. }) = 578 // - `!` type
579 cx.infer[cx.match_expr].strip_references() 579 // In those cases, no match arm is useful.
580 { 580 match cx.infer[cx.match_expr].strip_references() {
581 if cx.db.enum_data(*enum_id).variants.is_empty() { 581 Ty::Apply(ApplicationTy { ctor: TypeCtor::Adt(AdtId::EnumId(enum_id)), .. }) => {
582 if cx.db.enum_data(*enum_id).variants.is_empty() {
583 return Ok(Usefulness::NotUseful);
584 }
585 }
586 Ty::Apply(ApplicationTy { ctor: TypeCtor::Never, .. }) => {
582 return Ok(Usefulness::NotUseful); 587 return Ok(Usefulness::NotUseful);
583 } 588 }
589 _ => (),
584 } 590 }
585 591
586 if v.is_empty() { 592 if v.is_empty() {
@@ -1918,6 +1924,17 @@ mod tests {
1918 } 1924 }
1919 1925
1920 #[test] 1926 #[test]
1927 fn type_never() {
1928 let content = r"
1929 fn test_fn(never: !) {
1930 match never {}
1931 }
1932 ";
1933
1934 check_no_diagnostic(content);
1935 }
1936
1937 #[test]
1921 fn enum_never_ref() { 1938 fn enum_never_ref() {
1922 let content = r" 1939 let content = r"
1923 enum Never {} 1940 enum Never {}
diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs
deleted file mode 100644
index 389339a03..000000000
--- a/crates/ra_ide/src/assists.rs
+++ /dev/null
@@ -1,42 +0,0 @@
1//! FIXME: write short doc here
2
3use ra_assists::{resolved_assists, AssistAction};
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 Assist {
25 id: assist.label.id,
26 label: assist.label.label.clone(),
27 group_label: assist.label.group.map(|it| it.0),
28 source_change: action_to_edit(assist.action, file_id, assist.label.label.clone()),
29 }
30 })
31 .collect()
32}
33
34fn action_to_edit(action: AssistAction, file_id: FileId, label: String) -> SourceChange {
35 let file_id = match action.file {
36 ra_assists::AssistFile::TargetFile(it) => it,
37 _ => file_id,
38 };
39 let file_edit = SourceFileEdit { file_id, edit: action.edit };
40 SourceChange::source_file_edit(label, file_edit)
41 .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id }))
42}
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs
index 383b23ac4..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.
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 09f602fe1..737f87109 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::resolved_assists(db, frange)
476 .into_iter()
477 .map(|assist| Assist {
478 id: assist.label.id,
479 label: assist.label.label,
480 group_label: assist.label.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/ssr.rs b/crates/ra_ide/src/ssr.rs
index 8bf52d0fa..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);
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs
index a03da4693..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
diff --git a/crates/ra_ide_db/src/lib.rs b/crates/ra_ide_db/src/lib.rs
index e6f2d36e9..52fcd7b6f 100644
--- a/crates/ra_ide_db/src/lib.rs
+++ b/crates/ra_ide_db/src/lib.rs
@@ -10,6 +10,7 @@ pub mod change;
10pub mod defs; 10pub mod defs;
11pub mod search; 11pub mod search;
12pub mod imports_locator; 12pub mod imports_locator;
13pub mod source_change;
13mod wasm_shims; 14mod wasm_shims;
14 15
15use std::sync::Arc; 16use std::sync::Arc;
diff --git a/crates/ra_ide/src/source_change.rs b/crates/ra_ide_db/src/source_change.rs
index 10afd7825..af81a91a4 100644
--- a/crates/ra_ide/src/source_change.rs
+++ b/crates/ra_ide_db/src/source_change.rs
@@ -3,13 +3,12 @@
3//! 3//!
4//! It can be viewed as a dual for `AnalysisChange`. 4//! It can be viewed as a dual for `AnalysisChange`.
5 5
6use ra_db::RelativePathBuf; 6use ra_db::{FileId, FilePosition, RelativePathBuf, SourceRootId};
7use ra_text_edit::TextEdit; 7use ra_text_edit::{TextEdit, TextSize};
8 8
9use crate::{FileId, FilePosition, SourceRootId, TextSize}; 9#[derive(Debug, Clone)]
10
11#[derive(Debug)]
12pub struct SourceChange { 10pub struct SourceChange {
11 /// For display in the undo log in the editor
13 pub label: String, 12 pub label: String,
14 pub source_file_edits: Vec<SourceFileEdit>, 13 pub source_file_edits: Vec<SourceFileEdit>,
15 pub file_system_edits: Vec<FileSystemEdit>, 14 pub file_system_edits: Vec<FileSystemEdit>,
@@ -19,7 +18,7 @@ pub struct SourceChange {
19impl SourceChange { 18impl SourceChange {
20 /// Creates a new SourceChange with the given label 19 /// Creates a new SourceChange with the given label
21 /// from the edits. 20 /// from the edits.
22 pub(crate) fn from_edits<L: Into<String>>( 21 pub fn from_edits<L: Into<String>>(
23 label: L, 22 label: L,
24 source_file_edits: Vec<SourceFileEdit>, 23 source_file_edits: Vec<SourceFileEdit>,
25 file_system_edits: Vec<FileSystemEdit>, 24 file_system_edits: Vec<FileSystemEdit>,
@@ -34,7 +33,7 @@ impl SourceChange {
34 33
35 /// Creates a new SourceChange with the given label, 34 /// Creates a new SourceChange with the given label,
36 /// containing only the given `SourceFileEdits`. 35 /// containing only the given `SourceFileEdits`.
37 pub(crate) fn source_file_edits<L: Into<String>>(label: L, edits: Vec<SourceFileEdit>) -> Self { 36 pub fn source_file_edits<L: Into<String>>(label: L, edits: Vec<SourceFileEdit>) -> Self {
38 let label = label.into(); 37 let label = label.into();
39 assert!(label.starts_with(char::is_uppercase)); 38 assert!(label.starts_with(char::is_uppercase));
40 SourceChange { 39 SourceChange {
@@ -58,13 +57,13 @@ impl SourceChange {
58 57
59 /// Creates a new SourceChange with the given label, 58 /// Creates a new SourceChange with the given label,
60 /// containing only a single `SourceFileEdit`. 59 /// containing only a single `SourceFileEdit`.
61 pub(crate) fn source_file_edit<L: Into<String>>(label: L, edit: SourceFileEdit) -> Self { 60 pub fn source_file_edit<L: Into<String>>(label: L, edit: SourceFileEdit) -> Self {
62 SourceChange::source_file_edits(label, vec![edit]) 61 SourceChange::source_file_edits(label, vec![edit])
63 } 62 }
64 63
65 /// Creates a new SourceChange with the given label 64 /// Creates a new SourceChange with the given label
66 /// from the given `FileId` and `TextEdit` 65 /// from the given `FileId` and `TextEdit`
67 pub(crate) fn source_file_edit_from<L: Into<String>>( 66 pub fn source_file_edit_from<L: Into<String>>(
68 label: L, 67 label: L,
69 file_id: FileId, 68 file_id: FileId,
70 edit: TextEdit, 69 edit: TextEdit,
@@ -74,43 +73,43 @@ impl SourceChange {
74 73
75 /// Creates a new SourceChange with the given label 74 /// Creates a new SourceChange with the given label
76 /// from the given `FileId` and `TextEdit` 75 /// from the given `FileId` and `TextEdit`
77 pub(crate) fn file_system_edit<L: Into<String>>(label: L, edit: FileSystemEdit) -> Self { 76 pub fn file_system_edit<L: Into<String>>(label: L, edit: FileSystemEdit) -> Self {
78 SourceChange::file_system_edits(label, vec![edit]) 77 SourceChange::file_system_edits(label, vec![edit])
79 } 78 }
80 79
81 /// Sets the cursor position to the given `FilePosition` 80 /// Sets the cursor position to the given `FilePosition`
82 pub(crate) fn with_cursor(mut self, cursor_position: FilePosition) -> Self { 81 pub fn with_cursor(mut self, cursor_position: FilePosition) -> Self {
83 self.cursor_position = Some(cursor_position); 82 self.cursor_position = Some(cursor_position);
84 self 83 self
85 } 84 }
86 85
87 /// Sets the cursor position to the given `FilePosition` 86 /// Sets the cursor position to the given `FilePosition`
88 pub(crate) fn with_cursor_opt(mut self, cursor_position: Option<FilePosition>) -> Self { 87 pub fn with_cursor_opt(mut self, cursor_position: Option<FilePosition>) -> Self {
89 self.cursor_position = cursor_position; 88 self.cursor_position = cursor_position;
90 self 89 self
91 } 90 }
92} 91}
93 92
94#[derive(Debug)] 93#[derive(Debug, Clone)]
95pub struct SourceFileEdit { 94pub struct SourceFileEdit {
96 pub file_id: FileId, 95 pub file_id: FileId,
97 pub edit: TextEdit, 96 pub edit: TextEdit,
98} 97}
99 98
100#[derive(Debug)] 99#[derive(Debug, Clone)]
101pub enum FileSystemEdit { 100pub enum FileSystemEdit {
102 CreateFile { source_root: SourceRootId, path: RelativePathBuf }, 101 CreateFile { source_root: SourceRootId, path: RelativePathBuf },
103 MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf }, 102 MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf },
104} 103}
105 104
106pub(crate) struct SingleFileChange { 105pub struct SingleFileChange {
107 pub label: String, 106 pub label: String,
108 pub edit: TextEdit, 107 pub edit: TextEdit,
109 pub cursor_position: Option<TextSize>, 108 pub cursor_position: Option<TextSize>,
110} 109}
111 110
112impl SingleFileChange { 111impl SingleFileChange {
113 pub(crate) fn into_source_change(self, file_id: FileId) -> SourceChange { 112 pub fn into_source_change(self, file_id: FileId) -> SourceChange {
114 SourceChange { 113 SourceChange {
115 label: self.label, 114 label: self.label,
116 source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }], 115 source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }],
diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs
index 7138bbc65..3409713ff 100644
--- a/crates/ra_text_edit/src/lib.rs
+++ b/crates/ra_text_edit/src/lib.rs
@@ -4,7 +4,7 @@
4//! so `TextEdit` is the ultimate representation of the work done by 4//! so `TextEdit` is the ultimate representation of the work done by
5//! rust-analyzer. 5//! rust-analyzer.
6 6
7use text_size::{TextRange, TextSize}; 7pub use text_size::{TextRange, TextSize};
8 8
9/// `InsertDelete` -- a single "atomic" change to text 9/// `InsertDelete` -- a single "atomic" change to text
10/// 10///
@@ -71,6 +71,10 @@ impl TextEdit {
71 TextEdit { indels } 71 TextEdit { indels }
72 } 72 }
73 73
74 pub fn is_empty(&self) -> bool {
75 self.indels.is_empty()
76 }
77
74 pub fn as_indels(&self) -> &[Indel] { 78 pub fn as_indels(&self) -> &[Indel] {
75 &self.indels 79 &self.indels
76 } 80 }
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index b1365444a..b13e13af2 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -155,7 +155,7 @@ pub fn add_cursor(text: &str, offset: TextSize) -> String {
155 res 155 res
156} 156}
157 157
158#[derive(Debug)] 158#[derive(Debug, Eq, PartialEq)]
159pub struct FixtureEntry { 159pub struct FixtureEntry {
160 pub meta: String, 160 pub meta: String,
161 pub text: String, 161 pub text: String,
@@ -170,19 +170,26 @@ pub struct FixtureEntry {
170/// // - other meta 170/// // - other meta
171/// ``` 171/// ```
172pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> { 172pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> {
173 let margin = fixture 173 let fixture = indent_first_line(fixture);
174 .lines() 174 let margin = fixture_margin(&fixture);
175 .filter(|it| it.trim_start().starts_with("//-"))
176 .map(|it| it.len() - it.trim_start().len())
177 .next()
178 .expect("empty fixture");
179 175
180 let mut lines = fixture 176 let mut lines = fixture
181 .split('\n') // don't use `.lines` to not drop `\r\n` 177 .split('\n') // don't use `.lines` to not drop `\r\n`
182 .filter_map(|line| { 178 .enumerate()
179 .filter_map(|(ix, line)| {
183 if line.len() >= margin { 180 if line.len() >= margin {
184 assert!(line[..margin].trim().is_empty()); 181 assert!(line[..margin].trim().is_empty());
185 Some(&line[margin..]) 182 let line_content = &line[margin..];
183 if !line_content.starts_with("//-") {
184 assert!(
185 !line_content.contains("//-"),
186 r#"Metadata line {} has invalid indentation. All metadata lines need to have the same indentation.
187The offending line: {:?}"#,
188 ix,
189 line
190 );
191 }
192 Some(line_content)
186 } else { 193 } else {
187 assert!(line.trim().is_empty()); 194 assert!(line.trim().is_empty());
188 None 195 None
@@ -202,6 +209,85 @@ pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> {
202 res 209 res
203} 210}
204 211
212/// Adjusts the indentation of the first line to the minimum indentation of the rest of the lines.
213/// This allows fixtures to start off in a different indentation, e.g. to align the first line with
214/// the other lines visually:
215/// ```
216/// let fixture = "//- /lib.rs
217/// mod foo;
218/// //- /foo.rs
219/// fn bar() {}
220/// ";
221/// assert_eq!(fixture_margin(fixture),
222/// " //- /lib.rs
223/// mod foo;
224/// //- /foo.rs
225/// fn bar() {}
226/// ")
227/// ```
228fn indent_first_line(fixture: &str) -> String {
229 if fixture.is_empty() {
230 return String::new();
231 }
232 let mut lines = fixture.lines();
233 let first_line = lines.next().unwrap();
234 if first_line.contains("//-") {
235 let rest = lines.collect::<Vec<_>>().join("\n");
236 let fixed_margin = fixture_margin(&rest);
237 let fixed_indent = fixed_margin - indent_len(first_line);
238 format!("\n{}{}\n{}", " ".repeat(fixed_indent), first_line, rest)
239 } else {
240 fixture.to_owned()
241 }
242}
243
244fn fixture_margin(fixture: &str) -> usize {
245 fixture
246 .lines()
247 .filter(|it| it.trim_start().starts_with("//-"))
248 .map(indent_len)
249 .next()
250 .expect("empty fixture")
251}
252
253fn indent_len(s: &str) -> usize {
254 s.len() - s.trim_start().len()
255}
256
257#[test]
258#[should_panic]
259fn parse_fixture_checks_further_indented_metadata() {
260 parse_fixture(
261 r"
262 //- /lib.rs
263 mod bar;
264
265 fn foo() {}
266 //- /bar.rs
267 pub fn baz() {}
268 ",
269 );
270}
271
272#[test]
273fn parse_fixture_can_handle_unindented_first_line() {
274 let fixture = "//- /lib.rs
275 mod foo;
276 //- /foo.rs
277 struct Bar;
278";
279 assert_eq!(
280 parse_fixture(fixture),
281 parse_fixture(
282 "//- /lib.rs
283mod foo;
284//- /foo.rs
285struct Bar;
286"
287 )
288 )
289}
290
205/// Same as `parse_fixture`, except it allow empty fixture 291/// Same as `parse_fixture`, except it allow empty fixture
206pub fn parse_single_fixture(fixture: &str) -> Option<FixtureEntry> { 292pub fn parse_single_fixture(fixture: &str) -> Option<FixtureEntry> {
207 if !fixture.lines().any(|it| it.trim_start().starts_with("//-")) { 293 if !fixture.lines().any(|it| it.trim_start().starts_with("//-")) {
diff --git a/docs/user/assists.md b/docs/user/assists.md
index ee515949e..692fd4f52 100644
--- a/docs/user/assists.md
+++ b/docs/user/assists.md
@@ -241,6 +241,18 @@ fn main() {
241} 241}
242``` 242```
243 243
244## `change_return_type_to_result`
245
246Change the function's return type to Result.
247
248```rust
249// BEFORE
250fn foo() -> i32┃ { 42i32 }
251
252// AFTER
253fn foo() -> Result<i32, > { Ok(42i32) }
254```
255
244## `change_visibility` 256## `change_visibility`
245 257
246Adds or changes existing visibility specifier. 258Adds or changes existing visibility specifier.
diff --git a/docs/user/readme.adoc b/docs/user/readme.adoc
index 69f5b13d6..301e9a49c 100644
--- a/docs/user/readme.adoc
+++ b/docs/user/readme.adoc
@@ -61,7 +61,7 @@ The server binary is stored in:
61 61
62* Linux: `~/.config/Code/User/globalStorage/matklad.rust-analyzer` 62* Linux: `~/.config/Code/User/globalStorage/matklad.rust-analyzer`
63* macOS: `~/Library/Application Support/Code/User/globalStorage/matklad.rust-analyzer` 63* macOS: `~/Library/Application Support/Code/User/globalStorage/matklad.rust-analyzer`
64* Windows: `%APPDATA%\Code\User\globalStorage` 64* Windows: `%APPDATA%\Code\User\globalStorage\matklad.rust-analyzer`
65 65
66Note that we only support the latest version of VS Code. 66Note that we only support the latest version of VS Code.
67 67
diff --git a/editors/code/package.json b/editors/code/package.json
index eeb3d3513..8849615c8 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -405,7 +405,7 @@
405 "ms-vscode.cpptools" 405 "ms-vscode.cpptools"
406 ], 406 ],
407 "default": "auto", 407 "default": "auto",
408 "description": "Preffered debug engine.", 408 "description": "Preferred debug engine.",
409 "markdownEnumDescriptions": [ 409 "markdownEnumDescriptions": [
410 "First try to use [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb), if it's not installed try to use [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools).", 410 "First try to use [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb), if it's not installed try to use [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools).",
411 "Use [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)", 411 "Use [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)",
diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs
index 0e4dcb95a..b4907f4b2 100644
--- a/xtask/src/codegen.rs
+++ b/xtask/src/codegen.rs
@@ -27,7 +27,7 @@ const AST_NODES: &str = "crates/ra_syntax/src/ast/generated/nodes.rs";
27const AST_TOKENS: &str = "crates/ra_syntax/src/ast/generated/tokens.rs"; 27const AST_TOKENS: &str = "crates/ra_syntax/src/ast/generated/tokens.rs";
28 28
29const ASSISTS_DIR: &str = "crates/ra_assists/src/handlers"; 29const ASSISTS_DIR: &str = "crates/ra_assists/src/handlers";
30const ASSISTS_TESTS: &str = "crates/ra_assists/src/doc_tests/generated.rs"; 30const ASSISTS_TESTS: &str = "crates/ra_assists/src/tests/generated.rs";
31const ASSISTS_DOCS: &str = "docs/user/assists.md"; 31const ASSISTS_DOCS: &str = "docs/user/assists.md";
32 32
33#[derive(Debug, PartialEq, Eq, Clone, Copy)] 33#[derive(Debug, PartialEq, Eq, Clone, Copy)]
diff --git a/xtask/src/codegen/gen_assists_docs.rs b/xtask/src/codegen/gen_assists_docs.rs
index 31d606535..4bd6b5f0c 100644
--- a/xtask/src/codegen/gen_assists_docs.rs
+++ b/xtask/src/codegen/gen_assists_docs.rs
@@ -101,14 +101,14 @@ fn collect_assists() -> Result<Vec<Assist>> {
101} 101}
102 102
103fn generate_tests(assists: &[Assist], mode: Mode) -> Result<()> { 103fn generate_tests(assists: &[Assist], mode: Mode) -> Result<()> {
104 let mut buf = String::from("use super::check;\n"); 104 let mut buf = String::from("use super::check_doc_test;\n");
105 105
106 for assist in assists.iter() { 106 for assist in assists.iter() {
107 let test = format!( 107 let test = format!(
108 r######" 108 r######"
109#[test] 109#[test]
110fn doctest_{}() {{ 110fn doctest_{}() {{
111 check( 111 check_doc_test(
112 "{}", 112 "{}",
113r#####" 113r#####"
114{}"#####, r#####" 114{}"#####, r#####"
diff --git a/xtask/tests/tidy-tests/cli.rs b/xtask/tests/tidy-tests/cli.rs
deleted file mode 100644
index f5b00a8b8..000000000
--- a/xtask/tests/tidy-tests/cli.rs
+++ /dev/null
@@ -1,32 +0,0 @@
1use xtask::{
2 codegen::{self, Mode},
3 run_rustfmt,
4};
5
6#[test]
7fn generated_grammar_is_fresh() {
8 if let Err(error) = codegen::generate_syntax(Mode::Verify) {
9 panic!("{}. Please update it by running `cargo xtask codegen`", error);
10 }
11}
12
13#[test]
14fn generated_tests_are_fresh() {
15 if let Err(error) = codegen::generate_parser_tests(Mode::Verify) {
16 panic!("{}. Please update tests by running `cargo xtask codegen`", error);
17 }
18}
19
20#[test]
21fn generated_assists_are_fresh() {
22 if let Err(error) = codegen::generate_assists_docs(Mode::Verify) {
23 panic!("{}. Please update assists by running `cargo xtask codegen`", error);
24 }
25}
26
27#[test]
28fn check_code_formatting() {
29 if let Err(error) = run_rustfmt(Mode::Verify) {
30 panic!("{}. Please format the code by running `cargo format`", error);
31 }
32}
diff --git a/xtask/tests/tidy-tests/main.rs b/xtask/tests/tidy.rs
index 3213c4dfa..b8e8860ba 100644
--- a/xtask/tests/tidy-tests/main.rs
+++ b/xtask/tests/tidy.rs
@@ -1,11 +1,41 @@
1mod cli;
2
3use std::{ 1use std::{
4 collections::HashMap, 2 collections::HashMap,
5 path::{Path, PathBuf}, 3 path::{Path, PathBuf},
6}; 4};
7 5
8use xtask::{not_bash::fs2, project_root, rust_files}; 6use xtask::{
7 codegen::{self, Mode},
8 not_bash::fs2,
9 project_root, run_rustfmt, rust_files,
10};
11
12#[test]
13fn generated_grammar_is_fresh() {
14 if let Err(error) = codegen::generate_syntax(Mode::Verify) {
15 panic!("{}. Please update it by running `cargo xtask codegen`", error);
16 }
17}
18
19#[test]
20fn generated_tests_are_fresh() {
21 if let Err(error) = codegen::generate_parser_tests(Mode::Verify) {
22 panic!("{}. Please update tests by running `cargo xtask codegen`", error);
23 }
24}
25
26#[test]
27fn generated_assists_are_fresh() {
28 if let Err(error) = codegen::generate_assists_docs(Mode::Verify) {
29 panic!("{}. Please update assists by running `cargo xtask codegen`", error);
30 }
31}
32
33#[test]
34fn check_code_formatting() {
35 if let Err(error) = run_rustfmt(Mode::Verify) {
36 panic!("{}. Please format the code by running `cargo format`", error);
37 }
38}
9 39
10#[test] 40#[test]
11fn rust_files_are_tidy() { 41fn rust_files_are_tidy() {
@@ -24,7 +54,7 @@ fn check_todo(path: &Path, text: &str) {
24 // This file itself is whitelisted since this test itself contains matches. 54 // This file itself is whitelisted since this test itself contains matches.
25 "tests/cli.rs", 55 "tests/cli.rs",
26 // Some of our assists generate `todo!()` so those files are whitelisted. 56 // Some of our assists generate `todo!()` so those files are whitelisted.
27 "doc_tests/generated.rs", 57 "tests/generated.rs",
28 "handlers/add_missing_impl_members.rs", 58 "handlers/add_missing_impl_members.rs",
29 "handlers/add_function.rs", 59 "handlers/add_function.rs",
30 // To support generating `todo!()` in assists, we have `expr_todo()` in ast::make. 60 // To support generating `todo!()` in assists, we have `expr_todo()` in ast::make.
@@ -106,7 +136,6 @@ impl TidyDocs {
106 } 136 }
107 137
108 let whitelist = [ 138 let whitelist = [
109 "ra_db",
110 "ra_hir", 139 "ra_hir",
111 "ra_hir_expand", 140 "ra_hir_expand",
112 "ra_ide", 141 "ra_ide",