diff options
Diffstat (limited to 'crates')
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 | |||
3 | use algo::find_covering_element; | ||
4 | use hir::Semantics; | ||
5 | use ra_db::{FileId, FileRange}; | ||
6 | use ra_fmt::{leading_indent, reindent}; | ||
7 | use ra_ide_db::{ | ||
8 | source_change::{SingleFileChange, SourceChange}, | ||
9 | RootDatabase, | ||
10 | }; | ||
11 | use ra_syntax::{ | ||
12 | algo::{self, find_node_at_offset, SyntaxRewriter}, | ||
13 | AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize, | ||
14 | TokenAtOffset, | ||
15 | }; | ||
16 | use ra_text_edit::TextEditBuilder; | ||
17 | |||
18 | use 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 :-) | ||
50 | pub(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 | |||
57 | impl<'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 | |||
90 | pub(crate) struct Assists { | ||
91 | resolve: bool, | ||
92 | file: FileId, | ||
93 | buf: Vec<(Assist, Option<SourceChange>)>, | ||
94 | } | ||
95 | |||
96 | impl 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 | |||
164 | pub(crate) struct AssistBuilder { | ||
165 | edit: TextEditBuilder, | ||
166 | cursor_position: Option<TextSize>, | ||
167 | file: FileId, | ||
168 | } | ||
169 | |||
170 | impl 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. | ||
2 | use hir::Semantics; | ||
3 | use ra_db::FileRange; | ||
4 | use ra_fmt::{leading_indent, reindent}; | ||
5 | use ra_ide_db::RootDatabase; | ||
6 | use 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 | }; | ||
11 | use ra_text_edit::TextEditBuilder; | ||
12 | |||
13 | use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist}; | ||
14 | |||
15 | #[derive(Clone, Debug)] | ||
16 | pub(crate) struct Assist(pub(crate) Vec<AssistInfo>); | ||
17 | |||
18 | #[derive(Clone, Debug)] | ||
19 | pub(crate) struct AssistInfo { | ||
20 | pub(crate) label: AssistLabel, | ||
21 | pub(crate) group_label: Option<GroupLabel>, | ||
22 | pub(crate) action: Option<AssistAction>, | ||
23 | } | ||
24 | |||
25 | impl 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)] | ||
75 | pub(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 | |||
83 | impl<'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 | |||
144 | pub(crate) struct AssistGroup<'a> { | ||
145 | ctx: AssistCtx<'a>, | ||
146 | group: GroupLabel, | ||
147 | assists: Vec<AssistInfo>, | ||
148 | } | ||
149 | |||
150 | impl<'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 | |||
181 | pub(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 | |||
189 | impl<'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 | |||
6 | mod generated; | ||
7 | |||
8 | use ra_db::FileRange; | ||
9 | use test_utils::{assert_eq_text, extract_range_or_offset}; | ||
10 | |||
11 | use crate::resolved_assists; | ||
12 | |||
13 | fn 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 | }; |
7 | use stdx::SepBy; | 7 | use stdx::SepBy; |
8 | 8 | ||
9 | use crate::{Assist, AssistCtx, AssistId}; | 9 | use 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 | // ``` |
28 | pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> { | 31 | pub(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)] |
97 | mod tests { | 99 | mod 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 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 7 | use 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 | // ``` |
27 | pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> { | 27 | pub(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)] |
59 | mod tests { | 59 | mod 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 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 7 | use 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 | // ``` |
24 | pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> { | 24 | pub(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> { | |||
77 | mod tests { | 77 | mod 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 | }; |
6 | use stdx::format_to; | 6 | use stdx::format_to; |
7 | |||
8 | use crate::{utils::FamousDefs, Assist, AssistCtx, AssistId}; | ||
9 | use test_utils::tested_by; | 7 | use test_utils::tested_by; |
10 | 8 | ||
9 | use 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 | // ``` |
28 | pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> { | 28 | pub(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( | |||
97 | mod tests { | 99 | mod 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 @@ | |||
1 | use hir::HirDisplay; | ||
2 | use ra_db::FileId; | ||
1 | use ra_syntax::{ | 3 | use 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 | |||
6 | use crate::{Assist, AssistCtx, AssistFile, AssistId}; | ||
7 | use ast::{edit::IndentLevel, ArgListOwner, ModuleItemOwner}; | ||
8 | use hir::HirDisplay; | ||
9 | use rustc_hash::{FxHashMap, FxHashSet}; | 7 | use rustc_hash::{FxHashMap, FxHashSet}; |
10 | 8 | ||
9 | use 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 | // ``` |
36 | pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> { | 36 | pub(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 | ||
78 | struct FunctionBuilder { | 76 | struct 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 |
159 | fn fn_args( | 153 | fn 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 | ||
227 | fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option<String> { | 221 | fn 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 | |||
259 | fn next_space_for_fn_in_module( | 253 | fn 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)] |
285 | mod tests { | 278 | mod 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 | }; |
5 | use stdx::{format_to, SepBy}; | 5 | use stdx::{format_to, SepBy}; |
6 | 6 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 7 | use 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 | // ``` |
28 | pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> { | 28 | pub(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)] |
61 | mod tests { | 61 | mod 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 | ||
11 | use crate::{ | 11 | use 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 | // ``` |
53 | pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option<Assist> { | 54 | pub(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 | // ``` |
94 | pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option<Assist> { | 96 | pub(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 | ||
103 | fn add_missing_impl_members_inner( | 106 | fn 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 | ||
172 | fn add_body(fn_def: ast::FnDef) -> ast::FnDef { | 175 | fn 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)] |
183 | mod tests { | 185 | mod 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 | }; |
8 | use stdx::{format_to, SepBy}; | 8 | use stdx::{format_to, SepBy}; |
9 | 9 | ||
10 | use crate::{Assist, AssistCtx, AssistId}; | 10 | use 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 | // ``` |
32 | pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> { | 32 | pub(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) |
127 | fn find_struct_impl(ctx: &AssistCtx, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> { | 126 | fn 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)] |
180 | mod tests { | 179 | mod 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 @@ | |||
1 | use ra_syntax::ast::{self, AstNode}; | 1 | use ra_syntax::ast::{self, AstNode}; |
2 | 2 | ||
3 | use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; | 3 | use 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 | // ``` |
24 | pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option<Assist> { | 24 | pub(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> { | |||
60 | mod tests { | 59 | mod 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 @@ | |||
1 | use std::collections::BTreeSet; | 1 | use std::collections::BTreeSet; |
2 | 2 | ||
3 | use either::Either; | ||
3 | use hir::{ | 4 | use 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 | }; |
13 | use rustc_hash::FxHashSet; | 14 | use rustc_hash::FxHashSet; |
14 | 15 | ||
15 | use crate::{ | 16 | use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, GroupLabel}; |
16 | assist_ctx::{Assist, AssistCtx}, | ||
17 | utils::insert_use_statement, | ||
18 | AssistId, | ||
19 | }; | ||
20 | use 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 | // ``` |
41 | pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { | 37 | pub(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 | ||
66 | impl AutoImportAssets { | 67 | impl 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)] |
278 | mod tests { | 280 | mod 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 @@ | |||
1 | use ra_syntax::{ | ||
2 | ast::{self, BlockExpr, Expr, LoopBodyOwner}, | ||
3 | AstNode, | ||
4 | SyntaxKind::{COMMENT, WHITESPACE}, | ||
5 | SyntaxNode, TextSize, | ||
6 | }; | ||
7 | |||
8 | use 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 | // ``` | ||
21 | pub(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 | |||
57 | struct TailReturnCollector { | ||
58 | exprs_to_wrap: Vec<SyntaxNode>, | ||
59 | } | ||
60 | |||
61 | impl 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)] | ||
177 | enum NodeType { | ||
178 | Leaf(SyntaxNode), | ||
179 | Node(SyntaxNode), | ||
180 | } | ||
181 | |||
182 | /// Get a tail expression inside a block | ||
183 | fn 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 | |||
253 | fn 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)] | ||
262 | mod 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 | |||
11 | use crate::{Assist, AssistCtx, AssistId}; | ||
12 | use test_utils::tested_by; | 10 | use test_utils::tested_by; |
13 | 11 | ||
12 | use 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 | // ``` |
25 | pub(crate) fn change_visibility(ctx: AssistCtx) -> Option<Assist> { | 25 | pub(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 | ||
32 | fn add_vis(ctx: AssistCtx) -> Option<Assist> { | 32 | fn 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 | ||
87 | fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> { | 86 | fn 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> { | |||
110 | mod tests { | 115 | mod 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 | ||
11 | use crate::{ | 11 | use 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 | // ``` |
39 | pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> { | 39 | pub(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)] |
184 | mod tests { | 184 | mod 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; | |||
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; | 6 | use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; |
7 | 7 | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use 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 | // ``` |
34 | pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { | 34 | pub(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)] |
170 | mod tests { | 169 | mod 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 @@ | |||
1 | use ra_syntax::ast::{AstNode, BinExpr, BinOp}; | 1 | use ra_syntax::ast::{AstNode, BinExpr, BinOp}; |
2 | 2 | ||
3 | use crate::{Assist, AssistCtx, AssistId}; | 3 | use 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 | // ``` |
20 | pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option<Assist> { | 20 | pub(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 { | |||
69 | mod tests { | 68 | mod 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 @@ | |||
1 | use ra_syntax::{algo::non_trivia_sibling, Direction, T}; | 1 | use ra_syntax::{algo::non_trivia_sibling, Direction, T}; |
2 | 2 | ||
3 | use crate::{Assist, AssistCtx, AssistId}; | 3 | use 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 | // ``` |
20 | pub(crate) fn flip_comma(ctx: AssistCtx) -> Option<Assist> { | 20 | pub(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> { | |||
39 | mod tests { | 38 | mod 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 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 7 | use 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 | // ``` |
20 | pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> { | 20 | pub(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> { | |||
43 | mod tests { | 43 | mod 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 | }; |
6 | use test_utils::tested_by; | 6 | use test_utils::tested_by; |
7 | 7 | ||
8 | use crate::{assist_ctx::ActionBuilder, Assist, AssistCtx, AssistId}; | 8 | use 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 | // ``` |
26 | pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> { | 29 | pub(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)] |
125 | mod tests { | 124 | mod 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::{ | |||
9 | use stdx::format_to; | 9 | use stdx::format_to; |
10 | use test_utils::tested_by; | 10 | use test_utils::tested_by; |
11 | 11 | ||
12 | use crate::{Assist, AssistCtx, AssistId}; | 12 | use 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 | // ``` |
30 | pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> { | 30 | pub(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)> { | |||
136 | mod tests { | 136 | mod 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 | ||
6 | use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; | 6 | use 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 | ||
27 | pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> { | 31 | pub(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)] |
62 | mod tests { | 65 | mod 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 | ||
9 | use crate::{Assist, AssistCtx, AssistId}; | 9 | use 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 | // ``` |
23 | pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> { | 26 | pub(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)] |
127 | mod tests { | 131 | mod 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 | ||
9 | use crate::{Assist, AssistCtx, AssistId, TextRange}; | 9 | use 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 | // ``` |
35 | pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> { | 35 | pub(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)] |
107 | mod tests { | 106 | mod 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 | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use 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 | // ``` |
25 | pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> { | 25 | pub(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> { | |||
89 | mod tests { | 89 | mod 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 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 7 | use 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 | // ``` |
34 | pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> { | 34 | pub(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 | // ``` |
91 | pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> { | 91 | pub(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> { | |||
132 | mod tests { | 133 | mod 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 | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use 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 | // ``` |
25 | pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> { | 25 | pub(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 | // ``` |
54 | pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> { | 54 | pub(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 | // ``` |
80 | pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> { | 80 | pub(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 | // ``` |
104 | pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> { | 104 | pub(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)] |
139 | mod test { | 139 | mod 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 | ||
6 | use crate::{Assist, AssistCtx, AssistId}; | 6 | use 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 | // ``` |
23 | pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> { | 23 | pub(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(¯o_call, "dbg")? { | 26 | if !is_valid_macrocall(¯o_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)] |
91 | mod tests { | 91 | mod 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 @@ | |||
1 | use ra_syntax::{SyntaxKind, TextRange, T}; | 1 | use ra_syntax::{SyntaxKind, TextRange, T}; |
2 | 2 | ||
3 | use crate::{Assist, AssistCtx, AssistId}; | 3 | use 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 | // ``` |
20 | pub(crate) fn remove_mut(ctx: AssistCtx) -> Option<Assist> { | 20 | pub(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; | |||
3 | use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct}; | 3 | use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct}; |
4 | use itertools::Itertools; | 4 | use itertools::Itertools; |
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode}; |
7 | algo, | 7 | |
8 | ast::{self, Path, RecordLit, RecordPat}, | 8 | use crate::{AssistContext, AssistId, Assists}; |
9 | match_ast, AstNode, SyntaxKind, | ||
10 | SyntaxKind::*, | ||
11 | SyntaxNode, | ||
12 | }; | ||
13 | |||
14 | use 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 | // |
34 | pub(crate) fn reorder_fields(ctx: AssistCtx) -> Option<Assist> { | 25 | pub(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 | ||
38 | fn reorder<R: AstNode>(ctx: AssistCtx) -> Option<Assist> { | 29 | fn 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 | ||
99 | fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> Option<HashMap<String, usize>> { | 90 | fn 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)] |
111 | mod tests { | 102 | mod 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 | ||
7 | use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; | 7 | use 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 | // ``` |
35 | pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> { | 35 | pub(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> { | |||
74 | mod tests { | 74 | mod 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 | ||
12 | use crate::{ | 12 | use 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 | // ``` |
42 | pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> { | 38 | pub(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)] |
76 | mod tests { | 72 | mod 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 @@ | |||
1 | use hir; | 1 | use hir; |
2 | use ra_syntax::{ast, AstNode, SmolStr, TextRange}; | 2 | use ra_syntax::{ast, AstNode, SmolStr, TextRange}; |
3 | 3 | ||
4 | use crate::{ | 4 | use 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 | // ``` |
23 | pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist> { | 19 | pub(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)] |
76 | mod tests { | 77 | mod 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 | ||
8 | use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; | 8 | use 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 | // ``` |
32 | pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> { | 32 | pub(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)] |
64 | mod tests { | 63 | mod 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 | ||
3 | use ra_syntax::{ast, AstNode, T}; | 3 | use ra_syntax::{ast, AstNode, T}; |
4 | 4 | ||
5 | use crate::{Assist, AssistCtx, AssistId}; | 5 | use 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 | // ``` |
18 | pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> { | 18 | pub(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)] |
39 | mod tests { | 39 | mod 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 @@ | |||
1 | use crate::{Assist, AssistCtx, AssistId}; | 1 | use crate::{AssistContext, AssistId, Assists}; |
2 | 2 | ||
3 | use ast::LoopBodyOwner; | 3 | use ast::LoopBodyOwner; |
4 | use ra_fmt::unwrap_trivial_block; | 4 | use 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 | // ``` |
24 | pub(crate) fn unwrap_block(ctx: AssistCtx) -> Option<Assist> { | 24 | pub(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)] |
91 | mod tests { | 91 | mod 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 | ||
13 | mod assist_ctx; | 13 | mod assist_context; |
14 | mod marks; | 14 | mod marks; |
15 | #[cfg(test)] | 15 | #[cfg(test)] |
16 | mod doc_tests; | 16 | mod tests; |
17 | pub mod utils; | 17 | pub mod utils; |
18 | pub mod ast_transform; | 18 | pub mod ast_transform; |
19 | 19 | ||
20 | use hir::Semantics; | 20 | use hir::Semantics; |
21 | use ra_db::{FileId, FileRange}; | 21 | use ra_db::FileRange; |
22 | use ra_ide_db::RootDatabase; | 22 | use ra_ide_db::{source_change::SourceChange, RootDatabase}; |
23 | use ra_syntax::{TextRange, TextSize}; | 23 | use ra_syntax::TextRange; |
24 | use ra_text_edit::TextEdit; | ||
25 | 24 | ||
26 | pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; | 25 | pub(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)] |
31 | pub struct AssistId(pub &'static str); | 30 | pub struct AssistId(pub &'static str); |
32 | 31 | ||
32 | #[derive(Clone, Debug)] | ||
33 | pub struct GroupLabel(pub String); | ||
34 | |||
33 | #[derive(Debug, Clone)] | 35 | #[derive(Debug, Clone)] |
34 | pub struct AssistLabel { | 36 | pub 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, |
42 | pub struct GroupLabel(pub String); | ||
43 | |||
44 | impl 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)] | ||
53 | pub 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)] |
62 | pub struct ResolvedAssist { | 47 | pub 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)] | ||
68 | pub enum AssistFile { | ||
69 | CurrentFile, | ||
70 | TargetFile(FileId), | ||
71 | } | 50 | } |
72 | 51 | ||
73 | impl Default for AssistFile { | 52 | impl 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. |
83 | pub 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>, |
98 | pub 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 | ||
111 | mod handlers { | 93 | mod 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)] | ||
199 | mod 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)] | ||
301 | mod 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 @@ | |||
1 | mod generated; | ||
2 | |||
3 | use std::sync::Arc; | ||
4 | |||
5 | use hir::Semantics; | ||
6 | use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; | ||
7 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; | ||
8 | use ra_syntax::TextRange; | ||
9 | use test_utils::{ | ||
10 | add_cursor, assert_eq_text, extract_offset, extract_range, extract_range_or_offset, | ||
11 | RangeOrOffset, | ||
12 | }; | ||
13 | |||
14 | use crate::{handlers::Handler, Assist, AssistContext, Assists}; | ||
15 | |||
16 | pub(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 | |||
24 | pub(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? | ||
31 | pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) { | ||
32 | check(assist, ra_fixture, ExpectedResult::Target(target)); | ||
33 | } | ||
34 | |||
35 | pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) { | ||
36 | check(assist, ra_fixture, ExpectedResult::NotApplicable); | ||
37 | } | ||
38 | |||
39 | fn 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 | |||
68 | enum ExpectedResult<'a> { | ||
69 | NotApplicable, | ||
70 | After(&'a str), | ||
71 | Target(&'a str), | ||
72 | } | ||
73 | |||
74 | fn 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] | ||
134 | fn 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] | ||
150 | fn 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 | ||
3 | use super::check; | 3 | use super::check_doc_test; |
4 | 4 | ||
5 | #[test] | 5 | #[test] |
6 | fn doctest_add_custom_impl() { | 6 | fn 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] |
25 | fn doctest_add_derive() { | 25 | fn doctest_add_derive() { |
26 | check( | 26 | check_doc_test( |
27 | "add_derive", | 27 | "add_derive", |
28 | r#####" | 28 | r#####" |
29 | struct Point { | 29 | struct Point { |
@@ -43,7 +43,7 @@ struct Point { | |||
43 | 43 | ||
44 | #[test] | 44 | #[test] |
45 | fn doctest_add_explicit_type() { | 45 | fn doctest_add_explicit_type() { |
46 | check( | 46 | check_doc_test( |
47 | "add_explicit_type", | 47 | "add_explicit_type", |
48 | r#####" | 48 | r#####" |
49 | fn main() { | 49 | fn main() { |
@@ -60,7 +60,7 @@ fn main() { | |||
60 | 60 | ||
61 | #[test] | 61 | #[test] |
62 | fn doctest_add_function() { | 62 | fn doctest_add_function() { |
63 | check( | 63 | check_doc_test( |
64 | "add_function", | 64 | "add_function", |
65 | r#####" | 65 | r#####" |
66 | struct Baz; | 66 | struct Baz; |
@@ -87,7 +87,7 @@ fn bar(arg: &str, baz: Baz) { | |||
87 | 87 | ||
88 | #[test] | 88 | #[test] |
89 | fn doctest_add_hash() { | 89 | fn doctest_add_hash() { |
90 | check( | 90 | check_doc_test( |
91 | "add_hash", | 91 | "add_hash", |
92 | r#####" | 92 | r#####" |
93 | fn main() { | 93 | fn main() { |
@@ -104,7 +104,7 @@ fn main() { | |||
104 | 104 | ||
105 | #[test] | 105 | #[test] |
106 | fn doctest_add_impl() { | 106 | fn doctest_add_impl() { |
107 | check( | 107 | check_doc_test( |
108 | "add_impl", | 108 | "add_impl", |
109 | r#####" | 109 | r#####" |
110 | struct Ctx<T: Clone> { | 110 | struct Ctx<T: Clone> { |
@@ -125,7 +125,7 @@ impl<T: Clone> Ctx<T> { | |||
125 | 125 | ||
126 | #[test] | 126 | #[test] |
127 | fn doctest_add_impl_default_members() { | 127 | fn 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#####" |
131 | trait Trait { | 131 | trait Trait { |
@@ -159,7 +159,7 @@ impl Trait for () { | |||
159 | 159 | ||
160 | #[test] | 160 | #[test] |
161 | fn doctest_add_impl_missing_members() { | 161 | fn 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#####" |
165 | trait Trait<T> { | 165 | trait Trait<T> { |
@@ -191,7 +191,7 @@ impl Trait<u32> for () { | |||
191 | 191 | ||
192 | #[test] | 192 | #[test] |
193 | fn doctest_add_new() { | 193 | fn doctest_add_new() { |
194 | check( | 194 | check_doc_test( |
195 | "add_new", | 195 | "add_new", |
196 | r#####" | 196 | r#####" |
197 | struct Ctx<T: Clone> { | 197 | struct Ctx<T: Clone> { |
@@ -213,7 +213,7 @@ impl<T: Clone> Ctx<T> { | |||
213 | 213 | ||
214 | #[test] | 214 | #[test] |
215 | fn doctest_apply_demorgan() { | 215 | fn doctest_apply_demorgan() { |
216 | check( | 216 | check_doc_test( |
217 | "apply_demorgan", | 217 | "apply_demorgan", |
218 | r#####" | 218 | r#####" |
219 | fn main() { | 219 | fn main() { |
@@ -230,7 +230,7 @@ fn main() { | |||
230 | 230 | ||
231 | #[test] | 231 | #[test] |
232 | fn doctest_auto_import() { | 232 | fn doctest_auto_import() { |
233 | check( | 233 | check_doc_test( |
234 | "auto_import", | 234 | "auto_import", |
235 | r#####" | 235 | r#####" |
236 | fn main() { | 236 | fn main() { |
@@ -250,8 +250,21 @@ pub mod std { pub mod collections { pub struct HashMap { } } } | |||
250 | } | 250 | } |
251 | 251 | ||
252 | #[test] | 252 | #[test] |
253 | fn doctest_change_return_type_to_result() { | ||
254 | check_doc_test( | ||
255 | "change_return_type_to_result", | ||
256 | r#####" | ||
257 | fn foo() -> i32<|> { 42i32 } | ||
258 | "#####, | ||
259 | r#####" | ||
260 | fn foo() -> Result<i32, > { Ok(42i32) } | ||
261 | "#####, | ||
262 | ) | ||
263 | } | ||
264 | |||
265 | #[test] | ||
253 | fn doctest_change_visibility() { | 266 | fn 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] |
266 | fn doctest_convert_to_guarded_return() { | 279 | fn 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#####" |
270 | fn main() { | 283 | fn main() { |
@@ -288,7 +301,7 @@ fn main() { | |||
288 | 301 | ||
289 | #[test] | 302 | #[test] |
290 | fn doctest_fill_match_arms() { | 303 | fn doctest_fill_match_arms() { |
291 | check( | 304 | check_doc_test( |
292 | "fill_match_arms", | 305 | "fill_match_arms", |
293 | r#####" | 306 | r#####" |
294 | enum Action { Move { distance: u32 }, Stop } | 307 | enum Action { Move { distance: u32 }, Stop } |
@@ -314,7 +327,7 @@ fn handle(action: Action) { | |||
314 | 327 | ||
315 | #[test] | 328 | #[test] |
316 | fn doctest_flip_binexpr() { | 329 | fn doctest_flip_binexpr() { |
317 | check( | 330 | check_doc_test( |
318 | "flip_binexpr", | 331 | "flip_binexpr", |
319 | r#####" | 332 | r#####" |
320 | fn main() { | 333 | fn main() { |
@@ -331,7 +344,7 @@ fn main() { | |||
331 | 344 | ||
332 | #[test] | 345 | #[test] |
333 | fn doctest_flip_comma() { | 346 | fn doctest_flip_comma() { |
334 | check( | 347 | check_doc_test( |
335 | "flip_comma", | 348 | "flip_comma", |
336 | r#####" | 349 | r#####" |
337 | fn main() { | 350 | fn main() { |
@@ -348,7 +361,7 @@ fn main() { | |||
348 | 361 | ||
349 | #[test] | 362 | #[test] |
350 | fn doctest_flip_trait_bound() { | 363 | fn doctest_flip_trait_bound() { |
351 | check( | 364 | check_doc_test( |
352 | "flip_trait_bound", | 365 | "flip_trait_bound", |
353 | r#####" | 366 | r#####" |
354 | fn foo<T: Clone +<|> Copy>() { } | 367 | fn foo<T: Clone +<|> Copy>() { } |
@@ -361,7 +374,7 @@ fn foo<T: Copy + Clone>() { } | |||
361 | 374 | ||
362 | #[test] | 375 | #[test] |
363 | fn doctest_inline_local_variable() { | 376 | fn doctest_inline_local_variable() { |
364 | check( | 377 | check_doc_test( |
365 | "inline_local_variable", | 378 | "inline_local_variable", |
366 | r#####" | 379 | r#####" |
367 | fn main() { | 380 | fn main() { |
@@ -379,7 +392,7 @@ fn main() { | |||
379 | 392 | ||
380 | #[test] | 393 | #[test] |
381 | fn doctest_introduce_variable() { | 394 | fn doctest_introduce_variable() { |
382 | check( | 395 | check_doc_test( |
383 | "introduce_variable", | 396 | "introduce_variable", |
384 | r#####" | 397 | r#####" |
385 | fn main() { | 398 | fn main() { |
@@ -397,7 +410,7 @@ fn main() { | |||
397 | 410 | ||
398 | #[test] | 411 | #[test] |
399 | fn doctest_invert_if() { | 412 | fn doctest_invert_if() { |
400 | check( | 413 | check_doc_test( |
401 | "invert_if", | 414 | "invert_if", |
402 | r#####" | 415 | r#####" |
403 | fn main() { | 416 | fn main() { |
@@ -414,7 +427,7 @@ fn main() { | |||
414 | 427 | ||
415 | #[test] | 428 | #[test] |
416 | fn doctest_make_raw_string() { | 429 | fn doctest_make_raw_string() { |
417 | check( | 430 | check_doc_test( |
418 | "make_raw_string", | 431 | "make_raw_string", |
419 | r#####" | 432 | r#####" |
420 | fn main() { | 433 | fn main() { |
@@ -431,7 +444,7 @@ fn main() { | |||
431 | 444 | ||
432 | #[test] | 445 | #[test] |
433 | fn doctest_make_usual_string() { | 446 | fn doctest_make_usual_string() { |
434 | check( | 447 | check_doc_test( |
435 | "make_usual_string", | 448 | "make_usual_string", |
436 | r#####" | 449 | r#####" |
437 | fn main() { | 450 | fn main() { |
@@ -448,7 +461,7 @@ fn main() { | |||
448 | 461 | ||
449 | #[test] | 462 | #[test] |
450 | fn doctest_merge_imports() { | 463 | fn doctest_merge_imports() { |
451 | check( | 464 | check_doc_test( |
452 | "merge_imports", | 465 | "merge_imports", |
453 | r#####" | 466 | r#####" |
454 | use std::<|>fmt::Formatter; | 467 | use std::<|>fmt::Formatter; |
@@ -462,7 +475,7 @@ use std::{fmt::Formatter, io}; | |||
462 | 475 | ||
463 | #[test] | 476 | #[test] |
464 | fn doctest_merge_match_arms() { | 477 | fn doctest_merge_match_arms() { |
465 | check( | 478 | check_doc_test( |
466 | "merge_match_arms", | 479 | "merge_match_arms", |
467 | r#####" | 480 | r#####" |
468 | enum Action { Move { distance: u32 }, Stop } | 481 | enum Action { Move { distance: u32 }, Stop } |
@@ -488,7 +501,7 @@ fn handle(action: Action) { | |||
488 | 501 | ||
489 | #[test] | 502 | #[test] |
490 | fn doctest_move_arm_cond_to_match_guard() { | 503 | fn 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#####" |
494 | enum Action { Move { distance: u32 }, Stop } | 507 | enum Action { Move { distance: u32 }, Stop } |
@@ -515,7 +528,7 @@ fn handle(action: Action) { | |||
515 | 528 | ||
516 | #[test] | 529 | #[test] |
517 | fn doctest_move_bounds_to_where_clause() { | 530 | fn 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#####" |
521 | fn apply<T, U, <|>F: FnOnce(T) -> U>(f: F, x: T) -> U { | 534 | fn 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] |
534 | fn doctest_move_guard_to_arm_body() { | 547 | fn 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#####" |
538 | enum Action { Move { distance: u32 }, Stop } | 551 | enum Action { Move { distance: u32 }, Stop } |
@@ -559,7 +572,7 @@ fn handle(action: Action) { | |||
559 | 572 | ||
560 | #[test] | 573 | #[test] |
561 | fn doctest_remove_dbg() { | 574 | fn doctest_remove_dbg() { |
562 | check( | 575 | check_doc_test( |
563 | "remove_dbg", | 576 | "remove_dbg", |
564 | r#####" | 577 | r#####" |
565 | fn main() { | 578 | fn main() { |
@@ -576,7 +589,7 @@ fn main() { | |||
576 | 589 | ||
577 | #[test] | 590 | #[test] |
578 | fn doctest_remove_hash() { | 591 | fn doctest_remove_hash() { |
579 | check( | 592 | check_doc_test( |
580 | "remove_hash", | 593 | "remove_hash", |
581 | r#####" | 594 | r#####" |
582 | fn main() { | 595 | fn main() { |
@@ -593,7 +606,7 @@ fn main() { | |||
593 | 606 | ||
594 | #[test] | 607 | #[test] |
595 | fn doctest_remove_mut() { | 608 | fn doctest_remove_mut() { |
596 | check( | 609 | check_doc_test( |
597 | "remove_mut", | 610 | "remove_mut", |
598 | r#####" | 611 | r#####" |
599 | impl Walrus { | 612 | impl Walrus { |
@@ -610,7 +623,7 @@ impl Walrus { | |||
610 | 623 | ||
611 | #[test] | 624 | #[test] |
612 | fn doctest_reorder_fields() { | 625 | fn doctest_reorder_fields() { |
613 | check( | 626 | check_doc_test( |
614 | "reorder_fields", | 627 | "reorder_fields", |
615 | r#####" | 628 | r#####" |
616 | struct Foo {foo: i32, bar: i32}; | 629 | struct Foo {foo: i32, bar: i32}; |
@@ -625,7 +638,7 @@ const test: Foo = Foo {foo: 1, bar: 0} | |||
625 | 638 | ||
626 | #[test] | 639 | #[test] |
627 | fn doctest_replace_if_let_with_match() { | 640 | fn 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#####" |
631 | enum Action { Move { distance: u32 }, Stop } | 644 | enum Action { Move { distance: u32 }, Stop } |
@@ -653,7 +666,7 @@ fn handle(action: Action) { | |||
653 | 666 | ||
654 | #[test] | 667 | #[test] |
655 | fn doctest_replace_let_with_if_let() { | 668 | fn 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#####" |
659 | enum Option<T> { Some(T), None } | 672 | enum Option<T> { Some(T), None } |
@@ -679,7 +692,7 @@ fn compute() -> Option<i32> { None } | |||
679 | 692 | ||
680 | #[test] | 693 | #[test] |
681 | fn doctest_replace_qualified_name_with_use() { | 694 | fn 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#####" |
685 | fn process(map: std::collections::<|>HashMap<String, String>) {} | 698 | fn process(map: std::collections::<|>HashMap<String, String>) {} |
@@ -694,7 +707,7 @@ fn process(map: HashMap<String, String>) {} | |||
694 | 707 | ||
695 | #[test] | 708 | #[test] |
696 | fn doctest_replace_unwrap_with_match() { | 709 | fn 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#####" |
700 | enum Result<T, E> { Ok(T), Err(E) } | 713 | enum Result<T, E> { Ok(T), Err(E) } |
@@ -718,7 +731,7 @@ fn main() { | |||
718 | 731 | ||
719 | #[test] | 732 | #[test] |
720 | fn doctest_split_import() { | 733 | fn doctest_split_import() { |
721 | check( | 734 | check_doc_test( |
722 | "split_import", | 735 | "split_import", |
723 | r#####" | 736 | r#####" |
724 | use std::<|>collections::HashMap; | 737 | use std::<|>collections::HashMap; |
@@ -731,7 +744,7 @@ use std::{collections::HashMap}; | |||
731 | 744 | ||
732 | #[test] | 745 | #[test] |
733 | fn doctest_unwrap_block() { | 746 | fn doctest_unwrap_block() { |
734 | check( | 747 | check_doc_test( |
735 | "unwrap_block", | 748 | "unwrap_block", |
736 | r#####" | 749 | r#####" |
737 | fn foo() { | 750 | fn 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 | ||
5 | use crate::assist_ctx::ActionBuilder; | ||
6 | use hir::{self, ModPath}; | 5 | use hir::{self, ModPath}; |
7 | use ra_syntax::{ | 6 | use ra_syntax::{ |
8 | ast::{self, NameOwner}, | 7 | ast::{self, NameOwner}, |
@@ -12,6 +11,8 @@ use ra_syntax::{ | |||
12 | }; | 11 | }; |
13 | use ra_text_edit::TextEditBuilder; | 12 | use ra_text_edit::TextEditBuilder; |
14 | 13 | ||
14 | use 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 | ||
3 | mod cfg_expr; | 3 | mod cfg_expr; |
4 | 4 | ||
5 | use std::iter::IntoIterator; | ||
6 | |||
7 | use ra_syntax::SmolStr; | 5 | use ra_syntax::SmolStr; |
8 | use rustc_hash::FxHashSet; | 6 | use 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 | ||
3 | use std::str::FromStr; | 47 | use std::str::FromStr; |
4 | use std::sync::Arc; | 48 | use 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 | |||
3 | use ra_assists::{resolved_assists, AssistAction}; | ||
4 | use ra_db::{FilePosition, FileRange}; | ||
5 | use ra_ide_db::RootDatabase; | ||
6 | |||
7 | use crate::{FileId, SourceChange, SourceFileEdit}; | ||
8 | |||
9 | pub use ra_assists::AssistId; | ||
10 | |||
11 | #[derive(Debug)] | ||
12 | pub struct Assist { | ||
13 | pub id: AssistId, | ||
14 | pub label: String, | ||
15 | pub group_label: Option<String>, | ||
16 | pub source_change: SourceChange, | ||
17 | } | ||
18 | |||
19 | pub(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 | |||
34 | fn 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 | ||
3 | use std::fmt; | 3 | use std::fmt; |
4 | 4 | ||
5 | use super::completion_config::SnippetCap; | ||
6 | use hir::Documentation; | 5 | use hir::Documentation; |
7 | use ra_syntax::TextRange; | 6 | use ra_syntax::TextRange; |
8 | use ra_text_edit::TextEdit; | 7 | use ra_text_edit::TextEdit; |
9 | 8 | ||
9 | use 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) | ||
3 | use std::{ | 5 | use 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> { | |||
417 | fn main() { | 458 | fn 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 | ||
18 | pub mod mock_analysis; | 18 | pub mod mock_analysis; |
19 | mod source_change; | ||
20 | 19 | ||
21 | mod prime_caches; | 20 | mod prime_caches; |
22 | mod status; | 21 | mod status; |
@@ -32,7 +31,6 @@ mod syntax_highlighting; | |||
32 | mod parent_module; | 31 | mod parent_module; |
33 | mod references; | 32 | mod references; |
34 | mod impls; | 33 | mod impls; |
35 | mod assists; | ||
36 | mod diagnostics; | 34 | mod diagnostics; |
37 | mod syntax_tree; | 35 | mod syntax_tree; |
38 | mod folding_ranges; | 36 | mod folding_ranges; |
@@ -65,7 +63,6 @@ use ra_syntax::{SourceFile, TextRange, TextSize}; | |||
65 | use crate::display::ToNav; | 63 | use crate::display::ToNav; |
66 | 64 | ||
67 | pub use crate::{ | 65 | pub 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 | ||
88 | pub use hir::Documentation; | 84 | pub use hir::Documentation; |
85 | pub use ra_assists::AssistId; | ||
89 | pub use ra_db::{ | 86 | pub 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 | ||
138 | impl Default for AnalysisHost { | 136 | #[derive(Debug)] |
139 | fn default() -> AnalysisHost { | 137 | pub 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 | ||
144 | impl AnalysisHost { | 144 | impl AnalysisHost { |
@@ -188,6 +188,12 @@ impl AnalysisHost { | |||
188 | } | 188 | } |
189 | } | 189 | } |
190 | 190 | ||
191 | impl 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 | ||
3 | use crate::source_change::SourceFileEdit; | 3 | use std::{collections::HashMap, iter::once, str::FromStr}; |
4 | |||
4 | use ra_db::{SourceDatabase, SourceDatabaseExt}; | 5 | use ra_db::{SourceDatabase, SourceDatabaseExt}; |
5 | use ra_ide_db::symbol_index::SymbolsDatabase; | 6 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; |
6 | use ra_ide_db::RootDatabase; | ||
7 | use ra_syntax::ast::make::try_expr_from_text; | ||
8 | use ra_syntax::ast::{ | 7 | use 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 | }; |
11 | use ra_syntax::{AstNode, SyntaxElement, SyntaxKind, SyntaxNode}; | 11 | use ra_syntax::{AstNode, SyntaxElement, SyntaxKind, SyntaxNode}; |
12 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 12 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
13 | use rustc_hash::FxHashMap; | 13 | use rustc_hash::FxHashMap; |
14 | use std::collections::HashMap; | 14 | |
15 | use std::{iter::once, str::FromStr}; | 15 | use crate::SourceFileEdit; |
16 | 16 | ||
17 | #[derive(Debug, PartialEq)] | 17 | #[derive(Debug, PartialEq)] |
18 | pub struct SsrError(String); | 18 | pub 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 | ||
18 | use ra_db::{FilePosition, SourceDatabase}; | 18 | use ra_db::{FilePosition, SourceDatabase}; |
19 | use ra_fmt::leading_indent; | 19 | use ra_fmt::leading_indent; |
20 | use ra_ide_db::RootDatabase; | 20 | use ra_ide_db::{source_change::SingleFileChange, RootDatabase}; |
21 | use ra_syntax::{ | 21 | use 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 | |||
26 | use ra_text_edit::TextEdit; | 27 | use ra_text_edit::TextEdit; |
27 | 28 | ||
28 | use crate::{source_change::SingleFileChange, SourceChange}; | 29 | use crate::SourceChange; |
29 | 30 | ||
30 | pub(crate) use on_enter::on_enter; | 31 | pub(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 | ||
8 | use hir::{ | 8 | use 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 | }; |
12 | use ra_prof::profile; | 12 | use 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; | |||
10 | pub mod defs; | 10 | pub mod defs; |
11 | pub mod search; | 11 | pub mod search; |
12 | pub mod imports_locator; | 12 | pub mod imports_locator; |
13 | pub mod source_change; | ||
13 | mod wasm_shims; | 14 | mod wasm_shims; |
14 | 15 | ||
15 | use std::sync::Arc; | 16 | use 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 | ||
6 | use ra_db::RelativePathBuf; | 6 | use ra_db::{FileId, FilePosition, RelativePathBuf, SourceRootId}; |
7 | use ra_text_edit::TextEdit; | 7 | use ra_text_edit::{TextEdit, TextSize}; |
8 | 8 | ||
9 | use crate::{FileId, FilePosition, SourceRootId, TextSize}; | 9 | #[derive(Debug, Clone)] |
10 | |||
11 | #[derive(Debug)] | ||
12 | pub struct SourceChange { | 10 | pub 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 { | |||
19 | impl SourceChange { | 18 | impl 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)] |
95 | pub struct SourceFileEdit { | 94 | pub 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)] |
101 | pub enum FileSystemEdit { | 100 | pub 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 | ||
106 | pub(crate) struct SingleFileChange { | 105 | pub 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 | ||
112 | impl SingleFileChange { | 111 | impl 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 { | |||
278 | pub struct ExternResources { | 282 | pub 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 | ||
283 | pub fn load_extern_resources( | 288 | pub 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 | ||
7 | use text_size::{TextRange, TextSize}; | 7 | pub 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 | }; |
45 | use ra_project_model::TargetKind; | ||
45 | 46 | ||
46 | pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> { | 47 | pub 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 | }; |
10 | use rust_analyzer::req::{ | 10 | use 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 | }; |
14 | use serde_json::json; | 15 | use serde_json::json; |
15 | use tempfile::TempDir; | 16 | use tempfile::TempDir; |
@@ -574,7 +575,7 @@ version = \"0.0.0\" | |||
574 | } | 575 | } |
575 | 576 | ||
576 | #[test] | 577 | #[test] |
577 | fn resolve_include_concat_env() { | 578 | fn 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 |
603 | include!(concat!(env!("OUT_DIR"), "/hello.rs")); | 606 | include!(concat!(env!("OUT_DIR"), "/hello.rs")); |
604 | 607 | ||
608 | #[cfg(atom_cfg)] | ||
609 | struct A; | ||
610 | #[cfg(bad_atom_cfg)] | ||
611 | struct A; | ||
612 | #[cfg(featlike = "set")] | ||
613 | struct B; | ||
614 | #[cfg(featlike = "not_set")] | ||
615 | struct B; | ||
616 | |||
617 | fn main() { | ||
618 | let va = A; | ||
619 | let vb = B; | ||
620 | message(); | ||
621 | } | ||
622 | |||
605 | fn main() { message(); } | 623 | fn 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)] |
159 | pub struct FixtureEntry { | 159 | pub 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 | /// ``` |
172 | pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> { | 172 | pub 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. | ||
187 | The 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 | /// ``` | ||
228 | fn 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 | |||
244 | fn 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 | |||
253 | fn indent_len(s: &str) -> usize { | ||
254 | s.len() - s.trim_start().len() | ||
255 | } | ||
256 | |||
257 | #[test] | ||
258 | #[should_panic] | ||
259 | fn 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] | ||
273 | fn 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 | ||
283 | mod foo; | ||
284 | //- /foo.rs | ||
285 | struct 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 |
206 | pub fn parse_single_fixture(fixture: &str) -> Option<FixtureEntry> { | 292 | pub 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("//-")) { |