aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/src/assist_context.rs233
-rw-r--r--crates/ra_assists/src/assist_ctx.rs274
-rw-r--r--crates/ra_assists/src/doc_tests.rs39
-rw-r--r--crates/ra_assists/src/handlers/add_custom_impl.rs14
-rw-r--r--crates/ra_assists/src/handlers/add_derive.rs11
-rw-r--r--crates/ra_assists/src/handlers/add_explicit_type.rs10
-rw-r--r--crates/ra_assists/src/handlers/add_from_impl_for_enum.rs14
-rw-r--r--crates/ra_assists/src/handlers/add_function.rs59
-rw-r--r--crates/ra_assists/src/handlers/add_impl.rs10
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs46
-rw-r--r--crates/ra_assists/src/handlers/add_new.rs13
-rw-r--r--crates/ra_assists/src/handlers/apply_demorgan.rs9
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs44
-rw-r--r--crates/ra_assists/src/handlers/change_return_type_to_result.rs971
-rw-r--r--crates/ra_assists/src/handlers/change_visibility.rs39
-rw-r--r--crates/ra_assists/src/handlers/early_return.rs12
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs11
-rw-r--r--crates/ra_assists/src/handlers/flip_binexpr.rs9
-rw-r--r--crates/ra_assists/src/handlers/flip_comma.rs9
-rw-r--r--crates/ra_assists/src/handlers/flip_trait_bound.rs10
-rw-r--r--crates/ra_assists/src/handlers/inline_local_variable.rs33
-rw-r--r--crates/ra_assists/src/handlers/introduce_variable.rs10
-rw-r--r--crates/ra_assists/src/handlers/invert_if.rs39
-rw-r--r--crates/ra_assists/src/handlers/merge_imports.rs18
-rw-r--r--crates/ra_assists/src/handlers/merge_match_arms.rs11
-rw-r--r--crates/ra_assists/src/handlers/move_bounds.rs10
-rw-r--r--crates/ra_assists/src/handlers/move_guard.rs17
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs28
-rw-r--r--crates/ra_assists/src/handlers/remove_dbg.rs10
-rw-r--r--crates/ra_assists/src/handlers/remove_mut.rs7
-rw-r--r--crates/ra_assists/src/handlers/reorder_fields.rs33
-rw-r--r--crates/ra_assists/src/handlers/replace_if_let_with_match.rs16
-rw-r--r--crates/ra_assists/src/handlers/replace_let_with_if_let.rs16
-rw-r--r--crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs23
-rw-r--r--crates/ra_assists/src/handlers/replace_unwrap_with_match.rs13
-rw-r--r--crates/ra_assists/src/handlers/split_import.rs12
-rw-r--r--crates/ra_assists/src/handlers/unwrap_block.rs10
-rw-r--r--crates/ra_assists/src/lib.rs277
-rw-r--r--crates/ra_assists/src/tests.rs167
-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_assists/src/utils/insert_use.rs10
-rw-r--r--crates/ra_cfg/src/lib.rs7
-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/display/function_signature.rs8
-rw-r--r--crates/ra_ide/src/hover.rs49
-rw-r--r--crates/ra_ide/src/lib.rs38
-rw-r--r--crates/ra_ide/src/references/rename.rs62
-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/defs.rs10
-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_project_model/src/cargo_workspace.rs15
-rw-r--r--crates/ra_project_model/src/lib.rs13
-rw-r--r--crates/ra_text_edit/src/lib.rs6
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs26
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs110
-rw-r--r--crates/test_utils/src/lib.rs104
61 files changed, 2297 insertions, 1008 deletions
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs
new file mode 100644
index 000000000..3085c4330
--- /dev/null
+++ b/crates/ra_assists/src/assist_context.rs
@@ -0,0 +1,233 @@
1//! See `AssistContext`
2
3use algo::find_covering_element;
4use hir::Semantics;
5use ra_db::{FileId, FileRange};
6use ra_fmt::{leading_indent, reindent};
7use ra_ide_db::{
8 source_change::{SingleFileChange, SourceChange},
9 RootDatabase,
10};
11use ra_syntax::{
12 algo::{self, find_node_at_offset, SyntaxRewriter},
13 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
14 TokenAtOffset,
15};
16use ra_text_edit::TextEditBuilder;
17
18use crate::{Assist, AssistId, GroupLabel, ResolvedAssist};
19
20/// `AssistContext` allows to apply an assist or check if it could be applied.
21///
22/// Assists use a somewhat over-engineered approach, given the current needs.
23/// The assists workflow consists of two phases. In the first phase, a user asks
24/// for the list of available assists. In the second phase, the user picks a
25/// particular assist and it gets applied.
26///
27/// There are two peculiarities here:
28///
29/// * first, we ideally avoid computing more things then necessary to answer "is
30/// assist applicable" in the first phase.
31/// * second, when we are applying assist, we don't have a guarantee that there
32/// weren't any changes between the point when user asked for assists and when
33/// they applied a particular assist. So, when applying assist, we need to do
34/// all the checks from scratch.
35///
36/// To avoid repeating the same code twice for both "check" and "apply"
37/// functions, we use an approach reminiscent of that of Django's function based
38/// views dealing with forms. Each assist receives a runtime parameter,
39/// `resolve`. It first check if an edit is applicable (potentially computing
40/// info required to compute the actual edit). If it is applicable, and
41/// `resolve` is `true`, it then computes the actual edit.
42///
43/// So, to implement the original assists workflow, we can first apply each edit
44/// with `resolve = false`, and then applying the selected edit again, with
45/// `resolve = true` this time.
46///
47/// Note, however, that we don't actually use such two-phase logic at the
48/// moment, because the LSP API is pretty awkward in this place, and it's much
49/// easier to just compute the edit eagerly :-)
50pub(crate) struct AssistContext<'a> {
51 pub(crate) sema: Semantics<'a, RootDatabase>,
52 pub(super) db: &'a RootDatabase,
53 pub(crate) frange: FileRange,
54 source_file: SourceFile,
55}
56
57impl<'a> AssistContext<'a> {
58 pub fn new(sema: Semantics<'a, RootDatabase>, frange: FileRange) -> AssistContext<'a> {
59 let source_file = sema.parse(frange.file_id);
60 let db = sema.db;
61 AssistContext { sema, db, frange, source_file }
62 }
63
64 // NB, this ignores active selection.
65 pub(crate) fn offset(&self) -> TextSize {
66 self.frange.range.start()
67 }
68
69 pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
70 self.source_file.syntax().token_at_offset(self.offset())
71 }
72 pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
73 self.token_at_offset().find(|it| it.kind() == kind)
74 }
75 pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
76 find_node_at_offset(self.source_file.syntax(), self.offset())
77 }
78 pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
79 self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset())
80 }
81 pub(crate) fn covering_element(&self) -> SyntaxElement {
82 find_covering_element(self.source_file.syntax(), self.frange.range)
83 }
84 // FIXME: remove
85 pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement {
86 find_covering_element(self.source_file.syntax(), range)
87 }
88}
89
90pub(crate) struct Assists {
91 resolve: bool,
92 file: FileId,
93 buf: Vec<(Assist, Option<SourceChange>)>,
94}
95
96impl Assists {
97 pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists {
98 Assists { resolve: true, file: ctx.frange.file_id, buf: Vec::new() }
99 }
100 pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists {
101 Assists { resolve: false, file: ctx.frange.file_id, buf: Vec::new() }
102 }
103
104 pub(crate) fn finish_unresolved(self) -> Vec<Assist> {
105 assert!(!self.resolve);
106 self.finish()
107 .into_iter()
108 .map(|(label, edit)| {
109 assert!(edit.is_none());
110 label
111 })
112 .collect()
113 }
114
115 pub(crate) fn finish_resolved(self) -> Vec<ResolvedAssist> {
116 assert!(self.resolve);
117 self.finish()
118 .into_iter()
119 .map(|(label, edit)| ResolvedAssist { assist: label, source_change: edit.unwrap() })
120 .collect()
121 }
122
123 pub(crate) fn add(
124 &mut self,
125 id: AssistId,
126 label: impl Into<String>,
127 target: TextRange,
128 f: impl FnOnce(&mut AssistBuilder),
129 ) -> Option<()> {
130 let label = Assist::new(id, label.into(), None, target);
131 self.add_impl(label, f)
132 }
133 pub(crate) fn add_group(
134 &mut self,
135 group: &GroupLabel,
136 id: AssistId,
137 label: impl Into<String>,
138 target: TextRange,
139 f: impl FnOnce(&mut AssistBuilder),
140 ) -> Option<()> {
141 let label = Assist::new(id, label.into(), Some(group.clone()), target);
142 self.add_impl(label, f)
143 }
144 fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
145 let change_label = label.label.clone();
146 let source_change = if self.resolve {
147 let mut builder = AssistBuilder::new(self.file);
148 f(&mut builder);
149 Some(builder.finish(change_label))
150 } else {
151 None
152 };
153
154 self.buf.push((label, source_change));
155 Some(())
156 }
157
158 fn finish(mut self) -> Vec<(Assist, Option<SourceChange>)> {
159 self.buf.sort_by_key(|(label, _edit)| label.target.len());
160 self.buf
161 }
162}
163
164pub(crate) struct AssistBuilder {
165 edit: TextEditBuilder,
166 cursor_position: Option<TextSize>,
167 file: FileId,
168}
169
170impl AssistBuilder {
171 pub(crate) fn new(file: FileId) -> AssistBuilder {
172 AssistBuilder { edit: TextEditBuilder::default(), cursor_position: None, file }
173 }
174
175 /// Remove specified `range` of text.
176 pub(crate) fn delete(&mut self, range: TextRange) {
177 self.edit.delete(range)
178 }
179 /// Append specified `text` at the given `offset`
180 pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
181 self.edit.insert(offset, text.into())
182 }
183 /// Replaces specified `range` of text with a given string.
184 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
185 self.edit.replace(range, replace_with.into())
186 }
187 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
188 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
189 }
190 /// Replaces specified `node` of text with a given string, reindenting the
191 /// string to maintain `node`'s existing indent.
192 // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent
193 pub(crate) fn replace_node_and_indent(
194 &mut self,
195 node: &SyntaxNode,
196 replace_with: impl Into<String>,
197 ) {
198 let mut replace_with = replace_with.into();
199 if let Some(indent) = leading_indent(node) {
200 replace_with = reindent(&replace_with, &indent)
201 }
202 self.replace(node.text_range(), replace_with)
203 }
204 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
205 let node = rewriter.rewrite_root().unwrap();
206 let new = rewriter.rewrite(&node);
207 algo::diff(&node, &new).into_text_edit(&mut self.edit)
208 }
209
210 /// Specify desired position of the cursor after the assist is applied.
211 pub(crate) fn set_cursor(&mut self, offset: TextSize) {
212 self.cursor_position = Some(offset)
213 }
214 // FIXME: better API
215 pub(crate) fn set_file(&mut self, assist_file: FileId) {
216 self.file = assist_file;
217 }
218
219 // FIXME: kill this API
220 /// Get access to the raw `TextEditBuilder`.
221 pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
222 &mut self.edit
223 }
224
225 fn finish(self, change_label: String) -> SourceChange {
226 let edit = self.edit.finish();
227 if edit.is_empty() && self.cursor_position.is_none() {
228 panic!("Only call `add_assist` if the assist can be applied")
229 }
230 SingleFileChange { label: change_label, edit, cursor_position: self.cursor_position }
231 .into_source_change(self.file)
232 }
233}
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
deleted file mode 100644
index 83dd270c6..000000000
--- a/crates/ra_assists/src/assist_ctx.rs
+++ /dev/null
@@ -1,274 +0,0 @@
1//! This module defines `AssistCtx` -- the API surface that is exposed to assists.
2use hir::Semantics;
3use ra_db::FileRange;
4use ra_fmt::{leading_indent, reindent};
5use ra_ide_db::RootDatabase;
6use ra_syntax::{
7 algo::{self, find_covering_element, find_node_at_offset, SyntaxRewriter},
8 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
9 TokenAtOffset,
10};
11use ra_text_edit::TextEditBuilder;
12
13use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
14
15#[derive(Clone, Debug)]
16pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);
17
18#[derive(Clone, Debug)]
19pub(crate) struct AssistInfo {
20 pub(crate) label: AssistLabel,
21 pub(crate) group_label: Option<GroupLabel>,
22 pub(crate) action: Option<AssistAction>,
23}
24
25impl AssistInfo {
26 fn new(label: AssistLabel) -> AssistInfo {
27 AssistInfo { label, group_label: None, action: None }
28 }
29
30 fn resolved(self, action: AssistAction) -> AssistInfo {
31 AssistInfo { action: Some(action), ..self }
32 }
33
34 fn with_group(self, group_label: GroupLabel) -> AssistInfo {
35 AssistInfo { group_label: Some(group_label), ..self }
36 }
37
38 pub(crate) fn into_resolved(self) -> Option<ResolvedAssist> {
39 let label = self.label;
40 self.action.map(|action| ResolvedAssist { label, action })
41 }
42}
43
44/// `AssistCtx` allows to apply an assist or check if it could be applied.
45///
46/// Assists use a somewhat over-engineered approach, given the current needs. The
47/// assists workflow consists of two phases. In the first phase, a user asks for
48/// the list of available assists. In the second phase, the user picks a
49/// particular assist and it gets applied.
50///
51/// There are two peculiarities here:
52///
53/// * first, we ideally avoid computing more things then necessary to answer
54/// "is assist applicable" in the first phase.
55/// * second, when we are applying assist, we don't have a guarantee that there
56/// weren't any changes between the point when user asked for assists and when
57/// they applied a particular assist. So, when applying assist, we need to do
58/// all the checks from scratch.
59///
60/// To avoid repeating the same code twice for both "check" and "apply"
61/// functions, we use an approach reminiscent of that of Django's function based
62/// views dealing with forms. Each assist receives a runtime parameter,
63/// `should_compute_edit`. It first check if an edit is applicable (potentially
64/// computing info required to compute the actual edit). If it is applicable,
65/// and `should_compute_edit` is `true`, it then computes the actual edit.
66///
67/// So, to implement the original assists workflow, we can first apply each edit
68/// with `should_compute_edit = false`, and then applying the selected edit
69/// again, with `should_compute_edit = true` this time.
70///
71/// Note, however, that we don't actually use such two-phase logic at the
72/// moment, because the LSP API is pretty awkward in this place, and it's much
73/// easier to just compute the edit eagerly :-)
74#[derive(Clone)]
75pub(crate) struct AssistCtx<'a> {
76 pub(crate) sema: &'a Semantics<'a, RootDatabase>,
77 pub(crate) db: &'a RootDatabase,
78 pub(crate) frange: FileRange,
79 source_file: SourceFile,
80 should_compute_edit: bool,
81}
82
83impl<'a> AssistCtx<'a> {
84 pub fn new(
85 sema: &'a Semantics<'a, RootDatabase>,
86 frange: FileRange,
87 should_compute_edit: bool,
88 ) -> AssistCtx<'a> {
89 let source_file = sema.parse(frange.file_id);
90 AssistCtx { sema, db: sema.db, frange, source_file, should_compute_edit }
91 }
92
93 pub(crate) fn add_assist(
94 self,
95 id: AssistId,
96 label: impl Into<String>,
97 f: impl FnOnce(&mut ActionBuilder),
98 ) -> Option<Assist> {
99 let label = AssistLabel::new(id, label.into(), None);
100
101 let mut info = AssistInfo::new(label);
102 if self.should_compute_edit {
103 let action = {
104 let mut edit = ActionBuilder::new(&self);
105 f(&mut edit);
106 edit.build()
107 };
108 info = info.resolved(action)
109 };
110
111 Some(Assist(vec![info]))
112 }
113
114 pub(crate) fn add_assist_group(self, group_name: impl Into<String>) -> AssistGroup<'a> {
115 let group = GroupLabel(group_name.into());
116 AssistGroup { ctx: self, group, assists: Vec::new() }
117 }
118
119 pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
120 self.source_file.syntax().token_at_offset(self.frange.range.start())
121 }
122
123 pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
124 self.token_at_offset().find(|it| it.kind() == kind)
125 }
126
127 pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
128 find_node_at_offset(self.source_file.syntax(), self.frange.range.start())
129 }
130
131 pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
132 self.sema
133 .find_node_at_offset_with_descend(self.source_file.syntax(), self.frange.range.start())
134 }
135
136 pub(crate) fn covering_element(&self) -> SyntaxElement {
137 find_covering_element(self.source_file.syntax(), self.frange.range)
138 }
139 pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement {
140 find_covering_element(self.source_file.syntax(), range)
141 }
142}
143
144pub(crate) struct AssistGroup<'a> {
145 ctx: AssistCtx<'a>,
146 group: GroupLabel,
147 assists: Vec<AssistInfo>,
148}
149
150impl<'a> AssistGroup<'a> {
151 pub(crate) fn add_assist(
152 &mut self,
153 id: AssistId,
154 label: impl Into<String>,
155 f: impl FnOnce(&mut ActionBuilder),
156 ) {
157 let label = AssistLabel::new(id, label.into(), Some(self.group.clone()));
158
159 let mut info = AssistInfo::new(label).with_group(self.group.clone());
160 if self.ctx.should_compute_edit {
161 let action = {
162 let mut edit = ActionBuilder::new(&self.ctx);
163 f(&mut edit);
164 edit.build()
165 };
166 info = info.resolved(action)
167 };
168
169 self.assists.push(info)
170 }
171
172 pub(crate) fn finish(self) -> Option<Assist> {
173 if self.assists.is_empty() {
174 None
175 } else {
176 Some(Assist(self.assists))
177 }
178 }
179}
180
181pub(crate) struct ActionBuilder<'a, 'b> {
182 edit: TextEditBuilder,
183 cursor_position: Option<TextSize>,
184 target: Option<TextRange>,
185 file: AssistFile,
186 ctx: &'a AssistCtx<'b>,
187}
188
189impl<'a, 'b> ActionBuilder<'a, 'b> {
190 fn new(ctx: &'a AssistCtx<'b>) -> Self {
191 Self {
192 edit: TextEditBuilder::default(),
193 cursor_position: None,
194 target: None,
195 file: AssistFile::default(),
196 ctx,
197 }
198 }
199
200 pub(crate) fn ctx(&self) -> &AssistCtx<'b> {
201 &self.ctx
202 }
203
204 /// Replaces specified `range` of text with a given string.
205 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
206 self.edit.replace(range, replace_with.into())
207 }
208
209 /// Replaces specified `node` of text with a given string, reindenting the
210 /// string to maintain `node`'s existing indent.
211 // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent
212 pub(crate) fn replace_node_and_indent(
213 &mut self,
214 node: &SyntaxNode,
215 replace_with: impl Into<String>,
216 ) {
217 let mut replace_with = replace_with.into();
218 if let Some(indent) = leading_indent(node) {
219 replace_with = reindent(&replace_with, &indent)
220 }
221 self.replace(node.text_range(), replace_with)
222 }
223
224 /// Remove specified `range` of text.
225 #[allow(unused)]
226 pub(crate) fn delete(&mut self, range: TextRange) {
227 self.edit.delete(range)
228 }
229
230 /// Append specified `text` at the given `offset`
231 pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
232 self.edit.insert(offset, text.into())
233 }
234
235 /// Specify desired position of the cursor after the assist is applied.
236 pub(crate) fn set_cursor(&mut self, offset: TextSize) {
237 self.cursor_position = Some(offset)
238 }
239
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`.
249 pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
250 &mut self.edit
251 }
252
253 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
254 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
255 }
256 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
257 let node = rewriter.rewrite_root().unwrap();
258 let new = rewriter.rewrite(&node);
259 algo::diff(&node, &new).into_text_edit(&mut self.edit)
260 }
261
262 pub(crate) fn set_file(&mut self, assist_file: AssistFile) {
263 self.file = assist_file
264 }
265
266 fn build(self) -> AssistAction {
267 AssistAction {
268 edit: self.edit.finish(),
269 cursor_position: self.cursor_position,
270 target: self.target,
271 file: self.file,
272 }
273 }
274}
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..795a225a4 100644
--- a/crates/ra_assists/src/handlers/add_custom_impl.rs
+++ b/crates/ra_assists/src/handlers/add_custom_impl.rs
@@ -6,7 +6,10 @@ use ra_syntax::{
6}; 6};
7use stdx::SepBy; 7use stdx::SepBy;
8 8
9use crate::{Assist, AssistCtx, AssistId}; 9use crate::{
10 assist_context::{AssistContext, Assists},
11 AssistId,
12};
10 13
11// Assist: add_custom_impl 14// Assist: add_custom_impl
12// 15//
@@ -25,7 +28,7 @@ use crate::{Assist, AssistCtx, AssistId};
25// 28//
26// } 29// }
27// ``` 30// ```
28pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> { 31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
29 let input = ctx.find_node_at_offset::<ast::AttrInput>()?; 32 let input = ctx.find_node_at_offset::<ast::AttrInput>()?;
30 let attr = input.syntax().parent().and_then(ast::Attr::cast)?; 33 let attr = input.syntax().parent().and_then(ast::Attr::cast)?;
31 34
@@ -48,9 +51,8 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> {
48 let label = 51 let label =
49 format!("Add custom impl '{}' for '{}'", trait_token.text().as_str(), annotated_name); 52 format!("Add custom impl '{}' for '{}'", trait_token.text().as_str(), annotated_name);
50 53
51 ctx.add_assist(AssistId("add_custom_impl"), label, |edit| { 54 let target = attr.syntax().text_range();
52 edit.target(attr.syntax().text_range()); 55 acc.add(AssistId("add_custom_impl"), label, target, |edit| {
53
54 let new_attr_input = input 56 let new_attr_input = input
55 .syntax() 57 .syntax()
56 .descendants_with_tokens() 58 .descendants_with_tokens()
@@ -95,7 +97,7 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> {
95 97
96#[cfg(test)] 98#[cfg(test)]
97mod tests { 99mod tests {
98 use crate::helpers::{check_assist, check_assist_not_applicable}; 100 use crate::tests::{check_assist, check_assist_not_applicable};
99 101
100 use super::*; 102 use super::*;
101 103
diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/add_derive.rs
index 6254eb7c4..fb08c19e9 100644
--- a/crates/ra_assists/src/handlers/add_derive.rs
+++ b/crates/ra_assists/src/handlers/add_derive.rs
@@ -4,7 +4,7 @@ use ra_syntax::{
4 TextSize, 4 TextSize,
5}; 5};
6 6
7use crate::{Assist, AssistCtx, AssistId}; 7use crate::{AssistContext, AssistId, Assists};
8 8
9// Assist: add_derive 9// Assist: add_derive
10// 10//
@@ -24,10 +24,11 @@ use crate::{Assist, AssistCtx, AssistId};
24// y: u32, 24// y: u32,
25// } 25// }
26// ``` 26// ```
27pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> { 27pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
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 acc.add(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}
@@ -57,8 +57,9 @@ fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option<TextSize> {
57 57
58#[cfg(test)] 58#[cfg(test)]
59mod tests { 59mod tests {
60 use crate::tests::{check_assist, check_assist_target};
61
60 use super::*; 62 use super::*;
61 use crate::helpers::{check_assist, check_assist_target};
62 63
63 #[test] 64 #[test]
64 fn add_derive_new() { 65 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..55409e501 100644
--- a/crates/ra_assists/src/handlers/add_explicit_type.rs
+++ b/crates/ra_assists/src/handlers/add_explicit_type.rs
@@ -4,7 +4,7 @@ use ra_syntax::{
4 TextRange, 4 TextRange,
5}; 5};
6 6
7use crate::{Assist, AssistCtx, AssistId}; 7use crate::{AssistContext, AssistId, Assists};
8 8
9// Assist: add_explicit_type 9// Assist: add_explicit_type
10// 10//
@@ -21,7 +21,7 @@ use crate::{Assist, AssistCtx, AssistId};
21// let x: i32 = 92; 21// let x: i32 = 92;
22// } 22// }
23// ``` 23// ```
24pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> { 24pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let stmt = ctx.find_node_at_offset::<LetStmt>()?; 25 let stmt = ctx.find_node_at_offset::<LetStmt>()?;
26 let expr = stmt.initializer()?; 26 let expr = stmt.initializer()?;
27 let pat = stmt.pat()?; 27 let pat = stmt.pat()?;
@@ -59,11 +59,11 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> {
59 59
60 let db = ctx.db; 60 let db = ctx.db;
61 let new_type_string = ty.display_truncated(db, None).to_string(); 61 let new_type_string = ty.display_truncated(db, None).to_string();
62 ctx.add_assist( 62 acc.add(
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..275184e24 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
@@ -4,10 +4,10 @@ use ra_syntax::{
4 TextSize, 4 TextSize,
5}; 5};
6use stdx::format_to; 6use stdx::format_to;
7
8use crate::{utils::FamousDefs, Assist, AssistCtx, AssistId};
9use test_utils::tested_by; 7use test_utils::tested_by;
10 8
9use crate::{utils::FamousDefs, AssistContext, AssistId, Assists};
10
11// Assist add_from_impl_for_enum 11// Assist add_from_impl_for_enum
12// 12//
13// Adds a From impl for an enum variant with one tuple field 13// Adds a From impl for an enum variant with one tuple field
@@ -25,7 +25,7 @@ use test_utils::tested_by;
25// } 25// }
26// } 26// }
27// ``` 27// ```
28pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> { 28pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
29 let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?; 29 let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?;
30 let variant_name = variant.name()?; 30 let variant_name = variant.name()?;
31 let enum_name = variant.parent_enum().name()?; 31 let enum_name = variant.parent_enum().name()?;
@@ -42,14 +42,16 @@ pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> {
42 _ => return None, 42 _ => return None,
43 }; 43 };
44 44
45 if existing_from_impl(ctx.sema, &variant).is_some() { 45 if existing_from_impl(&ctx.sema, &variant).is_some() {
46 tested_by!(test_add_from_impl_already_exists); 46 tested_by!(test_add_from_impl_already_exists);
47 return None; 47 return None;
48 } 48 }
49 49
50 ctx.add_assist( 50 let target = variant.syntax().text_range();
51 acc.add(
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..6b5616aa9 100644
--- a/crates/ra_assists/src/handlers/add_function.rs
+++ b/crates/ra_assists/src/handlers/add_function.rs
@@ -1,13 +1,13 @@
1use hir::HirDisplay;
2use ra_db::FileId;
1use ra_syntax::{ 3use ra_syntax::{
2 ast::{self, AstNode}, 4 ast::{self, edit::IndentLevel, ArgListOwner, AstNode, ModuleItemOwner},
3 SyntaxKind, SyntaxNode, TextSize, 5 SyntaxKind, SyntaxNode, TextSize,
4}; 6};
5
6use crate::{Assist, AssistCtx, AssistFile, AssistId};
7use ast::{edit::IndentLevel, ArgListOwner, ModuleItemOwner};
8use hir::HirDisplay;
9use rustc_hash::{FxHashMap, FxHashSet}; 7use rustc_hash::{FxHashMap, FxHashSet};
10 8
9use crate::{AssistContext, AssistId, Assists};
10
11// Assist: add_function 11// Assist: add_function
12// 12//
13// Adds a stub function with a signature matching the function under the cursor. 13// Adds a stub function with a signature matching the function under the cursor.
@@ -33,7 +33,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
33// } 33// }
34// 34//
35// ``` 35// ```
36pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> { 36pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
37 let path_expr: ast::PathExpr = ctx.find_node_at_offset()?; 37 let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
38 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; 38 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
39 let path = path_expr.path()?; 39 let path = path_expr.path()?;
@@ -57,14 +57,12 @@ pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> {
57 57
58 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; 58 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
59 59
60 ctx.add_assist(AssistId("add_function"), "Add function", |edit| { 60 let target = call.syntax().text_range();
61 edit.target(call.syntax().text_range()); 61 acc.add(AssistId("add_function"), "Add function", target, |edit| {
62 62 let function_template = function_builder.render();
63 if let Some(function_template) = function_builder.render() { 63 edit.set_file(function_template.file);
64 edit.set_file(function_template.file); 64 edit.set_cursor(function_template.cursor_offset);
65 edit.set_cursor(function_template.cursor_offset); 65 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 }) 66 })
69} 67}
70 68
@@ -72,7 +70,7 @@ struct FunctionTemplate {
72 insert_offset: TextSize, 70 insert_offset: TextSize,
73 cursor_offset: TextSize, 71 cursor_offset: TextSize,
74 fn_def: ast::SourceFile, 72 fn_def: ast::SourceFile,
75 file: AssistFile, 73 file: FileId,
76} 74}
77 75
78struct FunctionBuilder { 76struct FunctionBuilder {
@@ -80,7 +78,7 @@ struct FunctionBuilder {
80 fn_name: ast::Name, 78 fn_name: ast::Name,
81 type_params: Option<ast::TypeParamList>, 79 type_params: Option<ast::TypeParamList>,
82 params: ast::ParamList, 80 params: ast::ParamList,
83 file: AssistFile, 81 file: FileId,
84 needs_pub: bool, 82 needs_pub: bool,
85} 83}
86 84
@@ -88,13 +86,13 @@ impl FunctionBuilder {
88 /// Prepares a generated function that matches `call` in `generate_in` 86 /// Prepares a generated function that matches `call` in `generate_in`
89 /// (or as close to `call` as possible, if `generate_in` is `None`) 87 /// (or as close to `call` as possible, if `generate_in` is `None`)
90 fn from_call( 88 fn from_call(
91 ctx: &AssistCtx, 89 ctx: &AssistContext,
92 call: &ast::CallExpr, 90 call: &ast::CallExpr,
93 path: &ast::Path, 91 path: &ast::Path,
94 target_module: Option<hir::InFile<hir::ModuleSource>>, 92 target_module: Option<hir::InFile<hir::ModuleSource>>,
95 ) -> Option<Self> { 93 ) -> Option<Self> {
96 let needs_pub = target_module.is_some(); 94 let needs_pub = target_module.is_some();
97 let mut file = AssistFile::default(); 95 let mut file = ctx.frange.file_id;
98 let target = if let Some(target_module) = target_module { 96 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)?; 97 let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, target_module)?;
100 file = in_file; 98 file = in_file;
@@ -107,7 +105,7 @@ impl FunctionBuilder {
107 Some(Self { target, fn_name, type_params, params, file, needs_pub }) 105 Some(Self { target, fn_name, type_params, params, file, needs_pub })
108 } 106 }
109 107
110 fn render(self) -> Option<FunctionTemplate> { 108 fn render(self) -> FunctionTemplate {
111 let placeholder_expr = ast::make::expr_todo(); 109 let placeholder_expr = ast::make::expr_todo();
112 let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr)); 110 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); 111 let mut fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body);
@@ -133,15 +131,11 @@ impl FunctionBuilder {
133 } 131 }
134 }; 132 };
135 133
136 let cursor_offset_from_fn_start = fn_def 134 let placeholder_expr =
137 .syntax() 135 fn_def.syntax().descendants().find_map(ast::MacroCall::cast).unwrap();
138 .descendants() 136 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; 137 let cursor_offset = insert_offset + cursor_offset_from_fn_start;
144 Some(FunctionTemplate { insert_offset, cursor_offset, fn_def, file: self.file }) 138 FunctionTemplate { insert_offset, cursor_offset, fn_def, file: self.file }
145 } 139 }
146} 140}
147 141
@@ -157,7 +151,7 @@ fn fn_name(call: &ast::Path) -> Option<ast::Name> {
157 151
158/// Computes the type variables and arguments required for the generated function 152/// Computes the type variables and arguments required for the generated function
159fn fn_args( 153fn fn_args(
160 ctx: &AssistCtx, 154 ctx: &AssistContext,
161 call: &ast::CallExpr, 155 call: &ast::CallExpr,
162) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { 156) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> {
163 let mut arg_names = Vec::new(); 157 let mut arg_names = Vec::new();
@@ -224,7 +218,7 @@ fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> {
224 } 218 }
225} 219}
226 220
227fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option<String> { 221fn fn_arg_type(ctx: &AssistContext, fn_arg: &ast::Expr) -> Option<String> {
228 let ty = ctx.sema.type_of_expr(fn_arg)?; 222 let ty = ctx.sema.type_of_expr(fn_arg)?;
229 if ty.is_unknown() { 223 if ty.is_unknown() {
230 return None; 224 return None;
@@ -259,9 +253,8 @@ fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFu
259fn next_space_for_fn_in_module( 253fn next_space_for_fn_in_module(
260 db: &dyn hir::db::AstDatabase, 254 db: &dyn hir::db::AstDatabase,
261 module: hir::InFile<hir::ModuleSource>, 255 module: hir::InFile<hir::ModuleSource>,
262) -> Option<(AssistFile, GeneratedFunctionTarget)> { 256) -> Option<(FileId, GeneratedFunctionTarget)> {
263 let file = module.file_id.original_file(db); 257 let file = module.file_id.original_file(db);
264 let assist_file = AssistFile::TargetFile(file);
265 let assist_item = match module.value { 258 let assist_item = match module.value {
266 hir::ModuleSource::SourceFile(it) => { 259 hir::ModuleSource::SourceFile(it) => {
267 if let Some(last_item) = it.items().last() { 260 if let Some(last_item) = it.items().last() {
@@ -278,12 +271,12 @@ fn next_space_for_fn_in_module(
278 } 271 }
279 } 272 }
280 }; 273 };
281 Some((assist_file, assist_item)) 274 Some((file, assist_item))
282} 275}
283 276
284#[cfg(test)] 277#[cfg(test)]
285mod tests { 278mod tests {
286 use crate::helpers::{check_assist, check_assist_not_applicable}; 279 use crate::tests::{check_assist, check_assist_not_applicable};
287 280
288 use super::*; 281 use super::*;
289 282
diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs
index d26f8b93d..df114a0d8 100644
--- a/crates/ra_assists/src/handlers/add_impl.rs
+++ b/crates/ra_assists/src/handlers/add_impl.rs
@@ -4,7 +4,7 @@ use ra_syntax::{
4}; 4};
5use stdx::{format_to, SepBy}; 5use stdx::{format_to, SepBy};
6 6
7use crate::{Assist, AssistCtx, AssistId}; 7use crate::{AssistContext, AssistId, Assists};
8 8
9// Assist: add_impl 9// Assist: add_impl
10// 10//
@@ -25,11 +25,11 @@ use crate::{Assist, AssistCtx, AssistId};
25// 25//
26// } 26// }
27// ``` 27// ```
28pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> { 28pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
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 acc.add(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), target, |edit| {
33 let type_params = nominal.type_param_list(); 33 let type_params = nominal.type_param_list();
34 let start_offset = nominal.syntax().text_range().end(); 34 let start_offset = nominal.syntax().text_range().end();
35 let mut buf = String::new(); 35 let mut buf = String::new();
@@ -60,7 +60,7 @@ pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> {
60#[cfg(test)] 60#[cfg(test)]
61mod tests { 61mod tests {
62 use super::*; 62 use super::*;
63 use crate::helpers::{check_assist, check_assist_target}; 63 use crate::tests::{check_assist, check_assist_target};
64 64
65 #[test] 65 #[test]
66 fn test_add_impl() { 66 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..3482a75bf 100644
--- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
@@ -9,9 +9,10 @@ use ra_syntax::{
9}; 9};
10 10
11use crate::{ 11use crate::{
12 assist_context::{AssistContext, Assists},
12 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, 13 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
13 utils::{get_missing_assoc_items, resolve_target_trait}, 14 utils::{get_missing_assoc_items, resolve_target_trait},
14 Assist, AssistCtx, AssistId, 15 AssistId,
15}; 16};
16 17
17#[derive(PartialEq)] 18#[derive(PartialEq)]
@@ -50,8 +51,9 @@ enum AddMissingImplMembersMode {
50// 51//
51// } 52// }
52// ``` 53// ```
53pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option<Assist> { 54pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
54 add_missing_impl_members_inner( 55 add_missing_impl_members_inner(
56 acc,
55 ctx, 57 ctx,
56 AddMissingImplMembersMode::NoDefaultMethods, 58 AddMissingImplMembersMode::NoDefaultMethods,
57 "add_impl_missing_members", 59 "add_impl_missing_members",
@@ -91,8 +93,9 @@ pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option<Assist> {
91// 93//
92// } 94// }
93// ``` 95// ```
94pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option<Assist> { 96pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
95 add_missing_impl_members_inner( 97 add_missing_impl_members_inner(
98 acc,
96 ctx, 99 ctx,
97 AddMissingImplMembersMode::DefaultMethodsOnly, 100 AddMissingImplMembersMode::DefaultMethodsOnly,
98 "add_impl_default_members", 101 "add_impl_default_members",
@@ -101,16 +104,17 @@ pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option<Assist> {
101} 104}
102 105
103fn add_missing_impl_members_inner( 106fn add_missing_impl_members_inner(
104 ctx: AssistCtx, 107 acc: &mut Assists,
108 ctx: &AssistContext,
105 mode: AddMissingImplMembersMode, 109 mode: AddMissingImplMembersMode,
106 assist_id: &'static str, 110 assist_id: &'static str,
107 label: &'static str, 111 label: &'static str,
108) -> Option<Assist> { 112) -> Option<()> {
109 let _p = ra_prof::profile("add_missing_impl_members_inner"); 113 let _p = ra_prof::profile("add_missing_impl_members_inner");
110 let impl_node = ctx.find_node_at_offset::<ast::ImplDef>()?; 114 let impl_def = ctx.find_node_at_offset::<ast::ImplDef>()?;
111 let impl_item_list = impl_node.item_list()?; 115 let impl_item_list = impl_def.item_list()?;
112 116
113 let trait_ = resolve_target_trait(&ctx.sema, &impl_node)?; 117 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
114 118
115 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> { 119 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
116 match item { 120 match item {
@@ -121,7 +125,7 @@ fn add_missing_impl_members_inner(
121 .map(|it| it.text().clone()) 125 .map(|it| it.text().clone())
122 }; 126 };
123 127
124 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_node) 128 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
125 .iter() 129 .iter()
126 .map(|i| match i { 130 .map(|i| match i {
127 hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value), 131 hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value),
@@ -142,14 +146,13 @@ fn add_missing_impl_members_inner(
142 return None; 146 return None;
143 } 147 }
144 148
145 let sema = ctx.sema; 149 let target = impl_def.syntax().text_range();
146 150 acc.add(AssistId(assist_id), label, target, |edit| {
147 ctx.add_assist(AssistId(assist_id), label, |edit| {
148 let n_existing_items = impl_item_list.assoc_items().count(); 151 let n_existing_items = impl_item_list.assoc_items().count();
149 let source_scope = sema.scope_for_def(trait_); 152 let source_scope = ctx.sema.scope_for_def(trait_);
150 let target_scope = sema.scope(impl_item_list.syntax()); 153 let target_scope = ctx.sema.scope(impl_item_list.syntax());
151 let ast_transform = QualifyPaths::new(&target_scope, &source_scope) 154 let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
152 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_node)); 155 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def));
153 let items = missing_items 156 let items = missing_items
154 .into_iter() 157 .into_iter()
155 .map(|it| ast_transform::apply(&*ast_transform, it)) 158 .map(|it| ast_transform::apply(&*ast_transform, it))
@@ -170,18 +173,17 @@ fn add_missing_impl_members_inner(
170} 173}
171 174
172fn add_body(fn_def: ast::FnDef) -> ast::FnDef { 175fn add_body(fn_def: ast::FnDef) -> ast::FnDef {
173 if fn_def.body().is_none() { 176 if fn_def.body().is_some() {
174 let body = make::block_expr(None, Some(make::expr_todo())); 177 return fn_def;
175 let body = IndentLevel(1).increase_indent(body);
176 fn_def.with_body(body)
177 } else {
178 fn_def
179 } 178 }
179 let body = make::block_expr(None, Some(make::expr_todo()));
180 let body = IndentLevel(1).increase_indent(body);
181 fn_def.with_body(body)
180} 182}
181 183
182#[cfg(test)] 184#[cfg(test)]
183mod tests { 185mod tests {
184 use crate::helpers::{check_assist, check_assist_not_applicable}; 186 use crate::tests::{check_assist, check_assist_not_applicable};
185 187
186 use super::*; 188 use super::*;
187 189
diff --git a/crates/ra_assists/src/handlers/add_new.rs b/crates/ra_assists/src/handlers/add_new.rs
index e8a36c7de..fe7451dcf 100644
--- a/crates/ra_assists/src/handlers/add_new.rs
+++ b/crates/ra_assists/src/handlers/add_new.rs
@@ -7,7 +7,7 @@ use ra_syntax::{
7}; 7};
8use stdx::{format_to, SepBy}; 8use stdx::{format_to, SepBy};
9 9
10use crate::{Assist, AssistCtx, AssistId}; 10use crate::{AssistContext, AssistId, Assists};
11 11
12// Assist: add_new 12// Assist: add_new
13// 13//
@@ -29,7 +29,7 @@ use crate::{Assist, AssistCtx, AssistId};
29// } 29// }
30// 30//
31// ``` 31// ```
32pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> { 32pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let strukt = ctx.find_node_at_offset::<ast::StructDef>()?; 33 let strukt = ctx.find_node_at_offset::<ast::StructDef>()?;
34 34
35 // We want to only apply this to non-union structs with named fields 35 // We want to only apply this to non-union structs with named fields
@@ -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 acc.add(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() {
@@ -124,7 +123,7 @@ fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String {
124// 123//
125// FIXME: change the new fn checking to a more semantic approach when that's more 124// FIXME: change the new fn checking to a more semantic approach when that's more
126// viable (e.g. we process proc macros, etc) 125// viable (e.g. we process proc macros, etc)
127fn find_struct_impl(ctx: &AssistCtx, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> { 126fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> {
128 let db = ctx.db; 127 let db = ctx.db;
129 let module = strukt.syntax().ancestors().find(|node| { 128 let module = strukt.syntax().ancestors().find(|node| {
130 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) 129 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
@@ -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..0feba5e11 100644
--- a/crates/ra_assists/src/handlers/apply_demorgan.rs
+++ b/crates/ra_assists/src/handlers/apply_demorgan.rs
@@ -1,6 +1,6 @@
1use ra_syntax::ast::{self, AstNode}; 1use ra_syntax::ast::{self, AstNode};
2 2
3use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; 3use crate::{utils::invert_boolean_expression, AssistContext, AssistId, Assists};
4 4
5// Assist: apply_demorgan 5// Assist: apply_demorgan
6// 6//
@@ -21,7 +21,7 @@ use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId};
21// if !(x == 4 && y) {} 21// if !(x == 4 && y) {}
22// } 22// }
23// ``` 23// ```
24pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option<Assist> { 24pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let expr = ctx.find_node_at_offset::<ast::BinExpr>()?; 25 let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
26 let op = expr.op_kind()?; 26 let op = expr.op_kind()?;
27 let op_range = expr.op_token()?.text_range(); 27 let op_range = expr.op_token()?.text_range();
@@ -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 acc.add(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..78d23150d 100644
--- a/crates/ra_assists/src/handlers/auto_import.rs
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -1,5 +1,6 @@
1use std::collections::BTreeSet; 1use std::collections::BTreeSet;
2 2
3use either::Either;
3use hir::{ 4use hir::{
4 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, 5 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait,
5 Type, 6 Type,
@@ -12,12 +13,7 @@ use ra_syntax::{
12}; 13};
13use rustc_hash::FxHashSet; 14use rustc_hash::FxHashSet;
14 15
15use crate::{ 16use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, GroupLabel};
16 assist_ctx::{Assist, AssistCtx},
17 utils::insert_use_statement,
18 AssistId,
19};
20use either::Either;
21 17
22// Assist: auto_import 18// Assist: auto_import
23// 19//
@@ -38,7 +34,7 @@ use either::Either;
38// } 34// }
39// # pub mod std { pub mod collections { pub struct HashMap { } } } 35// # pub mod std { pub mod collections { pub struct HashMap { } } }
40// ``` 36// ```
41pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { 37pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
42 let auto_import_assets = AutoImportAssets::new(&ctx)?; 38 let auto_import_assets = AutoImportAssets::new(&ctx)?;
43 let proposed_imports = auto_import_assets.search_for_imports(ctx.db); 39 let proposed_imports = auto_import_assets.search_for_imports(ctx.db);
44 if proposed_imports.is_empty() { 40 if proposed_imports.is_empty() {
@@ -46,14 +42,19 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> {
46 } 42 }
47 43
48 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range; 44 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()); 45 let group = auto_import_assets.get_import_group_message();
50 for import in proposed_imports { 46 for import in proposed_imports {
51 group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { 47 acc.add_group(
52 edit.target(range); 48 &group,
53 insert_use_statement(&auto_import_assets.syntax_under_caret, &import, edit); 49 AssistId("auto_import"),
54 }); 50 format!("Import `{}`", &import),
51 range,
52 |builder| {
53 insert_use_statement(&auto_import_assets.syntax_under_caret, &import, ctx, builder);
54 },
55 );
55 } 56 }
56 group.finish() 57 Some(())
57} 58}
58 59
59#[derive(Debug)] 60#[derive(Debug)]
@@ -64,7 +65,7 @@ struct AutoImportAssets {
64} 65}
65 66
66impl AutoImportAssets { 67impl AutoImportAssets {
67 fn new(ctx: &AssistCtx) -> Option<Self> { 68 fn new(ctx: &AssistContext) -> Option<Self> {
68 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() { 69 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
69 Self::for_regular_path(path_under_caret, &ctx) 70 Self::for_regular_path(path_under_caret, &ctx)
70 } else { 71 } else {
@@ -72,7 +73,7 @@ impl AutoImportAssets {
72 } 73 }
73 } 74 }
74 75
75 fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistCtx) -> Option<Self> { 76 fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistContext) -> Option<Self> {
76 let syntax_under_caret = method_call.syntax().to_owned(); 77 let syntax_under_caret = method_call.syntax().to_owned();
77 let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?; 78 let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?;
78 Some(Self { 79 Some(Self {
@@ -82,7 +83,7 @@ impl AutoImportAssets {
82 }) 83 })
83 } 84 }
84 85
85 fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistCtx) -> Option<Self> { 86 fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> {
86 let syntax_under_caret = path_under_caret.syntax().to_owned(); 87 let syntax_under_caret = path_under_caret.syntax().to_owned();
87 if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() { 88 if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() {
88 return None; 89 return None;
@@ -105,8 +106,8 @@ impl AutoImportAssets {
105 } 106 }
106 } 107 }
107 108
108 fn get_import_group_message(&self) -> String { 109 fn get_import_group_message(&self) -> GroupLabel {
109 match &self.import_candidate { 110 let name = match &self.import_candidate {
110 ImportCandidate::UnqualifiedName(name) => format!("Import {}", name), 111 ImportCandidate::UnqualifiedName(name) => format!("Import {}", name),
111 ImportCandidate::QualifierStart(qualifier_start) => { 112 ImportCandidate::QualifierStart(qualifier_start) => {
112 format!("Import {}", qualifier_start) 113 format!("Import {}", qualifier_start)
@@ -117,7 +118,8 @@ impl AutoImportAssets {
117 ImportCandidate::TraitMethod(_, trait_method_name) => { 118 ImportCandidate::TraitMethod(_, trait_method_name) => {
118 format!("Import a trait for method {}", trait_method_name) 119 format!("Import a trait for method {}", trait_method_name)
119 } 120 }
120 } 121 };
122 GroupLabel(name)
121 } 123 }
122 124
123 fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> { 125 fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> {
@@ -277,7 +279,7 @@ impl ImportCandidate {
277#[cfg(test)] 279#[cfg(test)]
278mod tests { 280mod tests {
279 use super::*; 281 use super::*;
280 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 282 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
281 283
282 #[test] 284 #[test]
283 fn applicable_when_found_an_import() { 285 fn applicable_when_found_an_import() {
@@ -384,7 +386,7 @@ mod tests {
384 } 386 }
385 ", 387 ",
386 r" 388 r"
387 use PubMod1::PubStruct; 389 use PubMod3::PubStruct;
388 390
389 PubSt<|>ruct 391 PubSt<|>ruct
390 392
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..5c907097e
--- /dev/null
+++ b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
@@ -0,0 +1,971 @@
1use ra_syntax::{
2 ast::{self, BlockExpr, Expr, LoopBodyOwner},
3 AstNode,
4 SyntaxKind::{COMMENT, WHITESPACE},
5 SyntaxNode, TextSize,
6};
7
8use crate::{AssistContext, AssistId, Assists};
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(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
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 acc.add(
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..e631766ef 100644
--- a/crates/ra_assists/src/handlers/change_visibility.rs
+++ b/crates/ra_assists/src/handlers/change_visibility.rs
@@ -7,10 +7,10 @@ use ra_syntax::{
7 }, 7 },
8 SyntaxNode, TextSize, T, 8 SyntaxNode, TextSize, T,
9}; 9};
10
11use crate::{Assist, AssistCtx, AssistId};
12use test_utils::tested_by; 10use test_utils::tested_by;
13 11
12use crate::{AssistContext, AssistId, Assists};
13
14// Assist: change_visibility 14// Assist: change_visibility
15// 15//
16// Adds or changes existing visibility specifier. 16// Adds or changes existing visibility specifier.
@@ -22,14 +22,14 @@ use test_utils::tested_by;
22// ``` 22// ```
23// pub(crate) fn frobnicate() {} 23// pub(crate) fn frobnicate() {}
24// ``` 24// ```
25pub(crate) fn change_visibility(ctx: AssistCtx) -> Option<Assist> { 25pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() { 26 if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() {
27 return change_vis(ctx, vis); 27 return change_vis(acc, vis);
28 } 28 }
29 add_vis(ctx) 29 add_vis(acc, ctx)
30} 30}
31 31
32fn add_vis(ctx: AssistCtx) -> Option<Assist> { 32fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { 33 let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() {
34 T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, 34 T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true,
35 _ => false, 35 _ => false,
@@ -66,8 +66,7 @@ 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 acc.add(AssistId("change_visibility"), "Change visibility to pub(crate)", target, |edit| {
70 edit.target(target);
71 edit.insert(offset, "pub(crate) "); 70 edit.insert(offset, "pub(crate) ");
72 edit.set_cursor(offset); 71 edit.set_cursor(offset);
73 }) 72 })
@@ -84,24 +83,30 @@ fn vis_offset(node: &SyntaxNode) -> TextSize {
84 .unwrap_or_else(|| node.text_range().start()) 83 .unwrap_or_else(|| node.text_range().start())
85} 84}
86 85
87fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> { 86fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
88 if vis.syntax().text() == "pub" { 87 if vis.syntax().text() == "pub" {
89 return ctx.add_assist( 88 let target = vis.syntax().text_range();
89 return acc.add(
90 AssistId("change_visibility"), 90 AssistId("change_visibility"),
91 "Change Visibility to pub(crate)", 91 "Change Visibility to pub(crate)",
92 target,
92 |edit| { 93 |edit| {
93 edit.target(vis.syntax().text_range());
94 edit.replace(vis.syntax().text_range(), "pub(crate)"); 94 edit.replace(vis.syntax().text_range(), "pub(crate)");
95 edit.set_cursor(vis.syntax().text_range().start()) 95 edit.set_cursor(vis.syntax().text_range().start())
96 }, 96 },
97 ); 97 );
98 } 98 }
99 if vis.syntax().text() == "pub(crate)" { 99 if vis.syntax().text() == "pub(crate)" {
100 return ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub", |edit| { 100 let target = vis.syntax().text_range();
101 edit.target(vis.syntax().text_range()); 101 return acc.add(
102 edit.replace(vis.syntax().text_range(), "pub"); 102 AssistId("change_visibility"),
103 edit.set_cursor(vis.syntax().text_range().start()); 103 "Change visibility to pub",
104 }); 104 target,
105 |edit| {
106 edit.replace(vis.syntax().text_range(), "pub");
107 edit.set_cursor(vis.syntax().text_range().start());
108 },
109 );
105 } 110 }
106 None 111 None
107} 112}
@@ -110,7 +115,7 @@ fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> {
110mod tests { 115mod tests {
111 use test_utils::covers; 116 use test_utils::covers;
112 117
113 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 118 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
114 119
115 use super::*; 120 use super::*;
116 121
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs
index eede2fe91..810784ad5 100644
--- a/crates/ra_assists/src/handlers/early_return.rs
+++ b/crates/ra_assists/src/handlers/early_return.rs
@@ -9,7 +9,7 @@ use ra_syntax::{
9}; 9};
10 10
11use crate::{ 11use crate::{
12 assist_ctx::{Assist, AssistCtx}, 12 assist_context::{AssistContext, Assists},
13 utils::invert_boolean_expression, 13 utils::invert_boolean_expression,
14 AssistId, 14 AssistId,
15}; 15};
@@ -36,7 +36,7 @@ use crate::{
36// bar(); 36// bar();
37// } 37// }
38// ``` 38// ```
39pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> { 39pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; 40 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
41 if if_expr.else_branch().is_some() { 41 if if_expr.else_branch().is_some() {
42 return None; 42 return None;
@@ -93,9 +93,10 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
93 } 93 }
94 94
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.offset();
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 acc.add(AssistId("convert_to_guarded_return"), "Convert to guarded return", target, |edit| {
99 let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); 100 let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
100 let new_block = match if_let_pat { 101 let new_block = match if_let_pat {
101 None => { 102 None => {
@@ -143,7 +144,6 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
143 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) 144 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
144 } 145 }
145 }; 146 };
146 edit.target(if_expr.syntax().text_range());
147 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap()); 147 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
148 edit.set_cursor(cursor_position); 148 edit.set_cursor(cursor_position);
149 149
@@ -182,7 +182,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
182 182
183#[cfg(test)] 183#[cfg(test)]
184mod tests { 184mod tests {
185 use crate::helpers::{check_assist, check_assist_not_applicable}; 185 use crate::tests::{check_assist, check_assist_not_applicable};
186 186
187 use super::*; 187 use super::*;
188 188
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
index 8d1af9933..13c1e7e80 100644
--- a/crates/ra_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ra_assists/src/handlers/fill_match_arms.rs
@@ -5,7 +5,7 @@ use itertools::Itertools;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; 6use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
7 7
8use crate::{Assist, AssistCtx, AssistId}; 8use crate::{AssistContext, AssistId, Assists};
9 9
10// Assist: fill_match_arms 10// Assist: fill_match_arms
11// 11//
@@ -31,7 +31,7 @@ use crate::{Assist, AssistCtx, AssistId};
31// } 31// }
32// } 32// }
33// ``` 33// ```
34pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { 34pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
35 let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?; 35 let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
36 let match_arm_list = match_expr.match_arm_list()?; 36 let match_arm_list = match_expr.match_arm_list()?;
37 37
@@ -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 acc.add(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..692ba4895 100644
--- a/crates/ra_assists/src/handlers/flip_binexpr.rs
+++ b/crates/ra_assists/src/handlers/flip_binexpr.rs
@@ -1,6 +1,6 @@
1use ra_syntax::ast::{AstNode, BinExpr, BinOp}; 1use ra_syntax::ast::{AstNode, BinExpr, BinOp};
2 2
3use crate::{Assist, AssistCtx, AssistId}; 3use crate::{AssistContext, AssistId, Assists};
4 4
5// Assist: flip_binexpr 5// Assist: flip_binexpr
6// 6//
@@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId};
17// let _ = 2 + 90; 17// let _ = 2 + 90;
18// } 18// }
19// ``` 19// ```
20pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option<Assist> { 20pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let expr = ctx.find_node_at_offset::<BinExpr>()?; 21 let expr = ctx.find_node_at_offset::<BinExpr>()?;
22 let lhs = expr.lhs()?.syntax().clone(); 22 let lhs = expr.lhs()?.syntax().clone();
23 let rhs = expr.rhs()?.syntax().clone(); 23 let rhs = expr.rhs()?.syntax().clone();
@@ -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 acc.add(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..dfe2a7fed 100644
--- a/crates/ra_assists/src/handlers/flip_comma.rs
+++ b/crates/ra_assists/src/handlers/flip_comma.rs
@@ -1,6 +1,6 @@
1use ra_syntax::{algo::non_trivia_sibling, Direction, T}; 1use ra_syntax::{algo::non_trivia_sibling, Direction, T};
2 2
3use crate::{Assist, AssistCtx, AssistId}; 3use crate::{AssistContext, AssistId, Assists};
4 4
5// Assist: flip_comma 5// Assist: flip_comma
6// 6//
@@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId};
17// ((3, 4), (1, 2)); 17// ((3, 4), (1, 2));
18// } 18// }
19// ``` 19// ```
20pub(crate) fn flip_comma(ctx: AssistCtx) -> Option<Assist> { 20pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let comma = ctx.find_token_at_offset(T![,])?; 21 let comma = ctx.find_token_at_offset(T![,])?;
22 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; 22 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
23 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; 23 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
@@ -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 acc.add(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..8a08702ab 100644
--- a/crates/ra_assists/src/handlers/flip_trait_bound.rs
+++ b/crates/ra_assists/src/handlers/flip_trait_bound.rs
@@ -4,7 +4,7 @@ use ra_syntax::{
4 Direction, T, 4 Direction, T,
5}; 5};
6 6
7use crate::{Assist, AssistCtx, AssistId}; 7use crate::{AssistContext, AssistId, Assists};
8 8
9// Assist: flip_trait_bound 9// Assist: flip_trait_bound
10// 10//
@@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId};
17// ``` 17// ```
18// fn foo<T: Copy + Clone>() { } 18// fn foo<T: Copy + Clone>() { }
19// ``` 19// ```
20pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> { 20pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 // We want to replicate the behavior of `flip_binexpr` by only suggesting 21 // We want to replicate the behavior of `flip_binexpr` by only suggesting
22 // the assist when the cursor is on a `+` 22 // the assist when the cursor is on a `+`
23 let plus = ctx.find_token_at_offset(T![+])?; 23 let plus = ctx.find_token_at_offset(T![+])?;
@@ -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 acc.add(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..5b26814d3 100644
--- a/crates/ra_assists/src/handlers/inline_local_variable.rs
+++ b/crates/ra_assists/src/handlers/inline_local_variable.rs
@@ -5,7 +5,10 @@ use ra_syntax::{
5}; 5};
6use test_utils::tested_by; 6use test_utils::tested_by;
7 7
8use crate::{assist_ctx::ActionBuilder, Assist, AssistCtx, AssistId}; 8use crate::{
9 assist_context::{AssistContext, Assists},
10 AssistId,
11};
9 12
10// Assist: inline_local_variable 13// Assist: inline_local_variable
11// 14//
@@ -23,7 +26,7 @@ use crate::{assist_ctx::ActionBuilder, Assist, AssistCtx, AssistId};
23// (1 + 2) * 4; 26// (1 + 2) * 4;
24// } 27// }
25// ``` 28// ```
26pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> { 29pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; 30 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
28 let bind_pat = match let_stmt.pat()? { 31 let bind_pat = match let_stmt.pat()? {
29 ast::Pat::BindPat(pat) => pat, 32 ast::Pat::BindPat(pat) => pat,
@@ -33,7 +36,7 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> {
33 tested_by!(test_not_inline_mut_variable); 36 tested_by!(test_not_inline_mut_variable);
34 return None; 37 return None;
35 } 38 }
36 if !bind_pat.syntax().text_range().contains_inclusive(ctx.frange.range.start()) { 39 if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) {
37 tested_by!(not_applicable_outside_of_bind_pat); 40 tested_by!(not_applicable_outside_of_bind_pat);
38 return None; 41 return None;
39 } 42 }
@@ -106,26 +109,22 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> {
106 let init_str = initializer_expr.syntax().text().to_string(); 109 let init_str = initializer_expr.syntax().text().to_string();
107 let init_in_paren = format!("({})", &init_str); 110 let init_in_paren = format!("({})", &init_str);
108 111
109 ctx.add_assist( 112 let target = bind_pat.syntax().text_range();
110 AssistId("inline_local_variable"), 113 acc.add(AssistId("inline_local_variable"), "Inline variable", target, move |builder| {
111 "Inline variable", 114 builder.delete(delete_range);
112 move |edit: &mut ActionBuilder| { 115 for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) {
113 edit.delete(delete_range); 116 let replacement = if should_wrap { init_in_paren.clone() } else { init_str.clone() };
114 for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { 117 builder.replace(desc.file_range.range, replacement)
115 let replacement = 118 }
116 if should_wrap { init_in_paren.clone() } else { init_str.clone() }; 119 builder.set_cursor(delete_range.start())
117 edit.replace(desc.file_range.range, replacement) 120 })
118 }
119 edit.set_cursor(delete_range.start())
120 },
121 )
122} 121}
123 122
124#[cfg(test)] 123#[cfg(test)]
125mod tests { 124mod tests {
126 use test_utils::covers; 125 use test_utils::covers;
127 126
128 use crate::helpers::{check_assist, check_assist_not_applicable}; 127 use crate::tests::{check_assist, check_assist_not_applicable};
129 128
130 use super::*; 129 use super::*;
131 130
diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/introduce_variable.rs
index 39c656305..fdf3ada0d 100644
--- a/crates/ra_assists/src/handlers/introduce_variable.rs
+++ b/crates/ra_assists/src/handlers/introduce_variable.rs
@@ -9,7 +9,7 @@ use ra_syntax::{
9use stdx::format_to; 9use stdx::format_to;
10use test_utils::tested_by; 10use test_utils::tested_by;
11 11
12use crate::{Assist, AssistCtx, AssistId}; 12use crate::{AssistContext, AssistId, Assists};
13 13
14// Assist: introduce_variable 14// Assist: introduce_variable
15// 15//
@@ -27,7 +27,7 @@ use crate::{Assist, AssistCtx, AssistId};
27// var_name * 4; 27// var_name * 4;
28// } 28// }
29// ``` 29// ```
30pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> { 30pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
31 if ctx.frange.range.is_empty() { 31 if ctx.frange.range.is_empty() {
32 return None; 32 return None;
33 } 33 }
@@ -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 acc.add(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..527c7caef 100644
--- a/crates/ra_assists/src/handlers/invert_if.rs
+++ b/crates/ra_assists/src/handlers/invert_if.rs
@@ -3,7 +3,11 @@ use ra_syntax::{
3 T, 3 T,
4}; 4};
5 5
6use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; 6use crate::{
7 assist_context::{AssistContext, Assists},
8 utils::invert_boolean_expression,
9 AssistId,
10};
7 11
8// Assist: invert_if 12// Assist: invert_if
9// 13//
@@ -24,7 +28,7 @@ use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId};
24// } 28// }
25// ``` 29// ```
26 30
27pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> { 31pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let if_keyword = ctx.find_token_at_offset(T![if])?; 32 let if_keyword = ctx.find_token_at_offset(T![if])?;
29 let expr = ast::IfExpr::cast(if_keyword.parent())?; 33 let expr = ast::IfExpr::cast(if_keyword.parent())?;
30 let if_range = if_keyword.text_range(); 34 let if_range = if_keyword.text_range();
@@ -40,29 +44,28 @@ pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> {
40 44
41 let cond = expr.condition()?.expr()?; 45 let cond = expr.condition()?.expr()?;
42 let then_node = expr.then_branch()?.syntax().clone(); 46 let then_node = expr.then_branch()?.syntax().clone();
47 let else_block = match expr.else_branch()? {
48 ast::ElseBranch::Block(it) => it,
49 ast::ElseBranch::IfExpr(_) => return None,
50 };
43 51
44 if let ast::ElseBranch::Block(else_block) = expr.else_branch()? { 52 let cond_range = cond.syntax().text_range();
45 let cond_range = cond.syntax().text_range(); 53 let flip_cond = invert_boolean_expression(cond);
46 let flip_cond = invert_boolean_expression(cond); 54 let else_node = else_block.syntax();
47 let else_node = else_block.syntax(); 55 let else_range = else_node.text_range();
48 let else_range = else_node.text_range(); 56 let then_range = then_node.text_range();
49 let then_range = then_node.text_range(); 57 acc.add(AssistId("invert_if"), "Invert if", if_range, |edit| {
50 return ctx.add_assist(AssistId("invert_if"), "Invert if", |edit| { 58 edit.replace(cond_range, flip_cond.syntax().text());
51 edit.target(if_range); 59 edit.replace(else_range, then_node.text());
52 edit.replace(cond_range, flip_cond.syntax().text()); 60 edit.replace(then_range, else_node.text());
53 edit.replace(else_range, then_node.text()); 61 })
54 edit.replace(then_range, else_node.text());
55 });
56 }
57
58 None
59} 62}
60 63
61#[cfg(test)] 64#[cfg(test)]
62mod tests { 65mod tests {
63 use super::*; 66 use super::*;
64 67
65 use crate::helpers::{check_assist, check_assist_not_applicable}; 68 use crate::tests::{check_assist, check_assist_not_applicable};
66 69
67 #[test] 70 #[test]
68 fn invert_if_remove_inequality() { 71 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..ac3e53c27 100644
--- a/crates/ra_assists/src/handlers/merge_imports.rs
+++ b/crates/ra_assists/src/handlers/merge_imports.rs
@@ -6,7 +6,10 @@ use ra_syntax::{
6 AstNode, Direction, InsertPosition, SyntaxElement, T, 6 AstNode, Direction, InsertPosition, SyntaxElement, T,
7}; 7};
8 8
9use crate::{Assist, AssistCtx, AssistId}; 9use crate::{
10 assist_context::{AssistContext, Assists},
11 AssistId,
12};
10 13
11// Assist: merge_imports 14// Assist: merge_imports
12// 15//
@@ -20,10 +23,10 @@ use crate::{Assist, AssistCtx, AssistId};
20// ``` 23// ```
21// use std::{fmt::Formatter, io}; 24// use std::{fmt::Formatter, io};
22// ``` 25// ```
23pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> { 26pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let tree: ast::UseTree = ctx.find_node_at_offset()?; 27 let tree: ast::UseTree = ctx.find_node_at_offset()?;
25 let mut rewriter = SyntaxRewriter::default(); 28 let mut rewriter = SyntaxRewriter::default();
26 let mut offset = ctx.frange.range.start(); 29 let mut offset = ctx.offset();
27 30
28 if let Some(use_item) = tree.syntax().parent().and_then(ast::UseItem::cast) { 31 if let Some(use_item) = tree.syntax().parent().and_then(ast::UseItem::cast) {
29 let (merged, to_delete) = next_prev() 32 let (merged, to_delete) = next_prev()
@@ -52,10 +55,11 @@ pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> {
52 } 55 }
53 }; 56 };
54 57
55 ctx.add_assist(AssistId("merge_imports"), "Merge imports", |edit| { 58 let target = tree.syntax().text_range();
56 edit.rewrite(rewriter); 59 acc.add(AssistId("merge_imports"), "Merge imports", target, |builder| {
60 builder.rewrite(rewriter);
57 // FIXME: we only need because our diff is imprecise 61 // FIXME: we only need because our diff is imprecise
58 edit.set_cursor(offset); 62 builder.set_cursor(offset);
59 }) 63 })
60} 64}
61 65
@@ -125,7 +129,7 @@ fn first_path(path: &ast::Path) -> ast::Path {
125 129
126#[cfg(test)] 130#[cfg(test)]
127mod tests { 131mod tests {
128 use crate::helpers::check_assist; 132 use crate::tests::check_assist;
129 133
130 use super::*; 134 use super::*;
131 135
diff --git a/crates/ra_assists/src/handlers/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs
index 5a77d3dbc..d4e38aa6a 100644
--- a/crates/ra_assists/src/handlers/merge_match_arms.rs
+++ b/crates/ra_assists/src/handlers/merge_match_arms.rs
@@ -6,7 +6,7 @@ use ra_syntax::{
6 Direction, TextSize, 6 Direction, TextSize,
7}; 7};
8 8
9use crate::{Assist, AssistCtx, AssistId, TextRange}; 9use crate::{AssistContext, AssistId, Assists, TextRange};
10 10
11// Assist: merge_match_arms 11// Assist: merge_match_arms
12// 12//
@@ -32,7 +32,7 @@ use crate::{Assist, AssistCtx, AssistId, TextRange};
32// } 32// }
33// } 33// }
34// ``` 34// ```
35pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> { 35pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36 let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?; 36 let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
37 // Don't try to handle arms with guards for now - can add support for this later 37 // Don't try to handle arms with guards for now - can add support for this later
38 if current_arm.guard().is_some() { 38 if current_arm.guard().is_some() {
@@ -45,7 +45,7 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> {
45 InExpr(TextSize), 45 InExpr(TextSize),
46 InPat(TextSize), 46 InPat(TextSize),
47 } 47 }
48 let cursor_pos = ctx.frange.range.start(); 48 let cursor_pos = ctx.offset();
49 let cursor_pos = if current_expr.syntax().text_range().contains(cursor_pos) { 49 let cursor_pos = if current_expr.syntax().text_range().contains(cursor_pos) {
50 CursorPos::InExpr(current_text_range.end() - cursor_pos) 50 CursorPos::InExpr(current_text_range.end() - cursor_pos)
51 } else { 51 } else {
@@ -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 acc.add(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..a41aacfc3 100644
--- a/crates/ra_assists/src/handlers/move_bounds.rs
+++ b/crates/ra_assists/src/handlers/move_bounds.rs
@@ -5,7 +5,7 @@ use ra_syntax::{
5 T, 5 T,
6}; 6};
7 7
8use crate::{Assist, AssistCtx, AssistId}; 8use crate::{AssistContext, AssistId, Assists};
9 9
10// Assist: move_bounds_to_where_clause 10// Assist: move_bounds_to_where_clause
11// 11//
@@ -22,7 +22,7 @@ use crate::{Assist, AssistCtx, AssistId};
22// f(x) 22// f(x)
23// } 23// }
24// ``` 24// ```
25pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> { 25pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?; 26 let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?;
27 27
28 let mut type_params = type_param_list.type_params(); 28 let mut type_params = type_param_list.type_params();
@@ -49,7 +49,8 @@ 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 acc.add(AssistId("move_bounds_to_where_clause"), "Move to where clause", target, |edit| {
53 let new_params = type_param_list 54 let new_params = type_param_list
54 .type_params() 55 .type_params()
55 .filter(|it| it.type_bound_list().is_some()) 56 .filter(|it| it.type_bound_list().is_some())
@@ -71,7 +72,6 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> {
71 _ => format!(" {}", where_clause.syntax()), 72 _ => format!(" {}", where_clause.syntax()),
72 }; 73 };
73 edit.insert(anchor.text_range().start(), to_insert); 74 edit.insert(anchor.text_range().start(), to_insert);
74 edit.target(type_param_list.syntax().text_range());
75 }) 75 })
76} 76}
77 77
@@ -89,7 +89,7 @@ fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {
89mod tests { 89mod tests {
90 use super::*; 90 use super::*;
91 91
92 use crate::helpers::check_assist; 92 use crate::tests::check_assist;
93 93
94 #[test] 94 #[test]
95 fn move_bounds_to_where_clause_fn() { 95 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..fc0335b57 100644
--- a/crates/ra_assists/src/handlers/move_guard.rs
+++ b/crates/ra_assists/src/handlers/move_guard.rs
@@ -4,7 +4,7 @@ use ra_syntax::{
4 TextSize, 4 TextSize,
5}; 5};
6 6
7use crate::{Assist, AssistCtx, AssistId}; 7use crate::{AssistContext, AssistId, Assists};
8 8
9// Assist: move_guard_to_arm_body 9// Assist: move_guard_to_arm_body
10// 10//
@@ -31,7 +31,7 @@ use crate::{Assist, AssistCtx, AssistId};
31// } 31// }
32// } 32// }
33// ``` 33// ```
34pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> { 34pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
35 let match_arm = ctx.find_node_at_offset::<MatchArm>()?; 35 let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
36 let guard = match_arm.guard()?; 36 let guard = match_arm.guard()?;
37 let space_before_guard = guard.syntax().prev_sibling_or_token(); 37 let space_before_guard = guard.syntax().prev_sibling_or_token();
@@ -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 acc.add(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() {
@@ -88,7 +88,7 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> {
88// } 88// }
89// } 89// }
90// ``` 90// ```
91pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> { 91pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
92 let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?; 92 let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
93 let match_pat = match_arm.pat()?; 93 let match_pat = match_arm.pat()?;
94 94
@@ -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 ctx.add_assist( 111 let target = if_expr.syntax().text_range();
112 acc.add(
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..c20ffe0b3 100644
--- a/crates/ra_assists/src/handlers/raw_string.rs
+++ b/crates/ra_assists/src/handlers/raw_string.rs
@@ -5,7 +5,7 @@ use ra_syntax::{
5 TextSize, 5 TextSize,
6}; 6};
7 7
8use crate::{Assist, AssistCtx, AssistId}; 8use crate::{AssistContext, AssistId, Assists};
9 9
10// Assist: make_raw_string 10// Assist: make_raw_string
11// 11//
@@ -22,11 +22,11 @@ use crate::{Assist, AssistCtx, AssistId};
22// r#"Hello, World!"#; 22// r#"Hello, World!"#;
23// } 23// }
24// ``` 24// ```
25pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> { 25pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
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 acc.add(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() {
@@ -51,11 +51,11 @@ pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> {
51// "Hello, \"World!\""; 51// "Hello, \"World!\"";
52// } 52// }
53// ``` 53// ```
54pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> { 54pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
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 acc.add(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));
@@ -77,10 +77,10 @@ pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
77// r##"Hello, World!"##; 77// r##"Hello, World!"##;
78// } 78// }
79// ``` 79// ```
80pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> { 80pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
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 acc.add(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 })
@@ -101,15 +101,15 @@ pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> {
101// r"Hello, World!"; 101// r"Hello, World!";
102// } 102// }
103// ``` 103// ```
104pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> { 104pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
105 let token = ctx.find_token_at_offset(RAW_STRING)?; 105 let token = ctx.find_token_at_offset(RAW_STRING)?;
106 let text = token.text().as_str(); 106 let text = token.text().as_str();
107 if text.starts_with("r\"") { 107 if text.starts_with("r\"") {
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 acc.add(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..8eef578cf 100644
--- a/crates/ra_assists/src/handlers/remove_dbg.rs
+++ b/crates/ra_assists/src/handlers/remove_dbg.rs
@@ -3,7 +3,7 @@ use ra_syntax::{
3 TextSize, T, 3 TextSize, T,
4}; 4};
5 5
6use crate::{Assist, AssistCtx, AssistId}; 6use crate::{AssistContext, AssistId, Assists};
7 7
8// Assist: remove_dbg 8// Assist: remove_dbg
9// 9//
@@ -20,7 +20,7 @@ use crate::{Assist, AssistCtx, AssistId};
20// 92; 20// 92;
21// } 21// }
22// ``` 22// ```
23pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> { 23pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; 24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
25 25
26 if !is_valid_macrocall(&macro_call, "dbg")? { 26 if !is_valid_macrocall(&macro_call, "dbg")? {
@@ -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 acc.add(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..dce546db7 100644
--- a/crates/ra_assists/src/handlers/remove_mut.rs
+++ b/crates/ra_assists/src/handlers/remove_mut.rs
@@ -1,6 +1,6 @@
1use ra_syntax::{SyntaxKind, TextRange, T}; 1use ra_syntax::{SyntaxKind, TextRange, T};
2 2
3use crate::{Assist, AssistCtx, AssistId}; 3use crate::{AssistContext, AssistId, Assists};
4 4
5// Assist: remove_mut 5// Assist: remove_mut
6// 6//
@@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId};
17// fn feed(&self, amount: u32) {} 17// fn feed(&self, amount: u32) {}
18// } 18// }
19// ``` 19// ```
20pub(crate) fn remove_mut(ctx: AssistCtx) -> Option<Assist> { 20pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let mut_token = ctx.find_token_at_offset(T![mut])?; 21 let mut_token = ctx.find_token_at_offset(T![mut])?;
22 let delete_from = mut_token.text_range().start(); 22 let delete_from = mut_token.text_range().start();
23 let delete_to = match mut_token.next_token() { 23 let delete_to = match mut_token.next_token() {
@@ -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 acc.add(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..757f6406e 100644
--- a/crates/ra_assists/src/handlers/reorder_fields.rs
+++ b/crates/ra_assists/src/handlers/reorder_fields.rs
@@ -3,18 +3,9 @@ use std::collections::HashMap;
3use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct}; 3use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
4use itertools::Itertools; 4use itertools::Itertools;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode};
7 algo, 7
8 ast::{self, Path, RecordLit, RecordPat}, 8use crate::{AssistContext, AssistId, Assists};
9 match_ast, AstNode, SyntaxKind,
10 SyntaxKind::*,
11 SyntaxNode,
12};
13
14use crate::{
15 assist_ctx::{Assist, AssistCtx},
16 AssistId,
17};
18 9
19// Assist: reorder_fields 10// Assist: reorder_fields
20// 11//
@@ -31,13 +22,13 @@ use crate::{
31// const test: Foo = Foo {foo: 1, bar: 0} 22// const test: Foo = Foo {foo: 1, bar: 0}
32// ``` 23// ```
33// 24//
34pub(crate) fn reorder_fields(ctx: AssistCtx) -> Option<Assist> { 25pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
35 reorder::<RecordLit>(ctx.clone()).or_else(|| reorder::<RecordPat>(ctx)) 26 reorder::<ast::RecordLit>(acc, ctx.clone()).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
36} 27}
37 28
38fn reorder<R: AstNode>(ctx: AssistCtx) -> Option<Assist> { 29fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39 let record = ctx.find_node_at_offset::<R>()?; 30 let record = ctx.find_node_at_offset::<R>()?;
40 let path = record.syntax().children().find_map(Path::cast)?; 31 let path = record.syntax().children().find_map(ast::Path::cast)?;
41 32
42 let ranks = compute_fields_ranks(&path, &ctx)?; 33 let ranks = compute_fields_ranks(&path, &ctx)?;
43 34
@@ -50,11 +41,11 @@ fn reorder<R: AstNode>(ctx: AssistCtx) -> Option<Assist> {
50 return None; 41 return None;
51 } 42 }
52 43
53 ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", |edit| { 44 let target = record.syntax().text_range();
45 acc.add(AssistId("reorder_fields"), "Reorder record fields", target, |edit| {
54 for (old, new) in fields.iter().zip(&sorted_fields) { 46 for (old, new) in fields.iter().zip(&sorted_fields) {
55 algo::diff(old, new).into_text_edit(edit.text_edit_builder()); 47 algo::diff(old, new).into_text_edit(edit.text_edit_builder());
56 } 48 }
57 edit.target(record.syntax().text_range())
58 }) 49 })
59} 50}
60 51
@@ -96,9 +87,9 @@ fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option
96 } 87 }
97} 88}
98 89
99fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> Option<HashMap<String, usize>> { 90fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<HashMap<String, usize>> {
100 Some( 91 Some(
101 struct_definition(path, ctx.sema)? 92 struct_definition(path, &ctx.sema)?
102 .fields(ctx.db) 93 .fields(ctx.db)
103 .iter() 94 .iter()
104 .enumerate() 95 .enumerate()
@@ -109,7 +100,7 @@ fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> Option<HashMap<String,
109 100
110#[cfg(test)] 101#[cfg(test)]
111mod tests { 102mod tests {
112 use crate::helpers::{check_assist, check_assist_not_applicable}; 103 use crate::tests::{check_assist, check_assist_not_applicable};
113 104
114 use super::*; 105 use super::*;
115 106
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..a59a06efa 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
@@ -4,7 +4,7 @@ use ra_syntax::{
4 AstNode, 4 AstNode,
5}; 5};
6 6
7use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; 7use crate::{utils::TryEnum, AssistContext, AssistId, Assists};
8 8
9// Assist: replace_if_let_with_match 9// Assist: replace_if_let_with_match
10// 10//
@@ -32,7 +32,7 @@ use crate::{utils::TryEnum, Assist, AssistCtx, AssistId};
32// } 32// }
33// } 33// }
34// ``` 34// ```
35pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> { 35pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; 36 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
37 let cond = if_expr.condition()?; 37 let cond = if_expr.condition()?;
38 let pat = cond.pat()?; 38 let pat = cond.pat()?;
@@ -43,17 +43,18 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
43 ast::ElseBranch::IfExpr(_) => return None, 43 ast::ElseBranch::IfExpr(_) => return None,
44 }; 44 };
45 45
46 let sema = ctx.sema; 46 let target = if_expr.syntax().text_range();
47 ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", move |edit| { 47 acc.add(AssistId("replace_if_let_with_match"), "Replace with match", target, move |edit| {
48 let match_expr = { 48 let match_expr = {
49 let then_arm = { 49 let then_arm = {
50 let then_expr = unwrap_trivial_block(then_block); 50 let then_expr = unwrap_trivial_block(then_block);
51 make::match_arm(vec![pat.clone()], then_expr) 51 make::match_arm(vec![pat.clone()], then_expr)
52 }; 52 };
53 let else_arm = { 53 let else_arm = {
54 let pattern = sema 54 let pattern = ctx
55 .sema
55 .type_of_pat(&pat) 56 .type_of_pat(&pat)
56 .and_then(|ty| TryEnum::from_ty(sema, &ty)) 57 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
57 .map(|it| it.sad_pattern()) 58 .map(|it| it.sad_pattern())
58 .unwrap_or_else(|| make::placeholder_pat().into()); 59 .unwrap_or_else(|| make::placeholder_pat().into());
59 let else_expr = unwrap_trivial_block(else_block); 60 let else_expr = unwrap_trivial_block(else_block);
@@ -64,7 +65,6 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
64 65
65 let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr); 66 let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr);
66 67
67 edit.target(if_expr.syntax().text_range());
68 edit.set_cursor(if_expr.syntax().text_range().start()); 68 edit.set_cursor(if_expr.syntax().text_range().start());
69 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr); 69 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
70 }) 70 })
@@ -74,7 +74,7 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
74mod tests { 74mod tests {
75 use super::*; 75 use super::*;
76 76
77 use crate::helpers::{check_assist, check_assist_target}; 77 use crate::tests::{check_assist, check_assist_target};
78 78
79 #[test] 79 #[test]
80 fn test_replace_if_let_with_match_unwraps_simple_expressions() { 80 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..d3f214591 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
@@ -9,11 +9,7 @@ use ra_syntax::{
9 AstNode, T, 9 AstNode, T,
10}; 10};
11 11
12use crate::{ 12use crate::{utils::TryEnum, AssistContext, AssistId, Assists};
13 assist_ctx::{Assist, AssistCtx},
14 utils::TryEnum,
15 AssistId,
16};
17 13
18// Assist: replace_let_with_if_let 14// Assist: replace_let_with_if_let
19// 15//
@@ -39,15 +35,16 @@ use crate::{
39// 35//
40// fn compute() -> Option<i32> { None } 36// fn compute() -> Option<i32> { None }
41// ``` 37// ```
42pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> { 38pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
43 let let_kw = ctx.find_token_at_offset(T![let])?; 39 let let_kw = ctx.find_token_at_offset(T![let])?;
44 let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?; 40 let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?;
45 let init = let_stmt.initializer()?; 41 let init = let_stmt.initializer()?;
46 let original_pat = let_stmt.pat()?; 42 let original_pat = let_stmt.pat()?;
47 let ty = ctx.sema.type_of_expr(&init)?; 43 let ty = ctx.sema.type_of_expr(&init)?;
48 let happy_variant = TryEnum::from_ty(ctx.sema, &ty).map(|it| it.happy_case()); 44 let happy_variant = TryEnum::from_ty(&ctx.sema, &ty).map(|it| it.happy_case());
49 45
50 ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", |edit| { 46 let target = let_kw.text_range();
47 acc.add(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| {
51 let with_placeholder: ast::Pat = match happy_variant { 48 let with_placeholder: ast::Pat = match happy_variant {
52 None => make::placeholder_pat().into(), 49 None => make::placeholder_pat().into(),
53 Some(var_name) => make::tuple_struct_pat( 50 Some(var_name) => make::tuple_struct_pat(
@@ -67,14 +64,13 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> {
67 let stmt = stmt.replace_descendant(placeholder.into(), original_pat); 64 let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
68 65
69 edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt)); 66 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); 67 edit.set_cursor(target_offset);
72 }) 68 })
73} 69}
74 70
75#[cfg(test)] 71#[cfg(test)]
76mod tests { 72mod tests {
77 use crate::helpers::check_assist; 73 use crate::tests::check_assist;
78 74
79 use super::*; 75 use super::*;
80 76
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..1a81d8a0e 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
@@ -1,11 +1,7 @@
1use hir; 1use hir;
2use ra_syntax::{ast, AstNode, SmolStr, TextRange}; 2use ra_syntax::{ast, AstNode, SmolStr, TextRange};
3 3
4use crate::{ 4use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists};
5 assist_ctx::{Assist, AssistCtx},
6 utils::insert_use_statement,
7 AssistId,
8};
9 5
10// Assist: replace_qualified_name_with_use 6// Assist: replace_qualified_name_with_use
11// 7//
@@ -20,7 +16,10 @@ use crate::{
20// 16//
21// fn process(map: HashMap<String, String>) {} 17// fn process(map: HashMap<String, String>) {}
22// ``` 18// ```
23pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist> { 19pub(crate) fn replace_qualified_name_with_use(
20 acc: &mut Assists,
21 ctx: &AssistContext,
22) -> Option<()> {
24 let path: ast::Path = ctx.find_node_at_offset()?; 23 let path: ast::Path = ctx.find_node_at_offset()?;
25 // We don't want to mess with use statements 24 // We don't want to mess with use statements
26 if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { 25 if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() {
@@ -33,17 +32,19 @@ pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist>
33 return None; 32 return None;
34 } 33 }
35 34
36 ctx.add_assist( 35 let target = path.syntax().text_range();
36 acc.add(
37 AssistId("replace_qualified_name_with_use"), 37 AssistId("replace_qualified_name_with_use"),
38 "Replace qualified path with use", 38 "Replace qualified path with use",
39 |edit| { 39 target,
40 |builder| {
40 let path_to_import = hir_path.mod_path().clone(); 41 let path_to_import = hir_path.mod_path().clone();
41 insert_use_statement(path.syntax(), &path_to_import, edit); 42 insert_use_statement(path.syntax(), &path_to_import, ctx, builder);
42 43
43 if let Some(last) = path.segment() { 44 if let Some(last) = path.segment() {
44 // Here we are assuming the assist will provide a correct use statement 45 // Here we are assuming the assist will provide a correct use statement
45 // so we can delete the path qualifier 46 // so we can delete the path qualifier
46 edit.delete(TextRange::new( 47 builder.delete(TextRange::new(
47 path.syntax().text_range().start(), 48 path.syntax().text_range().start(),
48 last.syntax().text_range().start(), 49 last.syntax().text_range().start(),
49 )); 50 ));
@@ -74,7 +75,7 @@ fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
74 75
75#[cfg(test)] 76#[cfg(test)]
76mod tests { 77mod tests {
77 use crate::helpers::{check_assist, check_assist_not_applicable}; 78 use crate::tests::{check_assist, check_assist_not_applicable};
78 79
79 use super::*; 80 use super::*;
80 81
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..a46998b8e 100644
--- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
@@ -5,7 +5,7 @@ use ra_syntax::{
5 AstNode, 5 AstNode,
6}; 6};
7 7
8use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; 8use crate::{utils::TryEnum, AssistContext, AssistId, Assists};
9 9
10// Assist: replace_unwrap_with_match 10// Assist: replace_unwrap_with_match
11// 11//
@@ -29,7 +29,7 @@ use crate::{utils::TryEnum, Assist, AssistCtx, AssistId};
29// }; 29// };
30// } 30// }
31// ``` 31// ```
32pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> { 32pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?; 33 let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
34 let name = method_call.name_ref()?; 34 let name = method_call.name_ref()?;
35 if name.text() != "unwrap" { 35 if name.text() != "unwrap" {
@@ -37,9 +37,9 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
37 } 37 }
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 41 let target = method_call.syntax().text_range();
42 ctx.add_assist(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", |edit| { 42 acc.add(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", target, |edit| {
43 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); 43 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
44 let it = make::bind_pat(make::name("a")).into(); 44 let it = make::bind_pat(make::name("a")).into();
45 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); 45 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
@@ -54,7 +54,6 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
54 let match_expr = make::expr_match(caller.clone(), match_arm_list); 54 let match_expr = make::expr_match(caller.clone(), match_arm_list);
55 let match_expr = IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr); 55 let match_expr = IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr);
56 56
57 edit.target(method_call.syntax().text_range());
58 edit.set_cursor(caller.syntax().text_range().start()); 57 edit.set_cursor(caller.syntax().text_range().start());
59 edit.replace_ast::<ast::Expr>(method_call.into(), match_expr); 58 edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
60 }) 59 })
@@ -63,7 +62,7 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
63#[cfg(test)] 62#[cfg(test)]
64mod tests { 63mod tests {
65 use super::*; 64 use super::*;
66 use crate::helpers::{check_assist, check_assist_target}; 65 use crate::tests::{check_assist, check_assist_target};
67 66
68 #[test] 67 #[test]
69 fn test_replace_result_unwrap_with_match() { 68 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..b2757e50c 100644
--- a/crates/ra_assists/src/handlers/split_import.rs
+++ b/crates/ra_assists/src/handlers/split_import.rs
@@ -2,7 +2,7 @@ use std::iter::successors;
2 2
3use ra_syntax::{ast, AstNode, T}; 3use ra_syntax::{ast, AstNode, T};
4 4
5use crate::{Assist, AssistCtx, AssistId}; 5use crate::{AssistContext, AssistId, Assists};
6 6
7// Assist: split_import 7// Assist: split_import
8// 8//
@@ -15,7 +15,7 @@ use crate::{Assist, AssistCtx, AssistId};
15// ``` 15// ```
16// use std::{collections::HashMap}; 16// use std::{collections::HashMap};
17// ``` 17// ```
18pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> { 18pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
19 let colon_colon = ctx.find_token_at_offset(T![::])?; 19 let colon_colon = ctx.find_token_at_offset(T![::])?;
20 let path = ast::Path::cast(colon_colon.parent())?.qualifier()?; 20 let path = ast::Path::cast(colon_colon.parent())?.qualifier()?;
21 let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?; 21 let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?;
@@ -26,10 +26,10 @@ pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> {
26 if new_tree == use_tree { 26 if new_tree == use_tree {
27 return None; 27 return None;
28 } 28 }
29 let cursor = ctx.frange.range.start(); 29 let cursor = ctx.offset();
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 acc.add(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..eba0631a4 100644
--- a/crates/ra_assists/src/handlers/unwrap_block.rs
+++ b/crates/ra_assists/src/handlers/unwrap_block.rs
@@ -1,4 +1,4 @@
1use crate::{Assist, AssistCtx, AssistId}; 1use crate::{AssistContext, AssistId, Assists};
2 2
3use ast::LoopBodyOwner; 3use ast::LoopBodyOwner;
4use ra_fmt::unwrap_trivial_block; 4use ra_fmt::unwrap_trivial_block;
@@ -21,7 +21,7 @@ use ra_syntax::{ast, match_ast, AstNode, TextRange, T};
21// println!("foo"); 21// println!("foo");
22// } 22// }
23// ``` 23// ```
24pub(crate) fn unwrap_block(ctx: AssistCtx) -> Option<Assist> { 24pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let l_curly_token = ctx.find_token_at_offset(T!['{'])?; 25 let l_curly_token = ctx.find_token_at_offset(T!['{'])?;
26 let block = ast::BlockExpr::cast(l_curly_token.parent())?; 26 let block = ast::BlockExpr::cast(l_curly_token.parent())?;
27 let parent = block.syntax().parent()?; 27 let parent = block.syntax().parent()?;
@@ -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 acc.add(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..b6dc7cb1b 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -10,118 +10,102 @@ macro_rules! eprintln {
10 ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; 10 ($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
11} 11}
12 12
13mod assist_ctx; 13mod assist_context;
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_context::{AssistContext, Assists};
27 26
28/// Unique identifier of the assist, should not be shown to the user 27/// Unique identifier of the assist, should not be shown to the user
29/// directly. 28/// directly.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)] 29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct AssistId(pub &'static str); 30pub struct AssistId(pub &'static str);
32 31
32#[derive(Clone, Debug)]
33pub struct GroupLabel(pub String);
34
33#[derive(Debug, Clone)] 35#[derive(Debug, Clone)]
34pub struct AssistLabel { 36pub struct Assist {
35 pub id: AssistId, 37 pub id: AssistId,
36 /// Short description of the assist, as shown in the UI. 38 /// Short description of the assist, as shown in the UI.
37 pub label: String, 39 pub label: String,
38 pub group: Option<GroupLabel>, 40 pub group: Option<GroupLabel>,
39} 41 /// Target ranges are used to sort assists: the smaller the target range,
40 42 /// the more specific assist is, and so it should be sorted first.
41#[derive(Clone, Debug)] 43 pub target: TextRange,
42pub struct GroupLabel(pub String);
43
44impl AssistLabel {
45 pub(crate) fn new(id: AssistId, label: String, group: Option<GroupLabel>) -> AssistLabel {
46 // FIXME: make fields private, so that this invariant can't be broken
47 assert!(label.starts_with(|c: char| c.is_uppercase()));
48 AssistLabel { id, label, group }
49 }
50}
51
52#[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} 44}
60 45
61#[derive(Debug, Clone)] 46#[derive(Debug, Clone)]
62pub struct ResolvedAssist { 47pub struct ResolvedAssist {
63 pub label: AssistLabel, 48 pub assist: Assist,
64 pub action: AssistAction, 49 pub source_change: SourceChange,
65}
66
67#[derive(Debug, Clone, Copy)]
68pub enum AssistFile {
69 CurrentFile,
70 TargetFile(FileId),
71} 50}
72 51
73impl Default for AssistFile { 52impl Assist {
74 fn default() -> Self { 53 /// Return all the assists applicable at the given position.
75 Self::CurrentFile 54 ///
55 /// Assists are returned in the "unresolved" state, that is only labels are
56 /// returned, without actual edits.
57 pub fn unresolved(db: &RootDatabase, range: FileRange) -> Vec<Assist> {
58 let sema = Semantics::new(db);
59 let ctx = AssistContext::new(sema, range);
60 let mut acc = Assists::new_unresolved(&ctx);
61 handlers::all().iter().for_each(|handler| {
62 handler(&mut acc, &ctx);
63 });
64 acc.finish_unresolved()
76 } 65 }
77}
78 66
79/// Return all the assists applicable at the given position. 67 /// Return all the assists applicable at the given position.
80/// 68 ///
81/// Assists are returned in the "unresolved" state, that is only labels are 69 /// Assists are returned in the "resolved" state, that is with edit fully
82/// returned, without actual edits. 70 /// computed.
83pub fn unresolved_assists(db: &RootDatabase, range: FileRange) -> Vec<AssistLabel> { 71 pub fn resolved(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssist> {
84 let sema = Semantics::new(db); 72 let sema = Semantics::new(db);
85 let ctx = AssistCtx::new(&sema, range, false); 73 let ctx = AssistContext::new(sema, range);
86 handlers::all() 74 let mut acc = Assists::new_resolved(&ctx);
87 .iter() 75 handlers::all().iter().for_each(|handler| {
88 .filter_map(|f| f(ctx.clone())) 76 handler(&mut acc, &ctx);
89 .flat_map(|it| it.0) 77 });
90 .map(|a| a.label) 78 acc.finish_resolved()
91 .collect() 79 }
92}
93 80
94/// Return all the assists applicable at the given position. 81 pub(crate) fn new(
95/// 82 id: AssistId,
96/// Assists are returned in the "resolved" state, that is with edit fully 83 label: String,
97/// computed. 84 group: Option<GroupLabel>,
98pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssist> { 85 target: TextRange,
99 let sema = Semantics::new(db); 86 ) -> Assist {
100 let ctx = AssistCtx::new(&sema, range, true); 87 // FIXME: make fields private, so that this invariant can't be broken
101 let mut a = handlers::all() 88 assert!(label.starts_with(|c: char| c.is_uppercase()));
102 .iter() 89 Assist { id, label, group, target }
103 .filter_map(|f| f(ctx.clone())) 90 }
104 .flat_map(|it| it.0)
105 .map(|it| it.into_resolved().unwrap())
106 .collect::<Vec<_>>();
107 a.sort_by_key(|it| it.action.target.map_or(TextSize::from(!0u32), |it| it.len()));
108 a
109} 91}
110 92
111mod handlers { 93mod handlers {
112 use crate::{Assist, AssistCtx}; 94 use crate::{AssistContext, Assists};
113 95
114 pub(crate) type Handler = fn(AssistCtx) -> Option<Assist>; 96 pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>;
115 97
116 mod add_custom_impl; 98 mod add_custom_impl;
117 mod add_derive; 99 mod add_derive;
118 mod add_explicit_type; 100 mod add_explicit_type;
101 mod add_from_impl_for_enum;
119 mod add_function; 102 mod add_function;
120 mod add_impl; 103 mod add_impl;
121 mod add_missing_impl_members; 104 mod add_missing_impl_members;
122 mod add_new; 105 mod add_new;
123 mod apply_demorgan; 106 mod apply_demorgan;
124 mod auto_import; 107 mod auto_import;
108 mod change_return_type_to_result;
125 mod change_visibility; 109 mod change_visibility;
126 mod early_return; 110 mod early_return;
127 mod fill_match_arms; 111 mod fill_match_arms;
@@ -138,13 +122,12 @@ mod handlers {
138 mod raw_string; 122 mod raw_string;
139 mod remove_dbg; 123 mod remove_dbg;
140 mod remove_mut; 124 mod remove_mut;
125 mod reorder_fields;
141 mod replace_if_let_with_match; 126 mod replace_if_let_with_match;
142 mod replace_let_with_if_let; 127 mod replace_let_with_if_let;
143 mod replace_qualified_name_with_use; 128 mod replace_qualified_name_with_use;
144 mod replace_unwrap_with_match; 129 mod replace_unwrap_with_match;
145 mod split_import; 130 mod split_import;
146 mod add_from_impl_for_enum;
147 mod reorder_fields;
148 mod unwrap_block; 131 mod unwrap_block;
149 132
150 pub(crate) fn all() -> &'static [Handler] { 133 pub(crate) fn all() -> &'static [Handler] {
@@ -159,6 +142,7 @@ mod handlers {
159 add_new::add_new, 142 add_new::add_new,
160 apply_demorgan::apply_demorgan, 143 apply_demorgan::apply_demorgan,
161 auto_import::auto_import, 144 auto_import::auto_import,
145 change_return_type_to_result::change_return_type_to_result,
162 change_visibility::change_visibility, 146 change_visibility::change_visibility,
163 early_return::convert_to_guarded_return, 147 early_return::convert_to_guarded_return,
164 fill_match_arms::fill_match_arms, 148 fill_match_arms::fill_match_arms,
@@ -194,150 +178,3 @@ mod handlers {
194 ] 178 ]
195 } 179 }
196} 180}
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..a3eacb8f1
--- /dev/null
+++ b/crates/ra_assists/src/tests.rs
@@ -0,0 +1,167 @@
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, Assist, AssistContext, Assists};
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 = Assist::resolved(&db, frange)
45 .into_iter()
46 .find(|assist| assist.assist.id.0 == assist_id)
47 .unwrap_or_else(|| {
48 panic!(
49 "\n\nAssist is not applicable: {}\nAvailable assists: {}",
50 assist_id,
51 Assist::resolved(&db, frange)
52 .into_iter()
53 .map(|assist| assist.assist.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(handler: 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 ctx = AssistContext::new(sema, frange);
94 let mut acc = Assists::new_resolved(&ctx);
95 handler(&mut acc, &ctx);
96 let mut res = acc.finish_resolved();
97 let assist = res.pop();
98 match (assist, expected) {
99 (Some(assist), ExpectedResult::After(after)) => {
100 let mut source_change = assist.source_change;
101 let change = source_change.source_file_edits.pop().unwrap();
102
103 let mut actual = db.file_text(change.file_id).as_ref().to_owned();
104 change.edit.apply(&mut actual);
105
106 match source_change.cursor_position {
107 None => {
108 if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {
109 let off = change
110 .edit
111 .apply_to_offset(before_cursor_pos)
112 .expect("cursor position is affected by the edit");
113 actual = add_cursor(&actual, off)
114 }
115 }
116 Some(off) => actual = add_cursor(&actual, off.offset),
117 };
118
119 assert_eq_text!(after, &actual);
120 }
121 (Some(assist), ExpectedResult::Target(target)) => {
122 let range = assist.assist.target;
123 assert_eq_text!(&text_without_caret[range], target);
124 }
125 (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),
126 (None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => {
127 panic!("code action is not applicable")
128 }
129 (None, ExpectedResult::NotApplicable) => (),
130 };
131}
132
133#[test]
134fn assist_order_field_struct() {
135 let before = "struct Foo { <|>bar: u32 }";
136 let (before_cursor_pos, before) = extract_offset(before);
137 let (db, file_id) = with_single_file(&before);
138 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
139 let assists = Assist::resolved(&db, frange);
140 let mut assists = assists.iter();
141
142 assert_eq!(
143 assists.next().expect("expected assist").assist.label,
144 "Change visibility to pub(crate)"
145 );
146 assert_eq!(assists.next().expect("expected assist").assist.label, "Add `#[derive]`");
147}
148
149#[test]
150fn assist_order_if_expr() {
151 let before = "
152 pub fn test_some_range(a: int) -> bool {
153 if let 2..6 = <|>5<|> {
154 true
155 } else {
156 false
157 }
158 }";
159 let (range, before) = extract_range(before);
160 let (db, file_id) = with_single_file(&before);
161 let frange = FileRange { file_id, range };
162 let assists = Assist::resolved(&db, frange);
163 let mut assists = assists.iter();
164
165 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
166 assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match");
167}
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_assists/src/utils/insert_use.rs b/crates/ra_assists/src/utils/insert_use.rs
index c1f447efe..1214e3cd4 100644
--- a/crates/ra_assists/src/utils/insert_use.rs
+++ b/crates/ra_assists/src/utils/insert_use.rs
@@ -2,7 +2,6 @@
2// FIXME: rewrite according to the plan, outlined in 2// FIXME: rewrite according to the plan, outlined in
3// https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553 3// https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553
4 4
5use crate::assist_ctx::ActionBuilder;
6use hir::{self, ModPath}; 5use hir::{self, ModPath};
7use ra_syntax::{ 6use ra_syntax::{
8 ast::{self, NameOwner}, 7 ast::{self, NameOwner},
@@ -12,6 +11,8 @@ use ra_syntax::{
12}; 11};
13use ra_text_edit::TextEditBuilder; 12use ra_text_edit::TextEditBuilder;
14 13
14use crate::assist_context::{AssistBuilder, AssistContext};
15
15/// Creates and inserts a use statement for the given path to import. 16/// Creates and inserts a use statement for the given path to import.
16/// The use statement is inserted in the scope most appropriate to the 17/// The use statement is inserted in the scope most appropriate to the
17/// the cursor position given, additionally merged with the existing use imports. 18/// the cursor position given, additionally merged with the existing use imports.
@@ -19,10 +20,11 @@ pub(crate) fn insert_use_statement(
19 // Ideally the position of the cursor, used to 20 // Ideally the position of the cursor, used to
20 position: &SyntaxNode, 21 position: &SyntaxNode,
21 path_to_import: &ModPath, 22 path_to_import: &ModPath,
22 edit: &mut ActionBuilder, 23 ctx: &AssistContext,
24 builder: &mut AssistBuilder,
23) { 25) {
24 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>(); 26 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>();
25 let container = edit.ctx().sema.ancestors_with_macros(position.clone()).find_map(|n| { 27 let container = ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| {
26 if let Some(module) = ast::Module::cast(n.clone()) { 28 if let Some(module) = ast::Module::cast(n.clone()) {
27 return module.item_list().map(|it| it.syntax().clone()); 29 return module.item_list().map(|it| it.syntax().clone());
28 } 30 }
@@ -31,7 +33,7 @@ pub(crate) fn insert_use_statement(
31 33
32 if let Some(container) = container { 34 if let Some(container) = container {
33 let action = best_action_for_target(container, position.clone(), &target); 35 let action = best_action_for_target(container, position.clone(), &target);
34 make_assist(&action, &target, edit.text_edit_builder()); 36 make_assist(&action, &target, builder.text_edit_builder());
35 } 37 }
36} 38}
37 39
diff --git a/crates/ra_cfg/src/lib.rs b/crates/ra_cfg/src/lib.rs
index 51d953f6e..57feabcb2 100644
--- a/crates/ra_cfg/src/lib.rs
+++ b/crates/ra_cfg/src/lib.rs
@@ -2,8 +2,6 @@
2 2
3mod cfg_expr; 3mod cfg_expr;
4 4
5use std::iter::IntoIterator;
6
7use ra_syntax::SmolStr; 5use ra_syntax::SmolStr;
8use rustc_hash::FxHashSet; 6use rustc_hash::FxHashSet;
9 7
@@ -48,9 +46,4 @@ impl CfgOptions {
48 pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) { 46 pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) {
49 self.key_values.insert((key, value)); 47 self.key_values.insert((key, value));
50 } 48 }
51
52 /// Shortcut to set features
53 pub fn insert_features(&mut self, iter: impl IntoIterator<Item = SmolStr>) {
54 iter.into_iter().for_each(|feat| self.insert_key_value("feature".into(), feat));
55 }
56} 49}
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/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs
index db3907fe6..f16d42276 100644
--- a/crates/ra_ide/src/display/function_signature.rs
+++ b/crates/ra_ide/src/display/function_signature.rs
@@ -1,5 +1,7 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3// FIXME: this modules relies on strings and AST way too much, and it should be
4// rewritten (matklad 2020-05-07)
3use std::{ 5use std::{
4 convert::From, 6 convert::From,
5 fmt::{self, Display}, 7 fmt::{self, Display},
@@ -202,7 +204,11 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
202 204
203 res.extend(param_list.params().map(|param| param.syntax().text().to_string())); 205 res.extend(param_list.params().map(|param| param.syntax().text().to_string()));
204 res_types.extend(param_list.params().map(|param| { 206 res_types.extend(param_list.params().map(|param| {
205 param.syntax().text().to_string().split(':').nth(1).unwrap()[1..].to_string() 207 let param_text = param.syntax().text().to_string();
208 match param_text.split(':').nth(1) {
209 Some(it) => it[1..].to_string(),
210 None => param_text,
211 }
206 })); 212 }));
207 } 213 }
208 (has_self_param, res, res_types) 214 (has_self_param, res, res_types)
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index 54d318858..06d4f1c63 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -143,7 +143,7 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
143 ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), 143 ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path),
144 ModuleDef::BuiltinType(it) => Some(it.to_string()), 144 ModuleDef::BuiltinType(it) => Some(it.to_string()),
145 }, 145 },
146 Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display_truncated(db, None))), 146 Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display(db))),
147 Definition::TypeParam(_) | Definition::SelfType(_) => { 147 Definition::TypeParam(_) | Definition::SelfType(_) => {
148 // FIXME: Hover for generic param 148 // FIXME: Hover for generic param
149 None 149 None
@@ -208,7 +208,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
208 } 208 }
209 }?; 209 }?;
210 210
211 res.extend(Some(rust_code_markup(&ty.display_truncated(db, None)))); 211 res.extend(Some(rust_code_markup(&ty.display(db))));
212 let range = sema.original_range(&node).range; 212 let range = sema.original_range(&node).range;
213 Some(RangeInfo::new(range, res)) 213 Some(RangeInfo::new(range, res))
214} 214}
@@ -280,6 +280,47 @@ mod tests {
280 } 280 }
281 281
282 #[test] 282 #[test]
283 fn hover_shows_long_type_of_an_expression() {
284 check_hover_result(
285 r#"
286 //- /main.rs
287 struct Scan<A, B, C> {
288 a: A,
289 b: B,
290 c: C,
291 }
292
293 struct FakeIter<I> {
294 inner: I,
295 }
296
297 struct OtherStruct<T> {
298 i: T,
299 }
300
301 enum FakeOption<T> {
302 Some(T),
303 None,
304 }
305
306 fn scan<A, B, C>(a: A, b: B, c: C) -> FakeIter<Scan<OtherStruct<A>, B, C>> {
307 FakeIter { inner: Scan { a, b, c } }
308 }
309
310 fn main() {
311 let num: i32 = 55;
312 let closure = |memo: &mut u32, value: &u32, _another: &mut u32| -> FakeOption<u32> {
313 FakeOption::Some(*memo + value)
314 };
315 let number = 5u32;
316 let mut iter<|> = scan(OtherStruct { i: num }, closure, number);
317 }
318 "#,
319 &["FakeIter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> FakeOption<u32>, u32>>"],
320 );
321 }
322
323 #[test]
283 fn hover_shows_fn_signature() { 324 fn hover_shows_fn_signature() {
284 // Single file with result 325 // Single file with result
285 check_hover_result( 326 check_hover_result(
@@ -405,7 +446,7 @@ mod tests {
405 } 446 }
406 447
407 #[test] 448 #[test]
408 fn hover_omits_default_generic_types() { 449 fn hover_default_generic_types() {
409 check_hover_result( 450 check_hover_result(
410 r#" 451 r#"
411//- /main.rs 452//- /main.rs
@@ -417,7 +458,7 @@ struct Test<K, T = u8> {
417fn main() { 458fn main() {
418 let zz<|> = Test { t: 23, k: 33 }; 459 let zz<|> = Test { t: 23, k: 33 };
419}"#, 460}"#,
420 &["Test<i32>"], 461 &["Test<i32, u8>"],
421 ); 462 );
422 } 463 }
423 464
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 09f602fe1..915199bd8 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -16,7 +16,6 @@ macro_rules! eprintln {
16} 16}
17 17
18pub mod mock_analysis; 18pub mod mock_analysis;
19mod source_change;
20 19
21mod prime_caches; 20mod prime_caches;
22mod status; 21mod status;
@@ -32,7 +31,6 @@ mod syntax_highlighting;
32mod parent_module; 31mod parent_module;
33mod references; 32mod references;
34mod impls; 33mod impls;
35mod assists;
36mod diagnostics; 34mod diagnostics;
37mod syntax_tree; 35mod syntax_tree;
38mod folding_ranges; 36mod folding_ranges;
@@ -65,7 +63,6 @@ use ra_syntax::{SourceFile, TextRange, TextSize};
65use crate::display::ToNav; 63use crate::display::ToNav;
66 64
67pub use crate::{ 65pub use crate::{
68 assists::{Assist, AssistId},
69 call_hierarchy::CallItem, 66 call_hierarchy::CallItem,
70 completion::{ 67 completion::{
71 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat, 68 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat,
@@ -78,7 +75,6 @@ pub use crate::{
78 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, 75 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
79 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, 76 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
80 runnables::{Runnable, RunnableKind, TestId}, 77 runnables::{Runnable, RunnableKind, TestId},
81 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
82 ssr::SsrError, 78 ssr::SsrError,
83 syntax_highlighting::{ 79 syntax_highlighting::{
84 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange, 80 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange,
@@ -86,6 +82,7 @@ pub use crate::{
86}; 82};
87 83
88pub use hir::Documentation; 84pub use hir::Documentation;
85pub use ra_assists::AssistId;
89pub use ra_db::{ 86pub use ra_db::{
90 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, 87 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId,
91}; 88};
@@ -94,6 +91,7 @@ pub use ra_ide_db::{
94 line_index::{LineCol, LineIndex}, 91 line_index::{LineCol, LineIndex},
95 line_index_utils::translate_offset_with_edit, 92 line_index_utils::translate_offset_with_edit,
96 search::SearchScope, 93 search::SearchScope,
94 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
97 symbol_index::Query, 95 symbol_index::Query,
98 RootDatabase, 96 RootDatabase,
99}; 97};
@@ -135,10 +133,12 @@ pub struct AnalysisHost {
135 db: RootDatabase, 133 db: RootDatabase,
136} 134}
137 135
138impl Default for AnalysisHost { 136#[derive(Debug)]
139 fn default() -> AnalysisHost { 137pub struct Assist {
140 AnalysisHost::new(None) 138 pub id: AssistId,
141 } 139 pub label: String,
140 pub group_label: Option<String>,
141 pub source_change: SourceChange,
142} 142}
143 143
144impl AnalysisHost { 144impl AnalysisHost {
@@ -188,6 +188,12 @@ impl AnalysisHost {
188 } 188 }
189} 189}
190 190
191impl Default for AnalysisHost {
192 fn default() -> AnalysisHost {
193 AnalysisHost::new(None)
194 }
195}
196
191/// Analysis is a snapshot of a world state at a moment in time. It is the main 197/// Analysis is a snapshot of a world state at a moment in time. It is the main
192/// entry point for asking semantic information about the world. When the world 198/// entry point for asking semantic information about the world. When the world
193/// state is advanced using `AnalysisHost::apply_change` method, all existing 199/// state is advanced using `AnalysisHost::apply_change` method, all existing
@@ -296,7 +302,7 @@ impl Analysis {
296 file_id: frange.file_id, 302 file_id: frange.file_id,
297 edit: join_lines::join_lines(&parse.tree(), frange.range), 303 edit: join_lines::join_lines(&parse.tree(), frange.range),
298 }; 304 };
299 SourceChange::source_file_edit("join lines", file_edit) 305 SourceChange::source_file_edit("Join lines", file_edit)
300 }) 306 })
301 } 307 }
302 308
@@ -465,7 +471,17 @@ impl Analysis {
465 /// Computes assists (aka code actions aka intentions) for the given 471 /// Computes assists (aka code actions aka intentions) for the given
466 /// position. 472 /// position.
467 pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> { 473 pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> {
468 self.with_db(|db| assists::assists(db, frange)) 474 self.with_db(|db| {
475 ra_assists::Assist::resolved(db, frange)
476 .into_iter()
477 .map(|assist| Assist {
478 id: assist.assist.id,
479 label: assist.assist.label,
480 group_label: assist.assist.group.map(|it| it.0),
481 source_change: assist.source_change,
482 })
483 .collect()
484 })
469 } 485 }
470 486
471 /// Computes the set of diagnostics for the given file. 487 /// Computes the set of diagnostics for the given file.
@@ -490,7 +506,7 @@ impl Analysis {
490 ) -> Cancelable<Result<SourceChange, SsrError>> { 506 ) -> Cancelable<Result<SourceChange, SsrError>> {
491 self.with_db(|db| { 507 self.with_db(|db| {
492 let edits = ssr::parse_search_replace(query, parse_only, db)?; 508 let edits = ssr::parse_search_replace(query, parse_only, db)?;
493 Ok(SourceChange::source_file_edits("ssr", edits)) 509 Ok(SourceChange::source_file_edits("Structural Search Replace", edits))
494 }) 510 })
495 } 511 }
496 512
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index 0398d53bc..2cbb82c1a 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -712,6 +712,68 @@ mod tests {
712 "###); 712 "###);
713 } 713 }
714 714
715 #[test]
716 fn test_enum_variant_from_module_1() {
717 test_rename(
718 r#"
719 mod foo {
720 pub enum Foo {
721 Bar<|>,
722 }
723 }
724
725 fn func(f: foo::Foo) {
726 match f {
727 foo::Foo::Bar => {}
728 }
729 }
730 "#,
731 "Baz",
732 r#"
733 mod foo {
734 pub enum Foo {
735 Baz,
736 }
737 }
738
739 fn func(f: foo::Foo) {
740 match f {
741 foo::Foo::Baz => {}
742 }
743 }
744 "#,
745 );
746 }
747
748 #[test]
749 fn test_enum_variant_from_module_2() {
750 test_rename(
751 r#"
752 mod foo {
753 pub struct Foo {
754 pub bar<|>: uint,
755 }
756 }
757
758 fn foo(f: foo::Foo) {
759 let _ = f.bar;
760 }
761 "#,
762 "baz",
763 r#"
764 mod foo {
765 pub struct Foo {
766 pub baz: uint,
767 }
768 }
769
770 fn foo(f: foo::Foo) {
771 let _ = f.baz;
772 }
773 "#,
774 );
775 }
776
715 fn test_rename(text: &str, new_name: &str, expected: &str) { 777 fn test_rename(text: &str, new_name: &str, expected: &str) {
716 let (analysis, position) = single_file_with_position(text); 778 let (analysis, position) = single_file_with_position(text);
717 let source_change = analysis.rename(position, new_name).unwrap(); 779 let source_change = analysis.rename(position, new_name).unwrap();
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/defs.rs b/crates/ra_ide_db/src/defs.rs
index 40d0e77b5..f990e3bb9 100644
--- a/crates/ra_ide_db/src/defs.rs
+++ b/crates/ra_ide_db/src/defs.rs
@@ -6,7 +6,7 @@
6// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06). 6// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).
7 7
8use hir::{ 8use hir::{
9 Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, Name, PathResolution, 9 Adt, Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, Name, PathResolution,
10 Semantics, TypeParam, Visibility, 10 Semantics, TypeParam, Visibility,
11}; 11};
12use ra_prof::profile; 12use ra_prof::profile;
@@ -47,7 +47,13 @@ impl Definition {
47 match self { 47 match self {
48 Definition::Macro(_) => None, 48 Definition::Macro(_) => None,
49 Definition::Field(sf) => Some(sf.visibility(db)), 49 Definition::Field(sf) => Some(sf.visibility(db)),
50 Definition::ModuleDef(def) => module?.visibility_of(db, def), 50 Definition::ModuleDef(def) => match def {
51 ModuleDef::EnumVariant(id) => {
52 let parent = id.parent_enum(db);
53 module?.visibility_of(db, &ModuleDef::Adt(Adt::Enum(parent)))
54 }
55 _ => module?.visibility_of(db, def),
56 },
51 Definition::SelfType(_) => None, 57 Definition::SelfType(_) => None,
52 Definition::Local(_) => None, 58 Definition::Local(_) => None,
53 Definition::TypeParam(_) => None, 59 Definition::TypeParam(_) => None,
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_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index 4027f020f..eb9f33ee8 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -87,6 +87,7 @@ pub struct PackageData {
87 pub dependencies: Vec<PackageDependency>, 87 pub dependencies: Vec<PackageDependency>,
88 pub edition: Edition, 88 pub edition: Edition,
89 pub features: Vec<String>, 89 pub features: Vec<String>,
90 pub cfgs: Vec<String>,
90 pub out_dir: Option<PathBuf>, 91 pub out_dir: Option<PathBuf>,
91 pub proc_macro_dylib_path: Option<PathBuf>, 92 pub proc_macro_dylib_path: Option<PathBuf>,
92} 93}
@@ -168,10 +169,12 @@ impl CargoWorkspace {
168 })?; 169 })?;
169 170
170 let mut out_dir_by_id = FxHashMap::default(); 171 let mut out_dir_by_id = FxHashMap::default();
172 let mut cfgs = FxHashMap::default();
171 let mut proc_macro_dylib_paths = FxHashMap::default(); 173 let mut proc_macro_dylib_paths = FxHashMap::default();
172 if cargo_features.load_out_dirs_from_check { 174 if cargo_features.load_out_dirs_from_check {
173 let resources = load_extern_resources(cargo_toml, cargo_features)?; 175 let resources = load_extern_resources(cargo_toml, cargo_features)?;
174 out_dir_by_id = resources.out_dirs; 176 out_dir_by_id = resources.out_dirs;
177 cfgs = resources.cfgs;
175 proc_macro_dylib_paths = resources.proc_dylib_paths; 178 proc_macro_dylib_paths = resources.proc_dylib_paths;
176 } 179 }
177 180
@@ -197,6 +200,7 @@ impl CargoWorkspace {
197 edition, 200 edition,
198 dependencies: Vec::new(), 201 dependencies: Vec::new(),
199 features: Vec::new(), 202 features: Vec::new(),
203 cfgs: cfgs.get(&id).cloned().unwrap_or_default(),
200 out_dir: out_dir_by_id.get(&id).cloned(), 204 out_dir: out_dir_by_id.get(&id).cloned(),
201 proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(), 205 proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(),
202 }); 206 });
@@ -278,6 +282,7 @@ impl CargoWorkspace {
278pub struct ExternResources { 282pub struct ExternResources {
279 out_dirs: FxHashMap<PackageId, PathBuf>, 283 out_dirs: FxHashMap<PackageId, PathBuf>,
280 proc_dylib_paths: FxHashMap<PackageId, PathBuf>, 284 proc_dylib_paths: FxHashMap<PackageId, PathBuf>,
285 cfgs: FxHashMap<PackageId, Vec<String>>,
281} 286}
282 287
283pub fn load_extern_resources( 288pub fn load_extern_resources(
@@ -303,8 +308,14 @@ pub fn load_extern_resources(
303 for message in cargo_metadata::parse_messages(output.stdout.as_slice()) { 308 for message in cargo_metadata::parse_messages(output.stdout.as_slice()) {
304 if let Ok(message) = message { 309 if let Ok(message) = message {
305 match message { 310 match message {
306 Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => { 311 Message::BuildScriptExecuted(BuildScript { package_id, out_dir, cfgs, .. }) => {
307 res.out_dirs.insert(package_id, out_dir); 312 res.out_dirs.insert(package_id.clone(), out_dir);
313 res.cfgs.insert(
314 package_id,
315 // FIXME: Current `cargo_metadata` uses `PathBuf` instead of `String`,
316 // change when https://github.com/oli-obk/cargo_metadata/pulls/112 reaches crates.io
317 cfgs.iter().filter_map(|c| c.to_str().map(|s| s.to_owned())).collect(),
318 );
308 } 319 }
309 320
310 Message::CompilerArtifact(message) => { 321 Message::CompilerArtifact(message) => {
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index e4b86f1e2..88a6ffb2a 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -399,7 +399,18 @@ impl ProjectWorkspace {
399 let edition = cargo[pkg].edition; 399 let edition = cargo[pkg].edition;
400 let cfg_options = { 400 let cfg_options = {
401 let mut opts = default_cfg_options.clone(); 401 let mut opts = default_cfg_options.clone();
402 opts.insert_features(cargo[pkg].features.iter().map(Into::into)); 402 for feature in cargo[pkg].features.iter() {
403 opts.insert_key_value("feature".into(), feature.into());
404 }
405 for cfg in cargo[pkg].cfgs.iter() {
406 match cfg.find('=') {
407 Some(split) => opts.insert_key_value(
408 cfg[..split].into(),
409 cfg[split + 1..].trim_matches('"').into(),
410 ),
411 None => opts.insert_atom(cfg.into()),
412 };
413 }
403 opts 414 opts
404 }; 415 };
405 let mut env = Env::default(); 416 let mut env = Env::default();
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/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index 15e8305f8..f4353af64 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -42,6 +42,7 @@ use crate::{
42 world::WorldSnapshot, 42 world::WorldSnapshot,
43 LspError, Result, 43 LspError, Result,
44}; 44};
45use ra_project_model::TargetKind;
45 46
46pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> { 47pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> {
47 let _p = profile("handle_analyzer_status"); 48 let _p = profile("handle_analyzer_status");
@@ -384,16 +385,27 @@ pub fn handle_runnables(
384 let offset = params.position.map(|it| it.conv_with(&line_index)); 385 let offset = params.position.map(|it| it.conv_with(&line_index));
385 let mut res = Vec::new(); 386 let mut res = Vec::new();
386 let workspace_root = world.workspace_root_for(file_id); 387 let workspace_root = world.workspace_root_for(file_id);
388 let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?;
387 for runnable in world.analysis().runnables(file_id)? { 389 for runnable in world.analysis().runnables(file_id)? {
388 if let Some(offset) = offset { 390 if let Some(offset) = offset {
389 if !runnable.range.contains_inclusive(offset) { 391 if !runnable.range.contains_inclusive(offset) {
390 continue; 392 continue;
391 } 393 }
392 } 394 }
395 // Do not suggest binary run on other target than binary
396 if let RunnableKind::Bin = runnable.kind {
397 if let Some(spec) = &cargo_spec {
398 match spec.target_kind {
399 TargetKind::Bin => {}
400 _ => continue,
401 }
402 }
403 }
393 res.push(to_lsp_runnable(&world, file_id, runnable)?); 404 res.push(to_lsp_runnable(&world, file_id, runnable)?);
394 } 405 }
406
395 // Add `cargo check` and `cargo test` for the whole package 407 // Add `cargo check` and `cargo test` for the whole package
396 match CargoTargetSpec::for_file(&world, file_id)? { 408 match cargo_spec {
397 Some(spec) => { 409 Some(spec) => {
398 for &cmd in ["check", "test"].iter() { 410 for &cmd in ["check", "test"].iter() {
399 res.push(req::Runnable { 411 res.push(req::Runnable {
@@ -831,13 +843,23 @@ pub fn handle_code_lens(
831 843
832 let mut lenses: Vec<CodeLens> = Default::default(); 844 let mut lenses: Vec<CodeLens> = Default::default();
833 845
846 let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?;
834 // Gather runnables 847 // Gather runnables
835 for runnable in world.analysis().runnables(file_id)? { 848 for runnable in world.analysis().runnables(file_id)? {
836 let title = match &runnable.kind { 849 let title = match &runnable.kind {
837 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶️\u{fe0e}Run Test", 850 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶️\u{fe0e}Run Test",
838 RunnableKind::DocTest { .. } => "▶️\u{fe0e}Run Doctest", 851 RunnableKind::DocTest { .. } => "▶️\u{fe0e}Run Doctest",
839 RunnableKind::Bench { .. } => "Run Bench", 852 RunnableKind::Bench { .. } => "Run Bench",
840 RunnableKind::Bin => "Run", 853 RunnableKind::Bin => {
854 // Do not suggest binary run on other target than binary
855 match &cargo_spec {
856 Some(spec) => match spec.target_kind {
857 TargetKind::Bin => "Run",
858 _ => continue,
859 },
860 None => continue,
861 }
862 }
841 } 863 }
842 .to_string(); 864 .to_string();
843 let mut r = to_lsp_runnable(&world, file_id, runnable)?; 865 let mut r = to_lsp_runnable(&world, file_id, runnable)?;
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index 1efa5dd63..e459e3a3c 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -9,7 +9,8 @@ use lsp_types::{
9}; 9};
10use rust_analyzer::req::{ 10use rust_analyzer::req::{
11 CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument, 11 CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument,
12 Formatting, GotoDefinition, HoverRequest, OnEnter, Runnables, RunnablesParams, 12 Formatting, GotoDefinition, GotoTypeDefinition, HoverRequest, OnEnter, Runnables,
13 RunnablesParams,
13}; 14};
14use serde_json::json; 15use serde_json::json;
15use tempfile::TempDir; 16use tempfile::TempDir;
@@ -574,7 +575,7 @@ version = \"0.0.0\"
574} 575}
575 576
576#[test] 577#[test]
577fn resolve_include_concat_env() { 578fn out_dirs_check() {
578 if skip_slow_tests() { 579 if skip_slow_tests() {
579 return; 580 return;
580 } 581 }
@@ -597,11 +598,28 @@ fn main() {
597 r#"pub fn message() -> &'static str { "Hello, World!" }"#, 598 r#"pub fn message() -> &'static str { "Hello, World!" }"#,
598 ) 599 )
599 .unwrap(); 600 .unwrap();
601 println!("cargo:rustc-cfg=atom_cfg");
602 println!("cargo:rustc-cfg=featlike=\"set\"");
600 println!("cargo:rerun-if-changed=build.rs"); 603 println!("cargo:rerun-if-changed=build.rs");
601} 604}
602//- src/main.rs 605//- src/main.rs
603include!(concat!(env!("OUT_DIR"), "/hello.rs")); 606include!(concat!(env!("OUT_DIR"), "/hello.rs"));
604 607
608#[cfg(atom_cfg)]
609struct A;
610#[cfg(bad_atom_cfg)]
611struct A;
612#[cfg(featlike = "set")]
613struct B;
614#[cfg(featlike = "not_set")]
615struct B;
616
617fn main() {
618 let va = A;
619 let vb = B;
620 message();
621}
622
605fn main() { message(); } 623fn main() { message(); }
606"###, 624"###,
607 ) 625 )
@@ -613,12 +631,98 @@ fn main() { message(); }
613 let res = server.send_request::<GotoDefinition>(GotoDefinitionParams { 631 let res = server.send_request::<GotoDefinition>(GotoDefinitionParams {
614 text_document_position_params: TextDocumentPositionParams::new( 632 text_document_position_params: TextDocumentPositionParams::new(
615 server.doc_id("src/main.rs"), 633 server.doc_id("src/main.rs"),
616 Position::new(2, 15), 634 Position::new(14, 8),
617 ), 635 ),
618 work_done_progress_params: Default::default(), 636 work_done_progress_params: Default::default(),
619 partial_result_params: Default::default(), 637 partial_result_params: Default::default(),
620 }); 638 });
621 assert!(format!("{}", res).contains("hello.rs")); 639 assert!(format!("{}", res).contains("hello.rs"));
640 server.request::<GotoTypeDefinition>(
641 GotoDefinitionParams {
642 text_document_position_params: TextDocumentPositionParams::new(
643 server.doc_id("src/main.rs"),
644 Position::new(12, 9),
645 ),
646 work_done_progress_params: Default::default(),
647 partial_result_params: Default::default(),
648 },
649 json!([{
650 "originSelectionRange": {
651 "end": {
652 "character": 10,
653 "line": 12
654 },
655 "start": {
656 "character": 8,
657 "line": 12
658 }
659 },
660 "targetRange": {
661 "end": {
662 "character": 9,
663 "line": 3
664 },
665 "start": {
666 "character": 0,
667 "line": 2
668 }
669 },
670 "targetSelectionRange": {
671 "end": {
672 "character": 8,
673 "line": 3
674 },
675 "start": {
676 "character": 7,
677 "line": 3
678 }
679 },
680 "targetUri": "file:///[..]src/main.rs"
681 }]),
682 );
683 server.request::<GotoTypeDefinition>(
684 GotoDefinitionParams {
685 text_document_position_params: TextDocumentPositionParams::new(
686 server.doc_id("src/main.rs"),
687 Position::new(13, 9),
688 ),
689 work_done_progress_params: Default::default(),
690 partial_result_params: Default::default(),
691 },
692 json!([{
693 "originSelectionRange": {
694 "end": {
695 "character": 10,
696 "line": 13
697 },
698 "start": {
699 "character": 8,
700 "line":13
701 }
702 },
703 "targetRange": {
704 "end": {
705 "character": 9,
706 "line": 7
707 },
708 "start": {
709 "character": 0,
710 "line":6
711 }
712 },
713 "targetSelectionRange": {
714 "end": {
715 "character": 8,
716 "line": 7
717 },
718 "start": {
719 "character": 7,
720 "line": 7
721 }
722 },
723 "targetUri": "file:///[..]src/main.rs"
724 }]),
725 );
622} 726}
623 727
624#[test] 728#[test]
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("//-")) {