diff options
Diffstat (limited to 'crates/ra_assists/src')
47 files changed, 4197 insertions, 1686 deletions
diff --git a/crates/ra_assists/src/assist_config.rs b/crates/ra_assists/src/assist_config.rs new file mode 100644 index 000000000..c0a0226fb --- /dev/null +++ b/crates/ra_assists/src/assist_config.rs | |||
@@ -0,0 +1,27 @@ | |||
1 | //! Settings for tweaking assists. | ||
2 | //! | ||
3 | //! The fun thing here is `SnippetCap` -- this type can only be created in this | ||
4 | //! module, and we use to statically check that we only produce snippet | ||
5 | //! assists if we are allowed to. | ||
6 | |||
7 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
8 | pub struct AssistConfig { | ||
9 | pub snippet_cap: Option<SnippetCap>, | ||
10 | } | ||
11 | |||
12 | impl AssistConfig { | ||
13 | pub fn allow_snippets(&mut self, yes: bool) { | ||
14 | self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None } | ||
15 | } | ||
16 | } | ||
17 | |||
18 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
19 | pub struct SnippetCap { | ||
20 | _private: (), | ||
21 | } | ||
22 | |||
23 | impl Default for AssistConfig { | ||
24 | fn default() -> Self { | ||
25 | AssistConfig { snippet_cap: Some(SnippetCap { _private: () }) } | ||
26 | } | ||
27 | } | ||
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs new file mode 100644 index 000000000..f3af70a3e --- /dev/null +++ b/crates/ra_assists/src/assist_context.rs | |||
@@ -0,0 +1,257 @@ | |||
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::{ | ||
19 | assist_config::{AssistConfig, SnippetCap}, | ||
20 | Assist, AssistId, GroupLabel, ResolvedAssist, | ||
21 | }; | ||
22 | |||
23 | /// `AssistContext` allows to apply an assist or check if it could be applied. | ||
24 | /// | ||
25 | /// Assists use a somewhat over-engineered approach, given the current needs. | ||
26 | /// The assists workflow consists of two phases. In the first phase, a user asks | ||
27 | /// for the list of available assists. In the second phase, the user picks a | ||
28 | /// particular assist and it gets applied. | ||
29 | /// | ||
30 | /// There are two peculiarities here: | ||
31 | /// | ||
32 | /// * first, we ideally avoid computing more things then necessary to answer "is | ||
33 | /// assist applicable" in the first phase. | ||
34 | /// * second, when we are applying assist, we don't have a guarantee that there | ||
35 | /// weren't any changes between the point when user asked for assists and when | ||
36 | /// they applied a particular assist. So, when applying assist, we need to do | ||
37 | /// all the checks from scratch. | ||
38 | /// | ||
39 | /// To avoid repeating the same code twice for both "check" and "apply" | ||
40 | /// functions, we use an approach reminiscent of that of Django's function based | ||
41 | /// views dealing with forms. Each assist receives a runtime parameter, | ||
42 | /// `resolve`. It first check if an edit is applicable (potentially computing | ||
43 | /// info required to compute the actual edit). If it is applicable, and | ||
44 | /// `resolve` is `true`, it then computes the actual edit. | ||
45 | /// | ||
46 | /// So, to implement the original assists workflow, we can first apply each edit | ||
47 | /// with `resolve = false`, and then applying the selected edit again, with | ||
48 | /// `resolve = true` this time. | ||
49 | /// | ||
50 | /// Note, however, that we don't actually use such two-phase logic at the | ||
51 | /// moment, because the LSP API is pretty awkward in this place, and it's much | ||
52 | /// easier to just compute the edit eagerly :-) | ||
53 | pub(crate) struct AssistContext<'a> { | ||
54 | pub(crate) config: &'a AssistConfig, | ||
55 | pub(crate) sema: Semantics<'a, RootDatabase>, | ||
56 | pub(crate) db: &'a RootDatabase, | ||
57 | pub(crate) frange: FileRange, | ||
58 | source_file: SourceFile, | ||
59 | } | ||
60 | |||
61 | impl<'a> AssistContext<'a> { | ||
62 | pub(crate) fn new( | ||
63 | sema: Semantics<'a, RootDatabase>, | ||
64 | config: &'a AssistConfig, | ||
65 | frange: FileRange, | ||
66 | ) -> AssistContext<'a> { | ||
67 | let source_file = sema.parse(frange.file_id); | ||
68 | let db = sema.db; | ||
69 | AssistContext { config, sema, db, frange, source_file } | ||
70 | } | ||
71 | |||
72 | // NB, this ignores active selection. | ||
73 | pub(crate) fn offset(&self) -> TextSize { | ||
74 | self.frange.range.start() | ||
75 | } | ||
76 | |||
77 | pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> { | ||
78 | self.source_file.syntax().token_at_offset(self.offset()) | ||
79 | } | ||
80 | pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> { | ||
81 | self.token_at_offset().find(|it| it.kind() == kind) | ||
82 | } | ||
83 | pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> { | ||
84 | find_node_at_offset(self.source_file.syntax(), self.offset()) | ||
85 | } | ||
86 | pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> { | ||
87 | self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset()) | ||
88 | } | ||
89 | pub(crate) fn covering_element(&self) -> SyntaxElement { | ||
90 | find_covering_element(self.source_file.syntax(), self.frange.range) | ||
91 | } | ||
92 | // FIXME: remove | ||
93 | pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement { | ||
94 | find_covering_element(self.source_file.syntax(), range) | ||
95 | } | ||
96 | } | ||
97 | |||
98 | pub(crate) struct Assists { | ||
99 | resolve: bool, | ||
100 | file: FileId, | ||
101 | buf: Vec<(Assist, Option<SourceChange>)>, | ||
102 | } | ||
103 | |||
104 | impl Assists { | ||
105 | pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { | ||
106 | Assists { resolve: true, file: ctx.frange.file_id, buf: Vec::new() } | ||
107 | } | ||
108 | pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { | ||
109 | Assists { resolve: false, file: ctx.frange.file_id, buf: Vec::new() } | ||
110 | } | ||
111 | |||
112 | pub(crate) fn finish_unresolved(self) -> Vec<Assist> { | ||
113 | assert!(!self.resolve); | ||
114 | self.finish() | ||
115 | .into_iter() | ||
116 | .map(|(label, edit)| { | ||
117 | assert!(edit.is_none()); | ||
118 | label | ||
119 | }) | ||
120 | .collect() | ||
121 | } | ||
122 | |||
123 | pub(crate) fn finish_resolved(self) -> Vec<ResolvedAssist> { | ||
124 | assert!(self.resolve); | ||
125 | self.finish() | ||
126 | .into_iter() | ||
127 | .map(|(label, edit)| ResolvedAssist { assist: label, source_change: edit.unwrap() }) | ||
128 | .collect() | ||
129 | } | ||
130 | |||
131 | pub(crate) fn add( | ||
132 | &mut self, | ||
133 | id: AssistId, | ||
134 | label: impl Into<String>, | ||
135 | target: TextRange, | ||
136 | f: impl FnOnce(&mut AssistBuilder), | ||
137 | ) -> Option<()> { | ||
138 | let label = Assist::new(id, label.into(), None, target); | ||
139 | self.add_impl(label, f) | ||
140 | } | ||
141 | pub(crate) fn add_group( | ||
142 | &mut self, | ||
143 | group: &GroupLabel, | ||
144 | id: AssistId, | ||
145 | label: impl Into<String>, | ||
146 | target: TextRange, | ||
147 | f: impl FnOnce(&mut AssistBuilder), | ||
148 | ) -> Option<()> { | ||
149 | let label = Assist::new(id, label.into(), Some(group.clone()), target); | ||
150 | self.add_impl(label, f) | ||
151 | } | ||
152 | fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { | ||
153 | let change_label = label.label.clone(); | ||
154 | let source_change = if self.resolve { | ||
155 | let mut builder = AssistBuilder::new(self.file); | ||
156 | f(&mut builder); | ||
157 | Some(builder.finish(change_label)) | ||
158 | } else { | ||
159 | None | ||
160 | }; | ||
161 | |||
162 | self.buf.push((label, source_change)); | ||
163 | Some(()) | ||
164 | } | ||
165 | |||
166 | fn finish(mut self) -> Vec<(Assist, Option<SourceChange>)> { | ||
167 | self.buf.sort_by_key(|(label, _edit)| label.target.len()); | ||
168 | self.buf | ||
169 | } | ||
170 | } | ||
171 | |||
172 | pub(crate) struct AssistBuilder { | ||
173 | edit: TextEditBuilder, | ||
174 | file: FileId, | ||
175 | is_snippet: bool, | ||
176 | } | ||
177 | |||
178 | impl AssistBuilder { | ||
179 | pub(crate) fn new(file: FileId) -> AssistBuilder { | ||
180 | AssistBuilder { edit: TextEditBuilder::default(), file, is_snippet: false } | ||
181 | } | ||
182 | |||
183 | /// Remove specified `range` of text. | ||
184 | pub(crate) fn delete(&mut self, range: TextRange) { | ||
185 | self.edit.delete(range) | ||
186 | } | ||
187 | /// Append specified `text` at the given `offset` | ||
188 | pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) { | ||
189 | self.edit.insert(offset, text.into()) | ||
190 | } | ||
191 | /// Append specified `snippet` at the given `offset` | ||
192 | pub(crate) fn insert_snippet( | ||
193 | &mut self, | ||
194 | _cap: SnippetCap, | ||
195 | offset: TextSize, | ||
196 | snippet: impl Into<String>, | ||
197 | ) { | ||
198 | self.is_snippet = true; | ||
199 | self.insert(offset, snippet); | ||
200 | } | ||
201 | /// Replaces specified `range` of text with a given string. | ||
202 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { | ||
203 | self.edit.replace(range, replace_with.into()) | ||
204 | } | ||
205 | /// Replaces specified `range` of text with a given `snippet`. | ||
206 | pub(crate) fn replace_snippet( | ||
207 | &mut self, | ||
208 | _cap: SnippetCap, | ||
209 | range: TextRange, | ||
210 | snippet: impl Into<String>, | ||
211 | ) { | ||
212 | self.is_snippet = true; | ||
213 | self.replace(range, snippet); | ||
214 | } | ||
215 | pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { | ||
216 | algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) | ||
217 | } | ||
218 | /// Replaces specified `node` of text with a given string, reindenting the | ||
219 | /// string to maintain `node`'s existing indent. | ||
220 | // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent | ||
221 | pub(crate) fn replace_node_and_indent( | ||
222 | &mut self, | ||
223 | node: &SyntaxNode, | ||
224 | replace_with: impl Into<String>, | ||
225 | ) { | ||
226 | let mut replace_with = replace_with.into(); | ||
227 | if let Some(indent) = leading_indent(node) { | ||
228 | replace_with = reindent(&replace_with, &indent) | ||
229 | } | ||
230 | self.replace(node.text_range(), replace_with) | ||
231 | } | ||
232 | pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { | ||
233 | let node = rewriter.rewrite_root().unwrap(); | ||
234 | let new = rewriter.rewrite(&node); | ||
235 | algo::diff(&node, &new).into_text_edit(&mut self.edit) | ||
236 | } | ||
237 | |||
238 | // FIXME: better API | ||
239 | pub(crate) fn set_file(&mut self, assist_file: FileId) { | ||
240 | self.file = assist_file; | ||
241 | } | ||
242 | |||
243 | // FIXME: kill this API | ||
244 | /// Get access to the raw `TextEditBuilder`. | ||
245 | pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { | ||
246 | &mut self.edit | ||
247 | } | ||
248 | |||
249 | fn finish(self, change_label: String) -> SourceChange { | ||
250 | let edit = self.edit.finish(); | ||
251 | let mut res = SingleFileChange { label: change_label, edit }.into_source_change(self.file); | ||
252 | if self.is_snippet { | ||
253 | res.is_snippet = true; | ||
254 | } | ||
255 | res | ||
256 | } | ||
257 | } | ||
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs deleted file mode 100644 index 2fe7c3de3..000000000 --- a/crates/ra_assists/src/assist_ctx.rs +++ /dev/null | |||
@@ -1,257 +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}, | ||
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 | use algo::SyntaxRewriter; | ||
15 | |||
16 | #[derive(Clone, Debug)] | ||
17 | pub(crate) struct Assist(pub(crate) Vec<AssistInfo>); | ||
18 | |||
19 | #[derive(Clone, Debug)] | ||
20 | pub(crate) struct AssistInfo { | ||
21 | pub(crate) label: AssistLabel, | ||
22 | pub(crate) group_label: Option<GroupLabel>, | ||
23 | pub(crate) action: Option<AssistAction>, | ||
24 | } | ||
25 | |||
26 | impl AssistInfo { | ||
27 | fn new(label: AssistLabel) -> AssistInfo { | ||
28 | AssistInfo { label, group_label: None, action: None } | ||
29 | } | ||
30 | |||
31 | fn resolved(self, action: AssistAction) -> AssistInfo { | ||
32 | AssistInfo { action: Some(action), ..self } | ||
33 | } | ||
34 | |||
35 | fn with_group(self, group_label: GroupLabel) -> AssistInfo { | ||
36 | AssistInfo { group_label: Some(group_label), ..self } | ||
37 | } | ||
38 | |||
39 | pub(crate) fn into_resolved(self) -> Option<ResolvedAssist> { | ||
40 | let label = self.label; | ||
41 | let group_label = self.group_label; | ||
42 | self.action.map(|action| ResolvedAssist { label, group_label, action }) | ||
43 | } | ||
44 | } | ||
45 | |||
46 | pub(crate) type AssistHandler = fn(AssistCtx) -> Option<Assist>; | ||
47 | |||
48 | /// `AssistCtx` allows to apply an assist or check if it could be applied. | ||
49 | /// | ||
50 | /// Assists use a somewhat over-engineered approach, given the current needs. The | ||
51 | /// assists workflow consists of two phases. In the first phase, a user asks for | ||
52 | /// the list of available assists. In the second phase, the user picks a | ||
53 | /// particular assist and it gets applied. | ||
54 | /// | ||
55 | /// There are two peculiarities here: | ||
56 | /// | ||
57 | /// * first, we ideally avoid computing more things then necessary to answer | ||
58 | /// "is assist applicable" in the first phase. | ||
59 | /// * second, when we are applying assist, we don't have a guarantee that there | ||
60 | /// weren't any changes between the point when user asked for assists and when | ||
61 | /// they applied a particular assist. So, when applying assist, we need to do | ||
62 | /// all the checks from scratch. | ||
63 | /// | ||
64 | /// To avoid repeating the same code twice for both "check" and "apply" | ||
65 | /// functions, we use an approach reminiscent of that of Django's function based | ||
66 | /// views dealing with forms. Each assist receives a runtime parameter, | ||
67 | /// `should_compute_edit`. It first check if an edit is applicable (potentially | ||
68 | /// computing info required to compute the actual edit). If it is applicable, | ||
69 | /// and `should_compute_edit` is `true`, it then computes the actual edit. | ||
70 | /// | ||
71 | /// So, to implement the original assists workflow, we can first apply each edit | ||
72 | /// with `should_compute_edit = false`, and then applying the selected edit | ||
73 | /// again, with `should_compute_edit = true` this time. | ||
74 | /// | ||
75 | /// Note, however, that we don't actually use such two-phase logic at the | ||
76 | /// moment, because the LSP API is pretty awkward in this place, and it's much | ||
77 | /// easier to just compute the edit eagerly :-) | ||
78 | #[derive(Clone)] | ||
79 | pub(crate) struct AssistCtx<'a> { | ||
80 | pub(crate) sema: &'a Semantics<'a, RootDatabase>, | ||
81 | pub(crate) db: &'a RootDatabase, | ||
82 | pub(crate) frange: FileRange, | ||
83 | source_file: SourceFile, | ||
84 | should_compute_edit: bool, | ||
85 | } | ||
86 | |||
87 | impl<'a> AssistCtx<'a> { | ||
88 | pub fn new( | ||
89 | sema: &'a Semantics<'a, RootDatabase>, | ||
90 | frange: FileRange, | ||
91 | should_compute_edit: bool, | ||
92 | ) -> AssistCtx<'a> { | ||
93 | let source_file = sema.parse(frange.file_id); | ||
94 | AssistCtx { sema, db: sema.db, frange, source_file, should_compute_edit } | ||
95 | } | ||
96 | |||
97 | pub(crate) fn add_assist( | ||
98 | self, | ||
99 | id: AssistId, | ||
100 | label: impl Into<String>, | ||
101 | f: impl FnOnce(&mut ActionBuilder), | ||
102 | ) -> Option<Assist> { | ||
103 | let label = AssistLabel::new(label.into(), id); | ||
104 | |||
105 | let mut info = AssistInfo::new(label); | ||
106 | if self.should_compute_edit { | ||
107 | let action = { | ||
108 | let mut edit = ActionBuilder::default(); | ||
109 | f(&mut edit); | ||
110 | edit.build() | ||
111 | }; | ||
112 | info = info.resolved(action) | ||
113 | }; | ||
114 | |||
115 | Some(Assist(vec![info])) | ||
116 | } | ||
117 | |||
118 | pub(crate) fn add_assist_group(self, group_name: impl Into<String>) -> AssistGroup<'a> { | ||
119 | AssistGroup { ctx: self, group_name: group_name.into(), assists: Vec::new() } | ||
120 | } | ||
121 | |||
122 | pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> { | ||
123 | self.source_file.syntax().token_at_offset(self.frange.range.start()) | ||
124 | } | ||
125 | |||
126 | pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> { | ||
127 | self.token_at_offset().find(|it| it.kind() == kind) | ||
128 | } | ||
129 | |||
130 | pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> { | ||
131 | find_node_at_offset(self.source_file.syntax(), self.frange.range.start()) | ||
132 | } | ||
133 | pub(crate) fn covering_element(&self) -> SyntaxElement { | ||
134 | find_covering_element(self.source_file.syntax(), self.frange.range) | ||
135 | } | ||
136 | pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement { | ||
137 | find_covering_element(self.source_file.syntax(), range) | ||
138 | } | ||
139 | } | ||
140 | |||
141 | pub(crate) struct AssistGroup<'a> { | ||
142 | ctx: AssistCtx<'a>, | ||
143 | group_name: String, | ||
144 | assists: Vec<AssistInfo>, | ||
145 | } | ||
146 | |||
147 | impl<'a> AssistGroup<'a> { | ||
148 | pub(crate) fn add_assist( | ||
149 | &mut self, | ||
150 | id: AssistId, | ||
151 | label: impl Into<String>, | ||
152 | f: impl FnOnce(&mut ActionBuilder), | ||
153 | ) { | ||
154 | let label = AssistLabel::new(label.into(), id); | ||
155 | |||
156 | let mut info = AssistInfo::new(label).with_group(GroupLabel(self.group_name.clone())); | ||
157 | if self.ctx.should_compute_edit { | ||
158 | let action = { | ||
159 | let mut edit = ActionBuilder::default(); | ||
160 | f(&mut edit); | ||
161 | edit.build() | ||
162 | }; | ||
163 | info = info.resolved(action) | ||
164 | }; | ||
165 | |||
166 | self.assists.push(info) | ||
167 | } | ||
168 | |||
169 | pub(crate) fn finish(self) -> Option<Assist> { | ||
170 | if self.assists.is_empty() { | ||
171 | None | ||
172 | } else { | ||
173 | Some(Assist(self.assists)) | ||
174 | } | ||
175 | } | ||
176 | } | ||
177 | |||
178 | #[derive(Default)] | ||
179 | pub(crate) struct ActionBuilder { | ||
180 | edit: TextEditBuilder, | ||
181 | cursor_position: Option<TextSize>, | ||
182 | target: Option<TextRange>, | ||
183 | file: AssistFile, | ||
184 | } | ||
185 | |||
186 | impl ActionBuilder { | ||
187 | /// Replaces specified `range` of text with a given string. | ||
188 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { | ||
189 | self.edit.replace(range, replace_with.into()) | ||
190 | } | ||
191 | |||
192 | /// Replaces specified `node` of text with a given string, reindenting the | ||
193 | /// string to maintain `node`'s existing indent. | ||
194 | // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent | ||
195 | pub(crate) fn replace_node_and_indent( | ||
196 | &mut self, | ||
197 | node: &SyntaxNode, | ||
198 | replace_with: impl Into<String>, | ||
199 | ) { | ||
200 | let mut replace_with = replace_with.into(); | ||
201 | if let Some(indent) = leading_indent(node) { | ||
202 | replace_with = reindent(&replace_with, &indent) | ||
203 | } | ||
204 | self.replace(node.text_range(), replace_with) | ||
205 | } | ||
206 | |||
207 | /// Remove specified `range` of text. | ||
208 | #[allow(unused)] | ||
209 | pub(crate) fn delete(&mut self, range: TextRange) { | ||
210 | self.edit.delete(range) | ||
211 | } | ||
212 | |||
213 | /// Append specified `text` at the given `offset` | ||
214 | pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) { | ||
215 | self.edit.insert(offset, text.into()) | ||
216 | } | ||
217 | |||
218 | /// Specify desired position of the cursor after the assist is applied. | ||
219 | pub(crate) fn set_cursor(&mut self, offset: TextSize) { | ||
220 | self.cursor_position = Some(offset) | ||
221 | } | ||
222 | |||
223 | /// Specify that the assist should be active withing the `target` range. | ||
224 | /// | ||
225 | /// Target ranges are used to sort assists: the smaller the target range, | ||
226 | /// the more specific assist is, and so it should be sorted first. | ||
227 | pub(crate) fn target(&mut self, target: TextRange) { | ||
228 | self.target = Some(target) | ||
229 | } | ||
230 | |||
231 | /// Get access to the raw `TextEditBuilder`. | ||
232 | pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { | ||
233 | &mut self.edit | ||
234 | } | ||
235 | |||
236 | pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { | ||
237 | algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) | ||
238 | } | ||
239 | pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { | ||
240 | let node = rewriter.rewrite_root().unwrap(); | ||
241 | let new = rewriter.rewrite(&node); | ||
242 | algo::diff(&node, &new).into_text_edit(&mut self.edit) | ||
243 | } | ||
244 | |||
245 | pub(crate) fn set_file(&mut self, assist_file: AssistFile) { | ||
246 | self.file = assist_file | ||
247 | } | ||
248 | |||
249 | fn build(self) -> AssistAction { | ||
250 | AssistAction { | ||
251 | edit: self.edit.finish(), | ||
252 | cursor_position: self.cursor_position, | ||
253 | target: self.target, | ||
254 | file: self.file, | ||
255 | } | ||
256 | } | ||
257 | } | ||
diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs index 9ac65ab39..3079a02a2 100644 --- a/crates/ra_assists/src/ast_transform.rs +++ b/crates/ra_assists/src/ast_transform.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | //! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined. | 1 | //! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined. |
2 | use rustc_hash::FxHashMap; | 2 | use rustc_hash::FxHashMap; |
3 | 3 | ||
4 | use hir::{PathResolution, SemanticsScope}; | 4 | use hir::{HirDisplay, PathResolution, SemanticsScope}; |
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{ |
7 | algo::SyntaxRewriter, | 7 | algo::SyntaxRewriter, |
@@ -51,7 +51,27 @@ impl<'a> SubstituteTypeParams<'a> { | |||
51 | .into_iter() | 51 | .into_iter() |
52 | // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky | 52 | // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky |
53 | .skip(1) | 53 | .skip(1) |
54 | .zip(substs.into_iter()) | 54 | // The actual list of trait type parameters may be longer than the one |
55 | // used in the `impl` block due to trailing default type parametrs. | ||
56 | // For that case we extend the `substs` with an empty iterator so we | ||
57 | // can still hit those trailing values and check if they actually have | ||
58 | // a default type. If they do, go for that type from `hir` to `ast` so | ||
59 | // the resulting change can be applied correctly. | ||
60 | .zip(substs.into_iter().map(Some).chain(std::iter::repeat(None))) | ||
61 | .filter_map(|(k, v)| match v { | ||
62 | Some(v) => Some((k, v)), | ||
63 | None => { | ||
64 | let default = k.default(source_scope.db)?; | ||
65 | Some(( | ||
66 | k, | ||
67 | ast::make::type_ref( | ||
68 | &default | ||
69 | .display_source_code(source_scope.db, source_scope.module()?.into()) | ||
70 | .ok()?, | ||
71 | ), | ||
72 | )) | ||
73 | } | ||
74 | }) | ||
55 | .collect(); | 75 | .collect(); |
56 | return SubstituteTypeParams { | 76 | return SubstituteTypeParams { |
57 | source_scope, | 77 | source_scope, |
diff --git a/crates/ra_assists/src/doc_tests.rs b/crates/ra_assists/src/doc_tests.rs deleted file mode 100644 index c0f9bc1fb..000000000 --- a/crates/ra_assists/src/doc_tests.rs +++ /dev/null | |||
@@ -1,35 +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 = assist.action.edit.apply(&before); | ||
34 | assert_eq_text!(after, &actual); | ||
35 | } | ||
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs index 4ea26a550..fa70c8496 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 | // |
@@ -22,10 +25,10 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
22 | // struct S; | 25 | // struct S; |
23 | // | 26 | // |
24 | // impl Debug for S { | 27 | // impl Debug for S { |
25 | // | 28 | // $0 |
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 | ||
@@ -46,11 +49,10 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> { | |||
46 | let start_offset = annotated.syntax().parent()?.text_range().end(); | 49 | let start_offset = annotated.syntax().parent()?.text_range().end(); |
47 | 50 | ||
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 | |||
51 | ctx.add_assist(AssistId("add_custom_impl"), label, |edit| { | ||
52 | edit.target(attr.syntax().text_range()); | ||
53 | 53 | ||
54 | let target = attr.syntax().text_range(); | ||
55 | acc.add(AssistId("add_custom_impl"), label, target, |builder| { | ||
54 | let new_attr_input = input | 56 | let new_attr_input = input |
55 | .syntax() | 57 | .syntax() |
56 | .descendants_with_tokens() | 58 | .descendants_with_tokens() |
@@ -61,20 +63,11 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> { | |||
61 | let has_more_derives = !new_attr_input.is_empty(); | 63 | let has_more_derives = !new_attr_input.is_empty(); |
62 | let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string(); | 64 | let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string(); |
63 | 65 | ||
64 | let mut buf = String::new(); | 66 | if has_more_derives { |
65 | buf.push_str("\n\nimpl "); | 67 | builder.replace(input.syntax().text_range(), new_attr_input); |
66 | buf.push_str(trait_token.text().as_str()); | ||
67 | buf.push_str(" for "); | ||
68 | buf.push_str(annotated_name.as_str()); | ||
69 | buf.push_str(" {\n"); | ||
70 | |||
71 | let cursor_delta = if has_more_derives { | ||
72 | let delta = input.syntax().text_range().len() - TextSize::of(&new_attr_input); | ||
73 | edit.replace(input.syntax().text_range(), new_attr_input); | ||
74 | delta | ||
75 | } else { | 68 | } else { |
76 | let attr_range = attr.syntax().text_range(); | 69 | let attr_range = attr.syntax().text_range(); |
77 | edit.delete(attr_range); | 70 | builder.delete(attr_range); |
78 | 71 | ||
79 | let line_break_range = attr | 72 | let line_break_range = attr |
80 | .syntax() | 73 | .syntax() |
@@ -82,20 +75,30 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> { | |||
82 | .filter(|t| t.kind() == WHITESPACE) | 75 | .filter(|t| t.kind() == WHITESPACE) |
83 | .map(|t| t.text_range()) | 76 | .map(|t| t.text_range()) |
84 | .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0))); | 77 | .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0))); |
85 | edit.delete(line_break_range); | 78 | builder.delete(line_break_range); |
86 | 79 | } | |
87 | attr_range.len() + line_break_range.len() | 80 | |
88 | }; | 81 | match ctx.config.snippet_cap { |
89 | 82 | Some(cap) => { | |
90 | edit.set_cursor(start_offset + TextSize::of(&buf) - cursor_delta); | 83 | builder.insert_snippet( |
91 | buf.push_str("\n}"); | 84 | cap, |
92 | edit.insert(start_offset, buf); | 85 | start_offset, |
86 | format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name), | ||
87 | ); | ||
88 | } | ||
89 | None => { | ||
90 | builder.insert( | ||
91 | start_offset, | ||
92 | format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name), | ||
93 | ); | ||
94 | } | ||
95 | } | ||
93 | }) | 96 | }) |
94 | } | 97 | } |
95 | 98 | ||
96 | #[cfg(test)] | 99 | #[cfg(test)] |
97 | mod tests { | 100 | mod tests { |
98 | use crate::helpers::{check_assist, check_assist_not_applicable}; | 101 | use crate::tests::{check_assist, check_assist_not_applicable}; |
99 | 102 | ||
100 | use super::*; | 103 | use super::*; |
101 | 104 | ||
@@ -115,7 +118,7 @@ struct Foo { | |||
115 | } | 118 | } |
116 | 119 | ||
117 | impl Debug for Foo { | 120 | impl Debug for Foo { |
118 | <|> | 121 | $0 |
119 | } | 122 | } |
120 | ", | 123 | ", |
121 | ) | 124 | ) |
@@ -137,7 +140,7 @@ pub struct Foo { | |||
137 | } | 140 | } |
138 | 141 | ||
139 | impl Debug for Foo { | 142 | impl Debug for Foo { |
140 | <|> | 143 | $0 |
141 | } | 144 | } |
142 | ", | 145 | ", |
143 | ) | 146 | ) |
@@ -156,7 +159,7 @@ struct Foo {} | |||
156 | struct Foo {} | 159 | struct Foo {} |
157 | 160 | ||
158 | impl Debug for Foo { | 161 | impl Debug for Foo { |
159 | <|> | 162 | $0 |
160 | } | 163 | } |
161 | ", | 164 | ", |
162 | ) | 165 | ) |
diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/add_derive.rs index 6254eb7c4..b123b8498 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 | // |
@@ -18,31 +18,37 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
18 | // ``` | 18 | // ``` |
19 | // -> | 19 | // -> |
20 | // ``` | 20 | // ``` |
21 | // #[derive()] | 21 | // #[derive($0)] |
22 | // struct Point { | 22 | // struct Point { |
23 | // x: u32, | 23 | // x: u32, |
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 cap = ctx.config.snippet_cap?; | ||
28 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; | 29 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; |
29 | let node_start = derive_insertion_offset(&nominal)?; | 30 | let node_start = derive_insertion_offset(&nominal)?; |
30 | ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", |edit| { | 31 | let target = nominal.syntax().text_range(); |
32 | acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |builder| { | ||
31 | let derive_attr = nominal | 33 | let derive_attr = nominal |
32 | .attrs() | 34 | .attrs() |
33 | .filter_map(|x| x.as_simple_call()) | 35 | .filter_map(|x| x.as_simple_call()) |
34 | .filter(|(name, _arg)| name == "derive") | 36 | .filter(|(name, _arg)| name == "derive") |
35 | .map(|(_name, arg)| arg) | 37 | .map(|(_name, arg)| arg) |
36 | .next(); | 38 | .next(); |
37 | let offset = match derive_attr { | 39 | match derive_attr { |
38 | None => { | 40 | None => { |
39 | edit.insert(node_start, "#[derive()]\n"); | 41 | builder.insert_snippet(cap, node_start, "#[derive($0)]\n"); |
40 | node_start + TextSize::of("#[derive(") | 42 | } |
43 | Some(tt) => { | ||
44 | // Just move the cursor. | ||
45 | builder.insert_snippet( | ||
46 | cap, | ||
47 | tt.syntax().text_range().end() - TextSize::of(')'), | ||
48 | "$0", | ||
49 | ) | ||
41 | } | 50 | } |
42 | Some(tt) => tt.syntax().text_range().end() - TextSize::of(')'), | ||
43 | }; | 51 | }; |
44 | edit.target(nominal.syntax().text_range()); | ||
45 | edit.set_cursor(offset) | ||
46 | }) | 52 | }) |
47 | } | 53 | } |
48 | 54 | ||
@@ -57,20 +63,21 @@ fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option<TextSize> { | |||
57 | 63 | ||
58 | #[cfg(test)] | 64 | #[cfg(test)] |
59 | mod tests { | 65 | mod tests { |
66 | use crate::tests::{check_assist, check_assist_target}; | ||
67 | |||
60 | use super::*; | 68 | use super::*; |
61 | use crate::helpers::{check_assist, check_assist_target}; | ||
62 | 69 | ||
63 | #[test] | 70 | #[test] |
64 | fn add_derive_new() { | 71 | fn add_derive_new() { |
65 | check_assist( | 72 | check_assist( |
66 | add_derive, | 73 | add_derive, |
67 | "struct Foo { a: i32, <|>}", | 74 | "struct Foo { a: i32, <|>}", |
68 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | 75 | "#[derive($0)]\nstruct Foo { a: i32, }", |
69 | ); | 76 | ); |
70 | check_assist( | 77 | check_assist( |
71 | add_derive, | 78 | add_derive, |
72 | "struct Foo { <|> a: i32, }", | 79 | "struct Foo { <|> a: i32, }", |
73 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | 80 | "#[derive($0)]\nstruct Foo { a: i32, }", |
74 | ); | 81 | ); |
75 | } | 82 | } |
76 | 83 | ||
@@ -79,7 +86,7 @@ mod tests { | |||
79 | check_assist( | 86 | check_assist( |
80 | add_derive, | 87 | add_derive, |
81 | "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", | 88 | "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", |
82 | "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", | 89 | "#[derive(Clone$0)]\nstruct Foo { a: i32, }", |
83 | ); | 90 | ); |
84 | } | 91 | } |
85 | 92 | ||
@@ -95,7 +102,7 @@ struct Foo { a: i32<|>, } | |||
95 | " | 102 | " |
96 | /// `Foo` is a pretty important struct. | 103 | /// `Foo` is a pretty important struct. |
97 | /// It does stuff. | 104 | /// It does stuff. |
98 | #[derive(<|>)] | 105 | #[derive($0)] |
99 | struct Foo { a: i32, } | 106 | struct Foo { a: i32, } |
100 | ", | 107 | ", |
101 | ); | 108 | ); |
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs index bc313782b..ab20c6649 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,12 +21,12 @@ 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 module = ctx.sema.scope(stmt.syntax()).module()?; | ||
26 | let expr = stmt.initializer()?; | 27 | let expr = stmt.initializer()?; |
27 | let pat = stmt.pat()?; | ||
28 | // Must be a binding | 28 | // Must be a binding |
29 | let pat = match pat { | 29 | let pat = match stmt.pat()? { |
30 | ast::Pat::BindPat(bind_pat) => bind_pat, | 30 | ast::Pat::BindPat(bind_pat) => bind_pat, |
31 | _ => return None, | 31 | _ => return None, |
32 | }; | 32 | }; |
@@ -45,7 +45,7 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> { | |||
45 | // Assist not applicable if the type has already been specified | 45 | // Assist not applicable if the type has already been specified |
46 | // and it has no placeholders | 46 | // and it has no placeholders |
47 | let ascribed_ty = stmt.ascribed_type(); | 47 | let ascribed_ty = stmt.ascribed_type(); |
48 | if let Some(ref ty) = ascribed_ty { | 48 | if let Some(ty) = &ascribed_ty { |
49 | if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() { | 49 | if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() { |
50 | return None; | 50 | return None; |
51 | } | 51 | } |
@@ -57,17 +57,17 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> { | |||
57 | return None; | 57 | return None; |
58 | } | 58 | } |
59 | 59 | ||
60 | let db = ctx.db; | 60 | let inferred_type = ty.display_source_code(ctx.db, module.into()).ok()?; |
61 | let new_type_string = ty.display_truncated(db, None).to_string(); | 61 | acc.add( |
62 | ctx.add_assist( | ||
63 | AssistId("add_explicit_type"), | 62 | AssistId("add_explicit_type"), |
64 | format!("Insert explicit type '{}'", new_type_string), | 63 | format!("Insert explicit type `{}`", inferred_type), |
65 | |edit| { | 64 | pat_range, |
66 | edit.target(pat_range); | 65 | |builder| match ascribed_ty { |
67 | if let Some(ascribed_ty) = ascribed_ty { | 66 | Some(ascribed_ty) => { |
68 | edit.replace(ascribed_ty.syntax().text_range(), new_type_string); | 67 | builder.replace(ascribed_ty.syntax().text_range(), inferred_type); |
69 | } else { | 68 | } |
70 | edit.insert(name_range.end(), format!(": {}", new_type_string)); | 69 | None => { |
70 | builder.insert(name_range.end(), format!(": {}", inferred_type)); | ||
71 | } | 71 | } |
72 | }, | 72 | }, |
73 | ) | 73 | ) |
@@ -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() { |
@@ -86,11 +86,7 @@ mod tests { | |||
86 | 86 | ||
87 | #[test] | 87 | #[test] |
88 | fn add_explicit_type_works_for_simple_expr() { | 88 | fn add_explicit_type_works_for_simple_expr() { |
89 | check_assist( | 89 | check_assist(add_explicit_type, "fn f() { let a<|> = 1; }", "fn f() { let a: i32 = 1; }"); |
90 | add_explicit_type, | ||
91 | "fn f() { let a<|> = 1; }", | ||
92 | "fn f() { let a<|>: i32 = 1; }", | ||
93 | ); | ||
94 | } | 90 | } |
95 | 91 | ||
96 | #[test] | 92 | #[test] |
@@ -98,7 +94,7 @@ mod tests { | |||
98 | check_assist( | 94 | check_assist( |
99 | add_explicit_type, | 95 | add_explicit_type, |
100 | "fn f() { let a<|>: _ = 1; }", | 96 | "fn f() { let a<|>: _ = 1; }", |
101 | "fn f() { let a<|>: i32 = 1; }", | 97 | "fn f() { let a: i32 = 1; }", |
102 | ); | 98 | ); |
103 | } | 99 | } |
104 | 100 | ||
@@ -122,7 +118,7 @@ mod tests { | |||
122 | } | 118 | } |
123 | 119 | ||
124 | fn f() { | 120 | fn f() { |
125 | let a<|>: Option<i32> = Option::Some(1); | 121 | let a: Option<i32> = Option::Some(1); |
126 | }"#, | 122 | }"#, |
127 | ); | 123 | ); |
128 | } | 124 | } |
@@ -132,7 +128,7 @@ mod tests { | |||
132 | check_assist( | 128 | check_assist( |
133 | add_explicit_type, | 129 | add_explicit_type, |
134 | r"macro_rules! v { () => {0u64} } fn f() { let a<|> = v!(); }", | 130 | r"macro_rules! v { () => {0u64} } fn f() { let a<|> = v!(); }", |
135 | r"macro_rules! v { () => {0u64} } fn f() { let a<|>: u64 = v!(); }", | 131 | r"macro_rules! v { () => {0u64} } fn f() { let a: u64 = v!(); }", |
136 | ); | 132 | ); |
137 | } | 133 | } |
138 | 134 | ||
@@ -140,8 +136,8 @@ mod tests { | |||
140 | fn add_explicit_type_works_for_macro_call_recursive() { | 136 | fn add_explicit_type_works_for_macro_call_recursive() { |
141 | check_assist( | 137 | check_assist( |
142 | add_explicit_type, | 138 | add_explicit_type, |
143 | "macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|> = v!(); }", | 139 | r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|> = v!(); }"#, |
144 | "macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|>: u64 = v!(); }", | 140 | r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a: u64 = v!(); }"#, |
145 | ); | 141 | ); |
146 | } | 142 | } |
147 | 143 | ||
@@ -208,7 +204,7 @@ struct Test<K, T = u8> { | |||
208 | } | 204 | } |
209 | 205 | ||
210 | fn main() { | 206 | fn main() { |
211 | let test<|>: Test<i32> = Test { t: 23, k: 33 }; | 207 | let test: Test<i32> = Test { t: 23, k: 33 }; |
212 | }"#, | 208 | }"#, |
213 | ); | 209 | ); |
214 | } | 210 | } |
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..6a675e812 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 | |||
@@ -1,12 +1,8 @@ | |||
1 | use ra_ide_db::RootDatabase; | 1 | use ra_ide_db::RootDatabase; |
2 | use ra_syntax::{ | 2 | use ra_syntax::ast::{self, AstNode, NameOwner}; |
3 | ast::{self, AstNode, NameOwner}, | 3 | use test_utils::mark; |
4 | TextSize, | ||
5 | }; | ||
6 | use stdx::format_to; | ||
7 | 4 | ||
8 | use crate::{utils::FamousDefs, Assist, AssistCtx, AssistId}; | 5 | use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; |
9 | use test_utils::tested_by; | ||
10 | 6 | ||
11 | // Assist add_from_impl_for_enum | 7 | // Assist add_from_impl_for_enum |
12 | // | 8 | // |
@@ -25,7 +21,7 @@ use test_utils::tested_by; | |||
25 | // } | 21 | // } |
26 | // } | 22 | // } |
27 | // ``` | 23 | // ``` |
28 | pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> { | 24 | pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
29 | let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?; | 25 | let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?; |
30 | let variant_name = variant.name()?; | 26 | let variant_name = variant.name()?; |
31 | let enum_name = variant.parent_enum().name()?; | 27 | let enum_name = variant.parent_enum().name()?; |
@@ -38,23 +34,23 @@ pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> { | |||
38 | } | 34 | } |
39 | let field_type = field_list.fields().next()?.type_ref()?; | 35 | let field_type = field_list.fields().next()?.type_ref()?; |
40 | let path = match field_type { | 36 | let path = match field_type { |
41 | ast::TypeRef::PathType(p) => p, | 37 | ast::TypeRef::PathType(it) => it, |
42 | _ => return None, | 38 | _ => return None, |
43 | }; | 39 | }; |
44 | 40 | ||
45 | if existing_from_impl(ctx.sema, &variant).is_some() { | 41 | if existing_from_impl(&ctx.sema, &variant).is_some() { |
46 | tested_by!(test_add_from_impl_already_exists); | 42 | mark::hit!(test_add_from_impl_already_exists); |
47 | return None; | 43 | return None; |
48 | } | 44 | } |
49 | 45 | ||
50 | ctx.add_assist( | 46 | let target = variant.syntax().text_range(); |
47 | acc.add( | ||
51 | AssistId("add_from_impl_for_enum"), | 48 | AssistId("add_from_impl_for_enum"), |
52 | "Add From impl for this enum variant", | 49 | "Add From impl for this enum variant", |
50 | target, | ||
53 | |edit| { | 51 | |edit| { |
54 | let start_offset = variant.parent_enum().syntax().text_range().end(); | 52 | let start_offset = variant.parent_enum().syntax().text_range().end(); |
55 | let mut buf = String::new(); | 53 | let buf = format!( |
56 | format_to!( | ||
57 | buf, | ||
58 | r#" | 54 | r#" |
59 | 55 | ||
60 | impl From<{0}> for {1} {{ | 56 | impl From<{0}> for {1} {{ |
@@ -67,7 +63,6 @@ impl From<{0}> for {1} {{ | |||
67 | variant_name | 63 | variant_name |
68 | ); | 64 | ); |
69 | edit.insert(start_offset, buf); | 65 | edit.insert(start_offset, buf); |
70 | edit.set_cursor(start_offset + TextSize::of("\n\n")); | ||
71 | }, | 66 | }, |
72 | ) | 67 | ) |
73 | } | 68 | } |
@@ -95,10 +90,11 @@ fn existing_from_impl( | |||
95 | 90 | ||
96 | #[cfg(test)] | 91 | #[cfg(test)] |
97 | mod tests { | 92 | mod tests { |
98 | use super::*; | 93 | use test_utils::mark; |
94 | |||
95 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
99 | 96 | ||
100 | use crate::helpers::{check_assist, check_assist_not_applicable}; | 97 | use super::*; |
101 | use test_utils::covers; | ||
102 | 98 | ||
103 | #[test] | 99 | #[test] |
104 | fn test_add_from_impl_for_enum() { | 100 | fn test_add_from_impl_for_enum() { |
@@ -107,7 +103,7 @@ mod tests { | |||
107 | "enum A { <|>One(u32) }", | 103 | "enum A { <|>One(u32) }", |
108 | r#"enum A { One(u32) } | 104 | r#"enum A { One(u32) } |
109 | 105 | ||
110 | <|>impl From<u32> for A { | 106 | impl From<u32> for A { |
111 | fn from(v: u32) -> Self { | 107 | fn from(v: u32) -> Self { |
112 | A::One(v) | 108 | A::One(v) |
113 | } | 109 | } |
@@ -119,10 +115,10 @@ mod tests { | |||
119 | fn test_add_from_impl_for_enum_complicated_path() { | 115 | fn test_add_from_impl_for_enum_complicated_path() { |
120 | check_assist( | 116 | check_assist( |
121 | add_from_impl_for_enum, | 117 | add_from_impl_for_enum, |
122 | "enum A { <|>One(foo::bar::baz::Boo) }", | 118 | r#"enum A { <|>One(foo::bar::baz::Boo) }"#, |
123 | r#"enum A { One(foo::bar::baz::Boo) } | 119 | r#"enum A { One(foo::bar::baz::Boo) } |
124 | 120 | ||
125 | <|>impl From<foo::bar::baz::Boo> for A { | 121 | impl From<foo::bar::baz::Boo> for A { |
126 | fn from(v: foo::bar::baz::Boo) -> Self { | 122 | fn from(v: foo::bar::baz::Boo) -> Self { |
127 | A::One(v) | 123 | A::One(v) |
128 | } | 124 | } |
@@ -153,7 +149,7 @@ mod tests { | |||
153 | 149 | ||
154 | #[test] | 150 | #[test] |
155 | fn test_add_from_impl_already_exists() { | 151 | fn test_add_from_impl_already_exists() { |
156 | covers!(test_add_from_impl_already_exists); | 152 | mark::check!(test_add_from_impl_already_exists); |
157 | check_not_applicable( | 153 | check_not_applicable( |
158 | r#" | 154 | r#" |
159 | enum A { <|>One(u32), } | 155 | enum A { <|>One(u32), } |
@@ -184,7 +180,7 @@ pub trait From<T> { | |||
184 | }"#, | 180 | }"#, |
185 | r#"enum A { One(u32), Two(String), } | 181 | r#"enum A { One(u32), Two(String), } |
186 | 182 | ||
187 | <|>impl From<u32> for A { | 183 | impl From<u32> for A { |
188 | fn from(v: u32) -> Self { | 184 | fn from(v: u32) -> Self { |
189 | A::One(v) | 185 | A::One(v) |
190 | } | 186 | } |
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs index 6c7456579..24f931a85 100644 --- a/crates/ra_assists/src/handlers/add_function.rs +++ b/crates/ra_assists/src/handlers/add_function.rs | |||
@@ -1,13 +1,21 @@ | |||
1 | use hir::HirDisplay; | ||
2 | use ra_db::FileId; | ||
1 | use ra_syntax::{ | 3 | use ra_syntax::{ |
2 | ast::{self, AstNode}, | 4 | ast::{ |
5 | self, | ||
6 | edit::{AstNodeEdit, IndentLevel}, | ||
7 | make, ArgListOwner, AstNode, ModuleItemOwner, | ||
8 | }, | ||
3 | SyntaxKind, SyntaxNode, TextSize, | 9 | SyntaxKind, SyntaxNode, TextSize, |
4 | }; | 10 | }; |
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}; | 11 | use rustc_hash::{FxHashMap, FxHashSet}; |
10 | 12 | ||
13 | use crate::{ | ||
14 | assist_config::SnippetCap, | ||
15 | utils::{render_snippet, Cursor}, | ||
16 | AssistContext, AssistId, Assists, | ||
17 | }; | ||
18 | |||
11 | // Assist: add_function | 19 | // Assist: add_function |
12 | // | 20 | // |
13 | // Adds a stub function with a signature matching the function under the cursor. | 21 | // Adds a stub function with a signature matching the function under the cursor. |
@@ -29,11 +37,11 @@ use rustc_hash::{FxHashMap, FxHashSet}; | |||
29 | // } | 37 | // } |
30 | // | 38 | // |
31 | // fn bar(arg: &str, baz: Baz) { | 39 | // fn bar(arg: &str, baz: Baz) { |
32 | // todo!() | 40 | // ${0:todo!()} |
33 | // } | 41 | // } |
34 | // | 42 | // |
35 | // ``` | 43 | // ``` |
36 | pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> { | 44 | pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
37 | let path_expr: ast::PathExpr = ctx.find_node_at_offset()?; | 45 | let path_expr: ast::PathExpr = ctx.find_node_at_offset()?; |
38 | let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; | 46 | let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; |
39 | let path = path_expr.path()?; | 47 | let path = path_expr.path()?; |
@@ -43,36 +51,49 @@ pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> { | |||
43 | return None; | 51 | return None; |
44 | } | 52 | } |
45 | 53 | ||
46 | let target_module = if let Some(qualifier) = path.qualifier() { | 54 | let target_module = match path.qualifier() { |
47 | if let Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) = | 55 | Some(qualifier) => match ctx.sema.resolve_path(&qualifier) { |
48 | ctx.sema.resolve_path(&qualifier) | 56 | Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) => Some(module), |
49 | { | 57 | _ => return None, |
50 | Some(module.definition_source(ctx.sema.db)) | 58 | }, |
51 | } else { | 59 | None => None, |
52 | return None; | ||
53 | } | ||
54 | } else { | ||
55 | None | ||
56 | }; | 60 | }; |
57 | 61 | ||
58 | let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; | 62 | let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; |
59 | 63 | ||
60 | ctx.add_assist(AssistId("add_function"), "Add function", |edit| { | 64 | let target = call.syntax().text_range(); |
61 | edit.target(call.syntax().text_range()); | 65 | acc.add(AssistId("add_function"), "Add function", target, |builder| { |
62 | 66 | let function_template = function_builder.render(); | |
63 | if let Some(function_template) = function_builder.render() { | 67 | builder.set_file(function_template.file); |
64 | edit.set_file(function_template.file); | 68 | let new_fn = function_template.to_string(ctx.config.snippet_cap); |
65 | edit.set_cursor(function_template.cursor_offset); | 69 | match ctx.config.snippet_cap { |
66 | edit.insert(function_template.insert_offset, function_template.fn_def.to_string()); | 70 | Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn), |
71 | None => builder.insert(function_template.insert_offset, new_fn), | ||
67 | } | 72 | } |
68 | }) | 73 | }) |
69 | } | 74 | } |
70 | 75 | ||
71 | struct FunctionTemplate { | 76 | struct FunctionTemplate { |
72 | insert_offset: TextSize, | 77 | insert_offset: TextSize, |
73 | cursor_offset: TextSize, | 78 | placeholder_expr: ast::MacroCall, |
74 | fn_def: ast::SourceFile, | 79 | leading_ws: String, |
75 | file: AssistFile, | 80 | fn_def: ast::FnDef, |
81 | trailing_ws: String, | ||
82 | file: FileId, | ||
83 | } | ||
84 | |||
85 | impl FunctionTemplate { | ||
86 | fn to_string(&self, cap: Option<SnippetCap>) -> String { | ||
87 | let f = match cap { | ||
88 | Some(cap) => render_snippet( | ||
89 | cap, | ||
90 | self.fn_def.syntax(), | ||
91 | Cursor::Replace(self.placeholder_expr.syntax()), | ||
92 | ), | ||
93 | None => self.fn_def.to_string(), | ||
94 | }; | ||
95 | format!("{}{}{}", self.leading_ws, f, self.trailing_ws) | ||
96 | } | ||
76 | } | 97 | } |
77 | 98 | ||
78 | struct FunctionBuilder { | 99 | struct FunctionBuilder { |
@@ -80,68 +101,73 @@ struct FunctionBuilder { | |||
80 | fn_name: ast::Name, | 101 | fn_name: ast::Name, |
81 | type_params: Option<ast::TypeParamList>, | 102 | type_params: Option<ast::TypeParamList>, |
82 | params: ast::ParamList, | 103 | params: ast::ParamList, |
83 | file: AssistFile, | 104 | file: FileId, |
84 | needs_pub: bool, | 105 | needs_pub: bool, |
85 | } | 106 | } |
86 | 107 | ||
87 | impl FunctionBuilder { | 108 | impl FunctionBuilder { |
88 | /// Prepares a generated function that matches `call` in `generate_in` | 109 | /// Prepares a generated function that matches `call`. |
89 | /// (or as close to `call` as possible, if `generate_in` is `None`) | 110 | /// The function is generated in `target_module` or next to `call` |
90 | fn from_call( | 111 | fn from_call( |
91 | ctx: &AssistCtx, | 112 | ctx: &AssistContext, |
92 | call: &ast::CallExpr, | 113 | call: &ast::CallExpr, |
93 | path: &ast::Path, | 114 | path: &ast::Path, |
94 | target_module: Option<hir::InFile<hir::ModuleSource>>, | 115 | target_module: Option<hir::Module>, |
95 | ) -> Option<Self> { | 116 | ) -> Option<Self> { |
96 | let needs_pub = target_module.is_some(); | 117 | let mut file = ctx.frange.file_id; |
97 | let mut file = AssistFile::default(); | 118 | let target = match &target_module { |
98 | let target = if let Some(target_module) = target_module { | 119 | Some(target_module) => { |
99 | let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, target_module)?; | 120 | let module_source = target_module.definition_source(ctx.db); |
100 | file = in_file; | 121 | let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?; |
101 | target | 122 | file = in_file; |
102 | } else { | 123 | target |
103 | next_space_for_fn_after_call_site(&call)? | 124 | } |
125 | None => next_space_for_fn_after_call_site(&call)?, | ||
104 | }; | 126 | }; |
127 | let needs_pub = target_module.is_some(); | ||
128 | let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).module())?; | ||
105 | let fn_name = fn_name(&path)?; | 129 | let fn_name = fn_name(&path)?; |
106 | let (type_params, params) = fn_args(ctx, &call)?; | 130 | let (type_params, params) = fn_args(ctx, target_module, &call)?; |
131 | |||
107 | Some(Self { target, fn_name, type_params, params, file, needs_pub }) | 132 | Some(Self { target, fn_name, type_params, params, file, needs_pub }) |
108 | } | 133 | } |
109 | 134 | ||
110 | fn render(self) -> Option<FunctionTemplate> { | 135 | fn render(self) -> FunctionTemplate { |
111 | let placeholder_expr = ast::make::expr_todo(); | 136 | let placeholder_expr = make::expr_todo(); |
112 | let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr)); | 137 | let fn_body = 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); | 138 | let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None }; |
114 | if self.needs_pub { | 139 | let mut fn_def = |
115 | fn_def = ast::make::add_pub_crate_modifier(fn_def); | 140 | make::fn_def(visibility, self.fn_name, self.type_params, self.params, fn_body); |
116 | } | 141 | let leading_ws; |
142 | let trailing_ws; | ||
117 | 143 | ||
118 | let (fn_def, insert_offset) = match self.target { | 144 | let insert_offset = match self.target { |
119 | GeneratedFunctionTarget::BehindItem(it) => { | 145 | GeneratedFunctionTarget::BehindItem(it) => { |
120 | let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def); | 146 | let indent = IndentLevel::from_node(&it); |
121 | let indented = IndentLevel::from_node(&it).increase_indent(with_leading_blank_line); | 147 | leading_ws = format!("\n\n{}", indent); |
122 | (indented, it.text_range().end()) | 148 | fn_def = fn_def.indent(indent); |
149 | trailing_ws = String::new(); | ||
150 | it.text_range().end() | ||
123 | } | 151 | } |
124 | GeneratedFunctionTarget::InEmptyItemList(it) => { | 152 | GeneratedFunctionTarget::InEmptyItemList(it) => { |
125 | let indent_once = IndentLevel(1); | ||
126 | let indent = IndentLevel::from_node(it.syntax()); | 153 | let indent = IndentLevel::from_node(it.syntax()); |
127 | 154 | leading_ws = format!("\n{}", indent + 1); | |
128 | let fn_def = ast::make::add_leading_newlines(1, fn_def); | 155 | fn_def = fn_def.indent(indent + 1); |
129 | let fn_def = indent_once.increase_indent(fn_def); | 156 | trailing_ws = format!("\n{}", indent); |
130 | let fn_def = ast::make::add_trailing_newlines(1, fn_def); | 157 | it.syntax().text_range().start() + TextSize::of('{') |
131 | let fn_def = indent.increase_indent(fn_def); | ||
132 | (fn_def, it.syntax().text_range().start() + TextSize::of('{')) | ||
133 | } | 158 | } |
134 | }; | 159 | }; |
135 | 160 | ||
136 | let cursor_offset_from_fn_start = fn_def | 161 | let placeholder_expr = |
137 | .syntax() | 162 | fn_def.syntax().descendants().find_map(ast::MacroCall::cast).unwrap(); |
138 | .descendants() | 163 | FunctionTemplate { |
139 | .find_map(ast::MacroCall::cast)? | 164 | insert_offset, |
140 | .syntax() | 165 | placeholder_expr, |
141 | .text_range() | 166 | leading_ws, |
142 | .start(); | 167 | fn_def, |
143 | let cursor_offset = insert_offset + cursor_offset_from_fn_start; | 168 | trailing_ws, |
144 | Some(FunctionTemplate { insert_offset, cursor_offset, fn_def, file: self.file }) | 169 | file: self.file, |
170 | } | ||
145 | } | 171 | } |
146 | } | 172 | } |
147 | 173 | ||
@@ -150,32 +176,41 @@ enum GeneratedFunctionTarget { | |||
150 | InEmptyItemList(ast::ItemList), | 176 | InEmptyItemList(ast::ItemList), |
151 | } | 177 | } |
152 | 178 | ||
179 | impl GeneratedFunctionTarget { | ||
180 | fn syntax(&self) -> &SyntaxNode { | ||
181 | match self { | ||
182 | GeneratedFunctionTarget::BehindItem(it) => it, | ||
183 | GeneratedFunctionTarget::InEmptyItemList(it) => it.syntax(), | ||
184 | } | ||
185 | } | ||
186 | } | ||
187 | |||
153 | fn fn_name(call: &ast::Path) -> Option<ast::Name> { | 188 | fn fn_name(call: &ast::Path) -> Option<ast::Name> { |
154 | let name = call.segment()?.syntax().to_string(); | 189 | let name = call.segment()?.syntax().to_string(); |
155 | Some(ast::make::name(&name)) | 190 | Some(make::name(&name)) |
156 | } | 191 | } |
157 | 192 | ||
158 | /// Computes the type variables and arguments required for the generated function | 193 | /// Computes the type variables and arguments required for the generated function |
159 | fn fn_args( | 194 | fn fn_args( |
160 | ctx: &AssistCtx, | 195 | ctx: &AssistContext, |
196 | target_module: hir::Module, | ||
161 | call: &ast::CallExpr, | 197 | call: &ast::CallExpr, |
162 | ) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { | 198 | ) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { |
163 | let mut arg_names = Vec::new(); | 199 | let mut arg_names = Vec::new(); |
164 | let mut arg_types = Vec::new(); | 200 | let mut arg_types = Vec::new(); |
165 | for arg in call.arg_list()?.args() { | 201 | for arg in call.arg_list()?.args() { |
166 | let arg_name = match fn_arg_name(&arg) { | 202 | arg_names.push(match fn_arg_name(&arg) { |
167 | Some(name) => name, | 203 | Some(name) => name, |
168 | None => String::from("arg"), | 204 | None => String::from("arg"), |
169 | }; | 205 | }); |
170 | arg_names.push(arg_name); | 206 | arg_types.push(match fn_arg_type(ctx, target_module, &arg) { |
171 | arg_types.push(match fn_arg_type(ctx, &arg) { | ||
172 | Some(ty) => ty, | 207 | Some(ty) => ty, |
173 | None => String::from("()"), | 208 | None => String::from("()"), |
174 | }); | 209 | }); |
175 | } | 210 | } |
176 | deduplicate_arg_names(&mut arg_names); | 211 | deduplicate_arg_names(&mut arg_names); |
177 | let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| ast::make::param(name, ty)); | 212 | let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| make::param(name, ty)); |
178 | Some((None, ast::make::param_list(params))) | 213 | Some((None, make::param_list(params))) |
179 | } | 214 | } |
180 | 215 | ||
181 | /// Makes duplicate argument names unique by appending incrementing numbers. | 216 | /// Makes duplicate argument names unique by appending incrementing numbers. |
@@ -224,12 +259,21 @@ fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> { | |||
224 | } | 259 | } |
225 | } | 260 | } |
226 | 261 | ||
227 | fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option<String> { | 262 | fn fn_arg_type( |
263 | ctx: &AssistContext, | ||
264 | target_module: hir::Module, | ||
265 | fn_arg: &ast::Expr, | ||
266 | ) -> Option<String> { | ||
228 | let ty = ctx.sema.type_of_expr(fn_arg)?; | 267 | let ty = ctx.sema.type_of_expr(fn_arg)?; |
229 | if ty.is_unknown() { | 268 | if ty.is_unknown() { |
230 | return None; | 269 | return None; |
231 | } | 270 | } |
232 | Some(ty.display(ctx.sema.db).to_string()) | 271 | |
272 | if let Ok(rendered) = ty.display_source_code(ctx.db, target_module.into()) { | ||
273 | Some(rendered) | ||
274 | } else { | ||
275 | None | ||
276 | } | ||
233 | } | 277 | } |
234 | 278 | ||
235 | /// Returns the position inside the current mod or file | 279 | /// Returns the position inside the current mod or file |
@@ -258,11 +302,10 @@ fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFu | |||
258 | 302 | ||
259 | fn next_space_for_fn_in_module( | 303 | fn next_space_for_fn_in_module( |
260 | db: &dyn hir::db::AstDatabase, | 304 | db: &dyn hir::db::AstDatabase, |
261 | module: hir::InFile<hir::ModuleSource>, | 305 | module_source: &hir::InFile<hir::ModuleSource>, |
262 | ) -> Option<(AssistFile, GeneratedFunctionTarget)> { | 306 | ) -> Option<(FileId, GeneratedFunctionTarget)> { |
263 | let file = module.file_id.original_file(db); | 307 | let file = module_source.file_id.original_file(db); |
264 | let assist_file = AssistFile::TargetFile(file); | 308 | let assist_item = match &module_source.value { |
265 | let assist_item = match module.value { | ||
266 | hir::ModuleSource::SourceFile(it) => { | 309 | hir::ModuleSource::SourceFile(it) => { |
267 | if let Some(last_item) = it.items().last() { | 310 | if let Some(last_item) = it.items().last() { |
268 | GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) | 311 | GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) |
@@ -278,12 +321,12 @@ fn next_space_for_fn_in_module( | |||
278 | } | 321 | } |
279 | } | 322 | } |
280 | }; | 323 | }; |
281 | Some((assist_file, assist_item)) | 324 | Some((file, assist_item)) |
282 | } | 325 | } |
283 | 326 | ||
284 | #[cfg(test)] | 327 | #[cfg(test)] |
285 | mod tests { | 328 | mod tests { |
286 | use crate::helpers::{check_assist, check_assist_not_applicable}; | 329 | use crate::tests::{check_assist, check_assist_not_applicable}; |
287 | 330 | ||
288 | use super::*; | 331 | use super::*; |
289 | 332 | ||
@@ -302,7 +345,7 @@ fn foo() { | |||
302 | } | 345 | } |
303 | 346 | ||
304 | fn bar() { | 347 | fn bar() { |
305 | <|>todo!() | 348 | ${0:todo!()} |
306 | } | 349 | } |
307 | ", | 350 | ", |
308 | ) | 351 | ) |
@@ -329,7 +372,7 @@ impl Foo { | |||
329 | } | 372 | } |
330 | 373 | ||
331 | fn bar() { | 374 | fn bar() { |
332 | <|>todo!() | 375 | ${0:todo!()} |
333 | } | 376 | } |
334 | ", | 377 | ", |
335 | ) | 378 | ) |
@@ -353,7 +396,7 @@ fn foo1() { | |||
353 | } | 396 | } |
354 | 397 | ||
355 | fn bar() { | 398 | fn bar() { |
356 | <|>todo!() | 399 | ${0:todo!()} |
357 | } | 400 | } |
358 | 401 | ||
359 | fn foo2() {} | 402 | fn foo2() {} |
@@ -379,7 +422,7 @@ mod baz { | |||
379 | } | 422 | } |
380 | 423 | ||
381 | fn bar() { | 424 | fn bar() { |
382 | <|>todo!() | 425 | ${0:todo!()} |
383 | } | 426 | } |
384 | } | 427 | } |
385 | ", | 428 | ", |
@@ -405,7 +448,7 @@ fn foo() { | |||
405 | } | 448 | } |
406 | 449 | ||
407 | fn bar(baz: Baz) { | 450 | fn bar(baz: Baz) { |
408 | <|>todo!() | 451 | ${0:todo!()} |
409 | } | 452 | } |
410 | ", | 453 | ", |
411 | ); | 454 | ); |
@@ -438,7 +481,7 @@ impl Baz { | |||
438 | } | 481 | } |
439 | 482 | ||
440 | fn bar(baz: Baz) { | 483 | fn bar(baz: Baz) { |
441 | <|>todo!() | 484 | ${0:todo!()} |
442 | } | 485 | } |
443 | ", | 486 | ", |
444 | ) | 487 | ) |
@@ -459,7 +502,7 @@ fn foo() { | |||
459 | } | 502 | } |
460 | 503 | ||
461 | fn bar(arg: &str) { | 504 | fn bar(arg: &str) { |
462 | <|>todo!() | 505 | ${0:todo!()} |
463 | } | 506 | } |
464 | "#, | 507 | "#, |
465 | ) | 508 | ) |
@@ -480,7 +523,7 @@ fn foo() { | |||
480 | } | 523 | } |
481 | 524 | ||
482 | fn bar(arg: char) { | 525 | fn bar(arg: char) { |
483 | <|>todo!() | 526 | ${0:todo!()} |
484 | } | 527 | } |
485 | "#, | 528 | "#, |
486 | ) | 529 | ) |
@@ -501,7 +544,7 @@ fn foo() { | |||
501 | } | 544 | } |
502 | 545 | ||
503 | fn bar(arg: i32) { | 546 | fn bar(arg: i32) { |
504 | <|>todo!() | 547 | ${0:todo!()} |
505 | } | 548 | } |
506 | ", | 549 | ", |
507 | ) | 550 | ) |
@@ -522,7 +565,7 @@ fn foo() { | |||
522 | } | 565 | } |
523 | 566 | ||
524 | fn bar(arg: u8) { | 567 | fn bar(arg: u8) { |
525 | <|>todo!() | 568 | ${0:todo!()} |
526 | } | 569 | } |
527 | ", | 570 | ", |
528 | ) | 571 | ) |
@@ -547,7 +590,7 @@ fn foo() { | |||
547 | } | 590 | } |
548 | 591 | ||
549 | fn bar(x: u8) { | 592 | fn bar(x: u8) { |
550 | <|>todo!() | 593 | ${0:todo!()} |
551 | } | 594 | } |
552 | ", | 595 | ", |
553 | ) | 596 | ) |
@@ -570,7 +613,7 @@ fn foo() { | |||
570 | } | 613 | } |
571 | 614 | ||
572 | fn bar(worble: ()) { | 615 | fn bar(worble: ()) { |
573 | <|>todo!() | 616 | ${0:todo!()} |
574 | } | 617 | } |
575 | ", | 618 | ", |
576 | ) | 619 | ) |
@@ -599,15 +642,40 @@ fn baz() { | |||
599 | } | 642 | } |
600 | 643 | ||
601 | fn bar(foo: impl Foo) { | 644 | fn bar(foo: impl Foo) { |
602 | <|>todo!() | 645 | ${0:todo!()} |
646 | } | ||
647 | ", | ||
648 | ) | ||
649 | } | ||
650 | |||
651 | #[test] | ||
652 | fn borrowed_arg() { | ||
653 | check_assist( | ||
654 | add_function, | ||
655 | r" | ||
656 | struct Baz; | ||
657 | fn baz() -> Baz { todo!() } | ||
658 | |||
659 | fn foo() { | ||
660 | bar<|>(&baz()) | ||
661 | } | ||
662 | ", | ||
663 | r" | ||
664 | struct Baz; | ||
665 | fn baz() -> Baz { todo!() } | ||
666 | |||
667 | fn foo() { | ||
668 | bar(&baz()) | ||
669 | } | ||
670 | |||
671 | fn bar(baz: &Baz) { | ||
672 | ${0:todo!()} | ||
603 | } | 673 | } |
604 | ", | 674 | ", |
605 | ) | 675 | ) |
606 | } | 676 | } |
607 | 677 | ||
608 | #[test] | 678 | #[test] |
609 | #[ignore] | ||
610 | // FIXME print paths properly to make this test pass | ||
611 | fn add_function_with_qualified_path_arg() { | 679 | fn add_function_with_qualified_path_arg() { |
612 | check_assist( | 680 | check_assist( |
613 | add_function, | 681 | add_function, |
@@ -616,10 +684,8 @@ mod Baz { | |||
616 | pub struct Bof; | 684 | pub struct Bof; |
617 | pub fn baz() -> Bof { Bof } | 685 | pub fn baz() -> Bof { Bof } |
618 | } | 686 | } |
619 | mod Foo { | 687 | fn foo() { |
620 | fn foo() { | 688 | <|>bar(Baz::baz()) |
621 | <|>bar(super::Baz::baz()) | ||
622 | } | ||
623 | } | 689 | } |
624 | ", | 690 | ", |
625 | r" | 691 | r" |
@@ -627,14 +693,12 @@ mod Baz { | |||
627 | pub struct Bof; | 693 | pub struct Bof; |
628 | pub fn baz() -> Bof { Bof } | 694 | pub fn baz() -> Bof { Bof } |
629 | } | 695 | } |
630 | mod Foo { | 696 | fn foo() { |
631 | fn foo() { | 697 | bar(Baz::baz()) |
632 | bar(super::Baz::baz()) | 698 | } |
633 | } | ||
634 | 699 | ||
635 | fn bar(baz: super::Baz::Bof) { | 700 | fn bar(baz: Baz::Bof) { |
636 | <|>todo!() | 701 | ${0:todo!()} |
637 | } | ||
638 | } | 702 | } |
639 | ", | 703 | ", |
640 | ) | 704 | ) |
@@ -657,7 +721,7 @@ fn foo<T>(t: T) { | |||
657 | } | 721 | } |
658 | 722 | ||
659 | fn bar<T>(t: T) { | 723 | fn bar<T>(t: T) { |
660 | <|>todo!() | 724 | ${0:todo!()} |
661 | } | 725 | } |
662 | ", | 726 | ", |
663 | ) | 727 | ) |
@@ -688,7 +752,7 @@ fn foo() { | |||
688 | } | 752 | } |
689 | 753 | ||
690 | fn bar(arg: fn() -> Baz) { | 754 | fn bar(arg: fn() -> Baz) { |
691 | <|>todo!() | 755 | ${0:todo!()} |
692 | } | 756 | } |
693 | ", | 757 | ", |
694 | ) | 758 | ) |
@@ -713,7 +777,7 @@ fn foo() { | |||
713 | } | 777 | } |
714 | 778 | ||
715 | fn bar(closure: impl Fn(i64) -> i64) { | 779 | fn bar(closure: impl Fn(i64) -> i64) { |
716 | <|>todo!() | 780 | ${0:todo!()} |
717 | } | 781 | } |
718 | ", | 782 | ", |
719 | ) | 783 | ) |
@@ -734,7 +798,7 @@ fn foo() { | |||
734 | } | 798 | } |
735 | 799 | ||
736 | fn bar(baz: ()) { | 800 | fn bar(baz: ()) { |
737 | <|>todo!() | 801 | ${0:todo!()} |
738 | } | 802 | } |
739 | ", | 803 | ", |
740 | ) | 804 | ) |
@@ -759,7 +823,7 @@ fn foo() { | |||
759 | } | 823 | } |
760 | 824 | ||
761 | fn bar(baz_1: Baz, baz_2: Baz) { | 825 | fn bar(baz_1: Baz, baz_2: Baz) { |
762 | <|>todo!() | 826 | ${0:todo!()} |
763 | } | 827 | } |
764 | ", | 828 | ", |
765 | ) | 829 | ) |
@@ -784,7 +848,7 @@ fn foo() { | |||
784 | } | 848 | } |
785 | 849 | ||
786 | fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) { | 850 | fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) { |
787 | <|>todo!() | 851 | ${0:todo!()} |
788 | } | 852 | } |
789 | "#, | 853 | "#, |
790 | ) | 854 | ) |
@@ -804,7 +868,7 @@ fn foo() { | |||
804 | r" | 868 | r" |
805 | mod bar { | 869 | mod bar { |
806 | pub(crate) fn my_fn() { | 870 | pub(crate) fn my_fn() { |
807 | <|>todo!() | 871 | ${0:todo!()} |
808 | } | 872 | } |
809 | } | 873 | } |
810 | 874 | ||
@@ -816,6 +880,40 @@ fn foo() { | |||
816 | } | 880 | } |
817 | 881 | ||
818 | #[test] | 882 | #[test] |
883 | #[ignore] | ||
884 | // Ignored until local imports are supported. | ||
885 | // See https://github.com/rust-analyzer/rust-analyzer/issues/1165 | ||
886 | fn qualified_path_uses_correct_scope() { | ||
887 | check_assist( | ||
888 | add_function, | ||
889 | " | ||
890 | mod foo { | ||
891 | pub struct Foo; | ||
892 | } | ||
893 | fn bar() { | ||
894 | use foo::Foo; | ||
895 | let foo = Foo; | ||
896 | baz<|>(foo) | ||
897 | } | ||
898 | ", | ||
899 | " | ||
900 | mod foo { | ||
901 | pub struct Foo; | ||
902 | } | ||
903 | fn bar() { | ||
904 | use foo::Foo; | ||
905 | let foo = Foo; | ||
906 | baz(foo) | ||
907 | } | ||
908 | |||
909 | fn baz(foo: foo::Foo) { | ||
910 | ${0:todo!()} | ||
911 | } | ||
912 | ", | ||
913 | ) | ||
914 | } | ||
915 | |||
916 | #[test] | ||
819 | fn add_function_in_module_containing_other_items() { | 917 | fn add_function_in_module_containing_other_items() { |
820 | check_assist( | 918 | check_assist( |
821 | add_function, | 919 | add_function, |
@@ -833,7 +931,7 @@ mod bar { | |||
833 | fn something_else() {} | 931 | fn something_else() {} |
834 | 932 | ||
835 | pub(crate) fn my_fn() { | 933 | pub(crate) fn my_fn() { |
836 | <|>todo!() | 934 | ${0:todo!()} |
837 | } | 935 | } |
838 | } | 936 | } |
839 | 937 | ||
@@ -861,7 +959,7 @@ fn foo() { | |||
861 | mod bar { | 959 | mod bar { |
862 | mod baz { | 960 | mod baz { |
863 | pub(crate) fn my_fn() { | 961 | pub(crate) fn my_fn() { |
864 | <|>todo!() | 962 | ${0:todo!()} |
865 | } | 963 | } |
866 | } | 964 | } |
867 | } | 965 | } |
@@ -890,7 +988,7 @@ fn main() { | |||
890 | 988 | ||
891 | 989 | ||
892 | pub(crate) fn bar() { | 990 | pub(crate) fn bar() { |
893 | <|>todo!() | 991 | ${0:todo!()} |
894 | }", | 992 | }", |
895 | ) | 993 | ) |
896 | } | 994 | } |
@@ -927,21 +1025,6 @@ fn bar(baz: ()) {} | |||
927 | } | 1025 | } |
928 | 1026 | ||
929 | #[test] | 1027 | #[test] |
930 | fn add_function_not_applicable_if_function_path_not_singleton() { | ||
931 | // In the future this assist could be extended to generate functions | ||
932 | // if the path is in the same crate (or even the same workspace). | ||
933 | // For the beginning, I think this is fine. | ||
934 | check_assist_not_applicable( | ||
935 | add_function, | ||
936 | r" | ||
937 | fn foo() { | ||
938 | other_crate::bar<|>(); | ||
939 | } | ||
940 | ", | ||
941 | ) | ||
942 | } | ||
943 | |||
944 | #[test] | ||
945 | #[ignore] | 1028 | #[ignore] |
946 | fn create_method_with_no_args() { | 1029 | fn create_method_with_no_args() { |
947 | check_assist( | 1030 | check_assist( |
diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs index d26f8b93d..eceba7d0a 100644 --- a/crates/ra_assists/src/handlers/add_impl.rs +++ b/crates/ra_assists/src/handlers/add_impl.rs | |||
@@ -1,10 +1,7 @@ | |||
1 | use ra_syntax::{ | 1 | use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner}; |
2 | ast::{self, AstNode, NameOwner, TypeParamsOwner}, | ||
3 | TextSize, | ||
4 | }; | ||
5 | use stdx::{format_to, SepBy}; | 2 | use stdx::{format_to, SepBy}; |
6 | 3 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 4 | use crate::{AssistContext, AssistId, Assists}; |
8 | 5 | ||
9 | // Assist: add_impl | 6 | // Assist: add_impl |
10 | // | 7 | // |
@@ -12,24 +9,24 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
12 | // | 9 | // |
13 | // ``` | 10 | // ``` |
14 | // struct Ctx<T: Clone> { | 11 | // struct Ctx<T: Clone> { |
15 | // data: T,<|> | 12 | // data: T,<|> |
16 | // } | 13 | // } |
17 | // ``` | 14 | // ``` |
18 | // -> | 15 | // -> |
19 | // ``` | 16 | // ``` |
20 | // struct Ctx<T: Clone> { | 17 | // struct Ctx<T: Clone> { |
21 | // data: T, | 18 | // data: T, |
22 | // } | 19 | // } |
23 | // | 20 | // |
24 | // impl<T: Clone> Ctx<T> { | 21 | // impl<T: Clone> Ctx<T> { |
25 | // | 22 | // $0 |
26 | // } | 23 | // } |
27 | // ``` | 24 | // ``` |
28 | pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> { | 25 | pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
29 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; | 26 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; |
30 | let name = nominal.name()?; | 27 | let name = nominal.name()?; |
31 | ctx.add_assist(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), |edit| { | 28 | let target = nominal.syntax().text_range(); |
32 | edit.target(nominal.syntax().text_range()); | 29 | acc.add(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), target, |edit| { |
33 | let type_params = nominal.type_param_list(); | 30 | let type_params = nominal.type_param_list(); |
34 | let start_offset = nominal.syntax().text_range().end(); | 31 | let start_offset = nominal.syntax().text_range().end(); |
35 | let mut buf = String::new(); | 32 | let mut buf = String::new(); |
@@ -50,30 +47,37 @@ pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> { | |||
50 | let generic_params = lifetime_params.chain(type_params).sep_by(", "); | 47 | let generic_params = lifetime_params.chain(type_params).sep_by(", "); |
51 | format_to!(buf, "<{}>", generic_params) | 48 | format_to!(buf, "<{}>", generic_params) |
52 | } | 49 | } |
53 | buf.push_str(" {\n"); | 50 | match ctx.config.snippet_cap { |
54 | edit.set_cursor(start_offset + TextSize::of(&buf)); | 51 | Some(cap) => { |
55 | buf.push_str("\n}"); | 52 | buf.push_str(" {\n $0\n}"); |
56 | edit.insert(start_offset, buf); | 53 | edit.insert_snippet(cap, start_offset, buf); |
54 | } | ||
55 | None => { | ||
56 | buf.push_str(" {\n}"); | ||
57 | edit.insert(start_offset, buf); | ||
58 | } | ||
59 | } | ||
57 | }) | 60 | }) |
58 | } | 61 | } |
59 | 62 | ||
60 | #[cfg(test)] | 63 | #[cfg(test)] |
61 | mod tests { | 64 | mod tests { |
65 | use crate::tests::{check_assist, check_assist_target}; | ||
66 | |||
62 | use super::*; | 67 | use super::*; |
63 | use crate::helpers::{check_assist, check_assist_target}; | ||
64 | 68 | ||
65 | #[test] | 69 | #[test] |
66 | fn test_add_impl() { | 70 | fn test_add_impl() { |
67 | check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n<|>\n}\n"); | 71 | check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n $0\n}\n"); |
68 | check_assist( | 72 | check_assist( |
69 | add_impl, | 73 | add_impl, |
70 | "struct Foo<T: Clone> {<|>}", | 74 | "struct Foo<T: Clone> {<|>}", |
71 | "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}", | 75 | "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}", |
72 | ); | 76 | ); |
73 | check_assist( | 77 | check_assist( |
74 | add_impl, | 78 | add_impl, |
75 | "struct Foo<'a, T: Foo<'a>> {<|>}", | 79 | "struct Foo<'a, T: Foo<'a>> {<|>}", |
76 | "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", | 80 | "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}", |
77 | ); | 81 | ); |
78 | } | 82 | } |
79 | 83 | ||
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 2d6d44980..abacd4065 100644 --- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs | |||
@@ -1,13 +1,18 @@ | |||
1 | use hir::HasSource; | 1 | use hir::HasSource; |
2 | use ra_syntax::{ | 2 | use ra_syntax::{ |
3 | ast::{self, edit, make, AstNode, NameOwner}, | 3 | ast::{ |
4 | self, | ||
5 | edit::{self, AstNodeEdit, IndentLevel}, | ||
6 | make, AstNode, NameOwner, | ||
7 | }, | ||
4 | SmolStr, | 8 | SmolStr, |
5 | }; | 9 | }; |
6 | 10 | ||
7 | use crate::{ | 11 | use crate::{ |
12 | assist_context::{AssistContext, Assists}, | ||
8 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | 13 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, |
9 | utils::{get_missing_impl_items, resolve_target_trait}, | 14 | utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor}, |
10 | Assist, AssistCtx, AssistId, | 15 | AssistId, |
11 | }; | 16 | }; |
12 | 17 | ||
13 | #[derive(PartialEq)] | 18 | #[derive(PartialEq)] |
@@ -40,12 +45,15 @@ enum AddMissingImplMembersMode { | |||
40 | // } | 45 | // } |
41 | // | 46 | // |
42 | // impl Trait<u32> for () { | 47 | // impl Trait<u32> for () { |
43 | // fn foo(&self) -> u32 { todo!() } | 48 | // fn foo(&self) -> u32 { |
49 | // ${0:todo!()} | ||
50 | // } | ||
44 | // | 51 | // |
45 | // } | 52 | // } |
46 | // ``` | 53 | // ``` |
47 | 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<()> { |
48 | add_missing_impl_members_inner( | 55 | add_missing_impl_members_inner( |
56 | acc, | ||
49 | ctx, | 57 | ctx, |
50 | AddMissingImplMembersMode::NoDefaultMethods, | 58 | AddMissingImplMembersMode::NoDefaultMethods, |
51 | "add_impl_missing_members", | 59 | "add_impl_missing_members", |
@@ -81,12 +89,13 @@ pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option<Assist> { | |||
81 | // impl Trait for () { | 89 | // impl Trait for () { |
82 | // Type X = (); | 90 | // Type X = (); |
83 | // fn foo(&self) {} | 91 | // fn foo(&self) {} |
84 | // fn bar(&self) {} | 92 | // $0fn bar(&self) {} |
85 | // | 93 | // |
86 | // } | 94 | // } |
87 | // ``` | 95 | // ``` |
88 | 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<()> { |
89 | add_missing_impl_members_inner( | 97 | add_missing_impl_members_inner( |
98 | acc, | ||
90 | ctx, | 99 | ctx, |
91 | AddMissingImplMembersMode::DefaultMethodsOnly, | 100 | AddMissingImplMembersMode::DefaultMethodsOnly, |
92 | "add_impl_default_members", | 101 | "add_impl_default_members", |
@@ -95,36 +104,37 @@ pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option<Assist> { | |||
95 | } | 104 | } |
96 | 105 | ||
97 | fn add_missing_impl_members_inner( | 106 | fn add_missing_impl_members_inner( |
98 | ctx: AssistCtx, | 107 | acc: &mut Assists, |
108 | ctx: &AssistContext, | ||
99 | mode: AddMissingImplMembersMode, | 109 | mode: AddMissingImplMembersMode, |
100 | assist_id: &'static str, | 110 | assist_id: &'static str, |
101 | label: &'static str, | 111 | label: &'static str, |
102 | ) -> Option<Assist> { | 112 | ) -> Option<()> { |
103 | let _p = ra_prof::profile("add_missing_impl_members_inner"); | 113 | let _p = ra_prof::profile("add_missing_impl_members_inner"); |
104 | let impl_node = ctx.find_node_at_offset::<ast::ImplDef>()?; | 114 | let impl_def = ctx.find_node_at_offset::<ast::ImplDef>()?; |
105 | let impl_item_list = impl_node.item_list()?; | 115 | let impl_item_list = impl_def.item_list()?; |
106 | 116 | ||
107 | let trait_ = resolve_target_trait(&ctx.sema, &impl_node)?; | 117 | let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; |
108 | 118 | ||
109 | let def_name = |item: &ast::ImplItem| -> Option<SmolStr> { | 119 | let def_name = |item: &ast::AssocItem| -> Option<SmolStr> { |
110 | match item { | 120 | match item { |
111 | ast::ImplItem::FnDef(def) => def.name(), | 121 | ast::AssocItem::FnDef(def) => def.name(), |
112 | ast::ImplItem::TypeAliasDef(def) => def.name(), | 122 | ast::AssocItem::TypeAliasDef(def) => def.name(), |
113 | ast::ImplItem::ConstDef(def) => def.name(), | 123 | ast::AssocItem::ConstDef(def) => def.name(), |
114 | } | 124 | } |
115 | .map(|it| it.text().clone()) | 125 | .map(|it| it.text().clone()) |
116 | }; | 126 | }; |
117 | 127 | ||
118 | let missing_items = get_missing_impl_items(&ctx.sema, &impl_node) | 128 | let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def) |
119 | .iter() | 129 | .iter() |
120 | .map(|i| match i { | 130 | .map(|i| match i { |
121 | hir::AssocItem::Function(i) => ast::ImplItem::FnDef(i.source(ctx.db).value), | 131 | hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value), |
122 | hir::AssocItem::TypeAlias(i) => ast::ImplItem::TypeAliasDef(i.source(ctx.db).value), | 132 | hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAliasDef(i.source(ctx.db).value), |
123 | hir::AssocItem::Const(i) => ast::ImplItem::ConstDef(i.source(ctx.db).value), | 133 | hir::AssocItem::Const(i) => ast::AssocItem::ConstDef(i.source(ctx.db).value), |
124 | }) | 134 | }) |
125 | .filter(|t| def_name(&t).is_some()) | 135 | .filter(|t| def_name(&t).is_some()) |
126 | .filter(|t| match t { | 136 | .filter(|t| match t { |
127 | ast::ImplItem::FnDef(def) => match mode { | 137 | ast::AssocItem::FnDef(def) => match mode { |
128 | AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(), | 138 | AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(), |
129 | AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(), | 139 | AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(), |
130 | }, | 140 | }, |
@@ -136,44 +146,59 @@ fn add_missing_impl_members_inner( | |||
136 | return None; | 146 | return None; |
137 | } | 147 | } |
138 | 148 | ||
139 | let sema = ctx.sema; | 149 | let target = impl_def.syntax().text_range(); |
140 | 150 | acc.add(AssistId(assist_id), label, target, |builder| { | |
141 | ctx.add_assist(AssistId(assist_id), label, |edit| { | 151 | let n_existing_items = impl_item_list.assoc_items().count(); |
142 | let n_existing_items = impl_item_list.impl_items().count(); | 152 | let source_scope = ctx.sema.scope_for_def(trait_); |
143 | let source_scope = sema.scope_for_def(trait_); | 153 | let target_scope = ctx.sema.scope(impl_item_list.syntax()); |
144 | let target_scope = sema.scope(impl_item_list.syntax()); | ||
145 | let ast_transform = QualifyPaths::new(&target_scope, &source_scope) | 154 | let ast_transform = QualifyPaths::new(&target_scope, &source_scope) |
146 | .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_node)); | 155 | .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def)); |
147 | let items = missing_items | 156 | let items = missing_items |
148 | .into_iter() | 157 | .into_iter() |
149 | .map(|it| ast_transform::apply(&*ast_transform, it)) | 158 | .map(|it| ast_transform::apply(&*ast_transform, it)) |
150 | .map(|it| match it { | 159 | .map(|it| match it { |
151 | ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)), | 160 | ast::AssocItem::FnDef(def) => ast::AssocItem::FnDef(add_body(def)), |
152 | _ => it, | 161 | _ => it, |
153 | }) | 162 | }) |
154 | .map(|it| edit::remove_attrs_and_docs(&it)); | 163 | .map(|it| edit::remove_attrs_and_docs(&it)); |
155 | let new_impl_item_list = impl_item_list.append_items(items); | 164 | let new_impl_item_list = impl_item_list.append_items(items); |
156 | let cursor_position = { | 165 | let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap(); |
157 | let first_new_item = new_impl_item_list.impl_items().nth(n_existing_items).unwrap(); | 166 | |
158 | first_new_item.syntax().text_range().start() | 167 | let original_range = impl_item_list.syntax().text_range(); |
168 | match ctx.config.snippet_cap { | ||
169 | None => builder.replace(original_range, new_impl_item_list.to_string()), | ||
170 | Some(cap) => { | ||
171 | let mut cursor = Cursor::Before(first_new_item.syntax()); | ||
172 | let placeholder; | ||
173 | if let ast::AssocItem::FnDef(func) = &first_new_item { | ||
174 | if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) { | ||
175 | if m.syntax().text() == "todo!()" { | ||
176 | placeholder = m; | ||
177 | cursor = Cursor::Replace(placeholder.syntax()); | ||
178 | } | ||
179 | } | ||
180 | } | ||
181 | builder.replace_snippet( | ||
182 | cap, | ||
183 | original_range, | ||
184 | render_snippet(cap, new_impl_item_list.syntax(), cursor), | ||
185 | ) | ||
186 | } | ||
159 | }; | 187 | }; |
160 | |||
161 | edit.replace_ast(impl_item_list, new_impl_item_list); | ||
162 | edit.set_cursor(cursor_position); | ||
163 | }) | 188 | }) |
164 | } | 189 | } |
165 | 190 | ||
166 | fn add_body(fn_def: ast::FnDef) -> ast::FnDef { | 191 | fn add_body(fn_def: ast::FnDef) -> ast::FnDef { |
167 | if fn_def.body().is_none() { | 192 | if fn_def.body().is_some() { |
168 | fn_def.with_body(make::block_from_expr(make::expr_todo())) | 193 | return fn_def; |
169 | } else { | ||
170 | fn_def | ||
171 | } | 194 | } |
195 | let body = make::block_expr(None, Some(make::expr_todo())).indent(IndentLevel(1)); | ||
196 | fn_def.with_body(body) | ||
172 | } | 197 | } |
173 | 198 | ||
174 | #[cfg(test)] | 199 | #[cfg(test)] |
175 | mod tests { | 200 | mod tests { |
176 | use crate::helpers::{check_assist, check_assist_not_applicable}; | 201 | use crate::tests::{check_assist, check_assist_not_applicable}; |
177 | 202 | ||
178 | use super::*; | 203 | use super::*; |
179 | 204 | ||
@@ -181,7 +206,7 @@ mod tests { | |||
181 | fn test_add_missing_impl_members() { | 206 | fn test_add_missing_impl_members() { |
182 | check_assist( | 207 | check_assist( |
183 | add_missing_impl_members, | 208 | add_missing_impl_members, |
184 | " | 209 | r#" |
185 | trait Foo { | 210 | trait Foo { |
186 | type Output; | 211 | type Output; |
187 | 212 | ||
@@ -197,8 +222,8 @@ struct S; | |||
197 | impl Foo for S { | 222 | impl Foo for S { |
198 | fn bar(&self) {} | 223 | fn bar(&self) {} |
199 | <|> | 224 | <|> |
200 | }", | 225 | }"#, |
201 | " | 226 | r#" |
202 | trait Foo { | 227 | trait Foo { |
203 | type Output; | 228 | type Output; |
204 | 229 | ||
@@ -213,12 +238,16 @@ struct S; | |||
213 | 238 | ||
214 | impl Foo for S { | 239 | impl Foo for S { |
215 | fn bar(&self) {} | 240 | fn bar(&self) {} |
216 | <|>type Output; | 241 | $0type Output; |
217 | const CONST: usize = 42; | 242 | const CONST: usize = 42; |
218 | fn foo(&self) { todo!() } | 243 | fn foo(&self) { |
219 | fn baz(&self) { todo!() } | 244 | todo!() |
245 | } | ||
246 | fn baz(&self) { | ||
247 | todo!() | ||
248 | } | ||
220 | 249 | ||
221 | }", | 250 | }"#, |
222 | ); | 251 | ); |
223 | } | 252 | } |
224 | 253 | ||
@@ -226,7 +255,7 @@ impl Foo for S { | |||
226 | fn test_copied_overriden_members() { | 255 | fn test_copied_overriden_members() { |
227 | check_assist( | 256 | check_assist( |
228 | add_missing_impl_members, | 257 | add_missing_impl_members, |
229 | " | 258 | r#" |
230 | trait Foo { | 259 | trait Foo { |
231 | fn foo(&self); | 260 | fn foo(&self); |
232 | fn bar(&self) -> bool { true } | 261 | fn bar(&self) -> bool { true } |
@@ -238,8 +267,8 @@ struct S; | |||
238 | impl Foo for S { | 267 | impl Foo for S { |
239 | fn bar(&self) {} | 268 | fn bar(&self) {} |
240 | <|> | 269 | <|> |
241 | }", | 270 | }"#, |
242 | " | 271 | r#" |
243 | trait Foo { | 272 | trait Foo { |
244 | fn foo(&self); | 273 | fn foo(&self); |
245 | fn bar(&self) -> bool { true } | 274 | fn bar(&self) -> bool { true } |
@@ -250,9 +279,11 @@ struct S; | |||
250 | 279 | ||
251 | impl Foo for S { | 280 | impl Foo for S { |
252 | fn bar(&self) {} | 281 | fn bar(&self) {} |
253 | <|>fn foo(&self) { todo!() } | 282 | fn foo(&self) { |
283 | ${0:todo!()} | ||
284 | } | ||
254 | 285 | ||
255 | }", | 286 | }"#, |
256 | ); | 287 | ); |
257 | } | 288 | } |
258 | 289 | ||
@@ -260,16 +291,18 @@ impl Foo for S { | |||
260 | fn test_empty_impl_def() { | 291 | fn test_empty_impl_def() { |
261 | check_assist( | 292 | check_assist( |
262 | add_missing_impl_members, | 293 | add_missing_impl_members, |
263 | " | 294 | r#" |
264 | trait Foo { fn foo(&self); } | 295 | trait Foo { fn foo(&self); } |
265 | struct S; | 296 | struct S; |
266 | impl Foo for S { <|> }", | 297 | impl Foo for S { <|> }"#, |
267 | " | 298 | r#" |
268 | trait Foo { fn foo(&self); } | 299 | trait Foo { fn foo(&self); } |
269 | struct S; | 300 | struct S; |
270 | impl Foo for S { | 301 | impl Foo for S { |
271 | <|>fn foo(&self) { todo!() } | 302 | fn foo(&self) { |
272 | }", | 303 | ${0:todo!()} |
304 | } | ||
305 | }"#, | ||
273 | ); | 306 | ); |
274 | } | 307 | } |
275 | 308 | ||
@@ -277,16 +310,18 @@ impl Foo for S { | |||
277 | fn fill_in_type_params_1() { | 310 | fn fill_in_type_params_1() { |
278 | check_assist( | 311 | check_assist( |
279 | add_missing_impl_members, | 312 | add_missing_impl_members, |
280 | " | 313 | r#" |
281 | trait Foo<T> { fn foo(&self, t: T) -> &T; } | 314 | trait Foo<T> { fn foo(&self, t: T) -> &T; } |
282 | struct S; | 315 | struct S; |
283 | impl Foo<u32> for S { <|> }", | 316 | impl Foo<u32> for S { <|> }"#, |
284 | " | 317 | r#" |
285 | trait Foo<T> { fn foo(&self, t: T) -> &T; } | 318 | trait Foo<T> { fn foo(&self, t: T) -> &T; } |
286 | struct S; | 319 | struct S; |
287 | impl Foo<u32> for S { | 320 | impl Foo<u32> for S { |
288 | <|>fn foo(&self, t: u32) -> &u32 { todo!() } | 321 | fn foo(&self, t: u32) -> &u32 { |
289 | }", | 322 | ${0:todo!()} |
323 | } | ||
324 | }"#, | ||
290 | ); | 325 | ); |
291 | } | 326 | } |
292 | 327 | ||
@@ -294,16 +329,18 @@ impl Foo<u32> for S { | |||
294 | fn fill_in_type_params_2() { | 329 | fn fill_in_type_params_2() { |
295 | check_assist( | 330 | check_assist( |
296 | add_missing_impl_members, | 331 | add_missing_impl_members, |
297 | " | 332 | r#" |
298 | trait Foo<T> { fn foo(&self, t: T) -> &T; } | 333 | trait Foo<T> { fn foo(&self, t: T) -> &T; } |
299 | struct S; | 334 | struct S; |
300 | impl<U> Foo<U> for S { <|> }", | 335 | impl<U> Foo<U> for S { <|> }"#, |
301 | " | 336 | r#" |
302 | trait Foo<T> { fn foo(&self, t: T) -> &T; } | 337 | trait Foo<T> { fn foo(&self, t: T) -> &T; } |
303 | struct S; | 338 | struct S; |
304 | impl<U> Foo<U> for S { | 339 | impl<U> Foo<U> for S { |
305 | <|>fn foo(&self, t: U) -> &U { todo!() } | 340 | fn foo(&self, t: U) -> &U { |
306 | }", | 341 | ${0:todo!()} |
342 | } | ||
343 | }"#, | ||
307 | ); | 344 | ); |
308 | } | 345 | } |
309 | 346 | ||
@@ -311,16 +348,18 @@ impl<U> Foo<U> for S { | |||
311 | fn test_cursor_after_empty_impl_def() { | 348 | fn test_cursor_after_empty_impl_def() { |
312 | check_assist( | 349 | check_assist( |
313 | add_missing_impl_members, | 350 | add_missing_impl_members, |
314 | " | 351 | r#" |
315 | trait Foo { fn foo(&self); } | 352 | trait Foo { fn foo(&self); } |
316 | struct S; | 353 | struct S; |
317 | impl Foo for S {}<|>", | 354 | impl Foo for S {}<|>"#, |
318 | " | 355 | r#" |
319 | trait Foo { fn foo(&self); } | 356 | trait Foo { fn foo(&self); } |
320 | struct S; | 357 | struct S; |
321 | impl Foo for S { | 358 | impl Foo for S { |
322 | <|>fn foo(&self) { todo!() } | 359 | fn foo(&self) { |
323 | }", | 360 | ${0:todo!()} |
361 | } | ||
362 | }"#, | ||
324 | ) | 363 | ) |
325 | } | 364 | } |
326 | 365 | ||
@@ -328,22 +367,24 @@ impl Foo for S { | |||
328 | fn test_qualify_path_1() { | 367 | fn test_qualify_path_1() { |
329 | check_assist( | 368 | check_assist( |
330 | add_missing_impl_members, | 369 | add_missing_impl_members, |
331 | " | 370 | r#" |
332 | mod foo { | 371 | mod foo { |
333 | pub struct Bar; | 372 | pub struct Bar; |
334 | trait Foo { fn foo(&self, bar: Bar); } | 373 | trait Foo { fn foo(&self, bar: Bar); } |
335 | } | 374 | } |
336 | struct S; | 375 | struct S; |
337 | impl foo::Foo for S { <|> }", | 376 | impl foo::Foo for S { <|> }"#, |
338 | " | 377 | r#" |
339 | mod foo { | 378 | mod foo { |
340 | pub struct Bar; | 379 | pub struct Bar; |
341 | trait Foo { fn foo(&self, bar: Bar); } | 380 | trait Foo { fn foo(&self, bar: Bar); } |
342 | } | 381 | } |
343 | struct S; | 382 | struct S; |
344 | impl foo::Foo for S { | 383 | impl foo::Foo for S { |
345 | <|>fn foo(&self, bar: foo::Bar) { todo!() } | 384 | fn foo(&self, bar: foo::Bar) { |
346 | }", | 385 | ${0:todo!()} |
386 | } | ||
387 | }"#, | ||
347 | ); | 388 | ); |
348 | } | 389 | } |
349 | 390 | ||
@@ -351,22 +392,24 @@ impl foo::Foo for S { | |||
351 | fn test_qualify_path_generic() { | 392 | fn test_qualify_path_generic() { |
352 | check_assist( | 393 | check_assist( |
353 | add_missing_impl_members, | 394 | add_missing_impl_members, |
354 | " | 395 | r#" |
355 | mod foo { | 396 | mod foo { |
356 | pub struct Bar<T>; | 397 | pub struct Bar<T>; |
357 | trait Foo { fn foo(&self, bar: Bar<u32>); } | 398 | trait Foo { fn foo(&self, bar: Bar<u32>); } |
358 | } | 399 | } |
359 | struct S; | 400 | struct S; |
360 | impl foo::Foo for S { <|> }", | 401 | impl foo::Foo for S { <|> }"#, |
361 | " | 402 | r#" |
362 | mod foo { | 403 | mod foo { |
363 | pub struct Bar<T>; | 404 | pub struct Bar<T>; |
364 | trait Foo { fn foo(&self, bar: Bar<u32>); } | 405 | trait Foo { fn foo(&self, bar: Bar<u32>); } |
365 | } | 406 | } |
366 | struct S; | 407 | struct S; |
367 | impl foo::Foo for S { | 408 | impl foo::Foo for S { |
368 | <|>fn foo(&self, bar: foo::Bar<u32>) { todo!() } | 409 | fn foo(&self, bar: foo::Bar<u32>) { |
369 | }", | 410 | ${0:todo!()} |
411 | } | ||
412 | }"#, | ||
370 | ); | 413 | ); |
371 | } | 414 | } |
372 | 415 | ||
@@ -374,22 +417,24 @@ impl foo::Foo for S { | |||
374 | fn test_qualify_path_and_substitute_param() { | 417 | fn test_qualify_path_and_substitute_param() { |
375 | check_assist( | 418 | check_assist( |
376 | add_missing_impl_members, | 419 | add_missing_impl_members, |
377 | " | 420 | r#" |
378 | mod foo { | 421 | mod foo { |
379 | pub struct Bar<T>; | 422 | pub struct Bar<T>; |
380 | trait Foo<T> { fn foo(&self, bar: Bar<T>); } | 423 | trait Foo<T> { fn foo(&self, bar: Bar<T>); } |
381 | } | 424 | } |
382 | struct S; | 425 | struct S; |
383 | impl foo::Foo<u32> for S { <|> }", | 426 | impl foo::Foo<u32> for S { <|> }"#, |
384 | " | 427 | r#" |
385 | mod foo { | 428 | mod foo { |
386 | pub struct Bar<T>; | 429 | pub struct Bar<T>; |
387 | trait Foo<T> { fn foo(&self, bar: Bar<T>); } | 430 | trait Foo<T> { fn foo(&self, bar: Bar<T>); } |
388 | } | 431 | } |
389 | struct S; | 432 | struct S; |
390 | impl foo::Foo<u32> for S { | 433 | impl foo::Foo<u32> for S { |
391 | <|>fn foo(&self, bar: foo::Bar<u32>) { todo!() } | 434 | fn foo(&self, bar: foo::Bar<u32>) { |
392 | }", | 435 | ${0:todo!()} |
436 | } | ||
437 | }"#, | ||
393 | ); | 438 | ); |
394 | } | 439 | } |
395 | 440 | ||
@@ -398,15 +443,15 @@ impl foo::Foo<u32> for S { | |||
398 | // when substituting params, the substituted param should not be qualified! | 443 | // when substituting params, the substituted param should not be qualified! |
399 | check_assist( | 444 | check_assist( |
400 | add_missing_impl_members, | 445 | add_missing_impl_members, |
401 | " | 446 | r#" |
402 | mod foo { | 447 | mod foo { |
403 | trait Foo<T> { fn foo(&self, bar: T); } | 448 | trait Foo<T> { fn foo(&self, bar: T); } |
404 | pub struct Param; | 449 | pub struct Param; |
405 | } | 450 | } |
406 | struct Param; | 451 | struct Param; |
407 | struct S; | 452 | struct S; |
408 | impl foo::Foo<Param> for S { <|> }", | 453 | impl foo::Foo<Param> for S { <|> }"#, |
409 | " | 454 | r#" |
410 | mod foo { | 455 | mod foo { |
411 | trait Foo<T> { fn foo(&self, bar: T); } | 456 | trait Foo<T> { fn foo(&self, bar: T); } |
412 | pub struct Param; | 457 | pub struct Param; |
@@ -414,8 +459,10 @@ mod foo { | |||
414 | struct Param; | 459 | struct Param; |
415 | struct S; | 460 | struct S; |
416 | impl foo::Foo<Param> for S { | 461 | impl foo::Foo<Param> for S { |
417 | <|>fn foo(&self, bar: Param) { todo!() } | 462 | fn foo(&self, bar: Param) { |
418 | }", | 463 | ${0:todo!()} |
464 | } | ||
465 | }"#, | ||
419 | ); | 466 | ); |
420 | } | 467 | } |
421 | 468 | ||
@@ -423,15 +470,15 @@ impl foo::Foo<Param> for S { | |||
423 | fn test_qualify_path_associated_item() { | 470 | fn test_qualify_path_associated_item() { |
424 | check_assist( | 471 | check_assist( |
425 | add_missing_impl_members, | 472 | add_missing_impl_members, |
426 | " | 473 | r#" |
427 | mod foo { | 474 | mod foo { |
428 | pub struct Bar<T>; | 475 | pub struct Bar<T>; |
429 | impl Bar<T> { type Assoc = u32; } | 476 | impl Bar<T> { type Assoc = u32; } |
430 | trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); } | 477 | trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); } |
431 | } | 478 | } |
432 | struct S; | 479 | struct S; |
433 | impl foo::Foo for S { <|> }", | 480 | impl foo::Foo for S { <|> }"#, |
434 | " | 481 | r#" |
435 | mod foo { | 482 | mod foo { |
436 | pub struct Bar<T>; | 483 | pub struct Bar<T>; |
437 | impl Bar<T> { type Assoc = u32; } | 484 | impl Bar<T> { type Assoc = u32; } |
@@ -439,8 +486,10 @@ mod foo { | |||
439 | } | 486 | } |
440 | struct S; | 487 | struct S; |
441 | impl foo::Foo for S { | 488 | impl foo::Foo for S { |
442 | <|>fn foo(&self, bar: foo::Bar<u32>::Assoc) { todo!() } | 489 | fn foo(&self, bar: foo::Bar<u32>::Assoc) { |
443 | }", | 490 | ${0:todo!()} |
491 | } | ||
492 | }"#, | ||
444 | ); | 493 | ); |
445 | } | 494 | } |
446 | 495 | ||
@@ -448,15 +497,15 @@ impl foo::Foo for S { | |||
448 | fn test_qualify_path_nested() { | 497 | fn test_qualify_path_nested() { |
449 | check_assist( | 498 | check_assist( |
450 | add_missing_impl_members, | 499 | add_missing_impl_members, |
451 | " | 500 | r#" |
452 | mod foo { | 501 | mod foo { |
453 | pub struct Bar<T>; | 502 | pub struct Bar<T>; |
454 | pub struct Baz; | 503 | pub struct Baz; |
455 | trait Foo { fn foo(&self, bar: Bar<Baz>); } | 504 | trait Foo { fn foo(&self, bar: Bar<Baz>); } |
456 | } | 505 | } |
457 | struct S; | 506 | struct S; |
458 | impl foo::Foo for S { <|> }", | 507 | impl foo::Foo for S { <|> }"#, |
459 | " | 508 | r#" |
460 | mod foo { | 509 | mod foo { |
461 | pub struct Bar<T>; | 510 | pub struct Bar<T>; |
462 | pub struct Baz; | 511 | pub struct Baz; |
@@ -464,8 +513,10 @@ mod foo { | |||
464 | } | 513 | } |
465 | struct S; | 514 | struct S; |
466 | impl foo::Foo for S { | 515 | impl foo::Foo for S { |
467 | <|>fn foo(&self, bar: foo::Bar<foo::Baz>) { todo!() } | 516 | fn foo(&self, bar: foo::Bar<foo::Baz>) { |
468 | }", | 517 | ${0:todo!()} |
518 | } | ||
519 | }"#, | ||
469 | ); | 520 | ); |
470 | } | 521 | } |
471 | 522 | ||
@@ -473,22 +524,24 @@ impl foo::Foo for S { | |||
473 | fn test_qualify_path_fn_trait_notation() { | 524 | fn test_qualify_path_fn_trait_notation() { |
474 | check_assist( | 525 | check_assist( |
475 | add_missing_impl_members, | 526 | add_missing_impl_members, |
476 | " | 527 | r#" |
477 | mod foo { | 528 | mod foo { |
478 | pub trait Fn<Args> { type Output; } | 529 | pub trait Fn<Args> { type Output; } |
479 | trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); } | 530 | trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); } |
480 | } | 531 | } |
481 | struct S; | 532 | struct S; |
482 | impl foo::Foo for S { <|> }", | 533 | impl foo::Foo for S { <|> }"#, |
483 | " | 534 | r#" |
484 | mod foo { | 535 | mod foo { |
485 | pub trait Fn<Args> { type Output; } | 536 | pub trait Fn<Args> { type Output; } |
486 | trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); } | 537 | trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); } |
487 | } | 538 | } |
488 | struct S; | 539 | struct S; |
489 | impl foo::Foo for S { | 540 | impl foo::Foo for S { |
490 | <|>fn foo(&self, bar: dyn Fn(u32) -> i32) { todo!() } | 541 | fn foo(&self, bar: dyn Fn(u32) -> i32) { |
491 | }", | 542 | ${0:todo!()} |
543 | } | ||
544 | }"#, | ||
492 | ); | 545 | ); |
493 | } | 546 | } |
494 | 547 | ||
@@ -496,10 +549,10 @@ impl foo::Foo for S { | |||
496 | fn test_empty_trait() { | 549 | fn test_empty_trait() { |
497 | check_assist_not_applicable( | 550 | check_assist_not_applicable( |
498 | add_missing_impl_members, | 551 | add_missing_impl_members, |
499 | " | 552 | r#" |
500 | trait Foo; | 553 | trait Foo; |
501 | struct S; | 554 | struct S; |
502 | impl Foo for S { <|> }", | 555 | impl Foo for S { <|> }"#, |
503 | ) | 556 | ) |
504 | } | 557 | } |
505 | 558 | ||
@@ -507,13 +560,13 @@ impl Foo for S { <|> }", | |||
507 | fn test_ignore_unnamed_trait_members_and_default_methods() { | 560 | fn test_ignore_unnamed_trait_members_and_default_methods() { |
508 | check_assist_not_applicable( | 561 | check_assist_not_applicable( |
509 | add_missing_impl_members, | 562 | add_missing_impl_members, |
510 | " | 563 | r#" |
511 | trait Foo { | 564 | trait Foo { |
512 | fn (arg: u32); | 565 | fn (arg: u32); |
513 | fn valid(some: u32) -> bool { false } | 566 | fn valid(some: u32) -> bool { false } |
514 | } | 567 | } |
515 | struct S; | 568 | struct S; |
516 | impl Foo for S { <|> }", | 569 | impl Foo for S { <|> }"#, |
517 | ) | 570 | ) |
518 | } | 571 | } |
519 | 572 | ||
@@ -543,8 +596,10 @@ trait Foo { | |||
543 | } | 596 | } |
544 | struct S; | 597 | struct S; |
545 | impl Foo for S { | 598 | impl Foo for S { |
546 | <|>type Output; | 599 | $0type Output; |
547 | fn foo(&self) { todo!() } | 600 | fn foo(&self) { |
601 | todo!() | ||
602 | } | ||
548 | }"#, | 603 | }"#, |
549 | ) | 604 | ) |
550 | } | 605 | } |
@@ -553,7 +608,7 @@ impl Foo for S { | |||
553 | fn test_default_methods() { | 608 | fn test_default_methods() { |
554 | check_assist( | 609 | check_assist( |
555 | add_missing_default_members, | 610 | add_missing_default_members, |
556 | " | 611 | r#" |
557 | trait Foo { | 612 | trait Foo { |
558 | type Output; | 613 | type Output; |
559 | 614 | ||
@@ -563,8 +618,8 @@ trait Foo { | |||
563 | fn foo(some: u32) -> bool; | 618 | fn foo(some: u32) -> bool; |
564 | } | 619 | } |
565 | struct S; | 620 | struct S; |
566 | impl Foo for S { <|> }", | 621 | impl Foo for S { <|> }"#, |
567 | " | 622 | r#" |
568 | trait Foo { | 623 | trait Foo { |
569 | type Output; | 624 | type Output; |
570 | 625 | ||
@@ -575,8 +630,58 @@ trait Foo { | |||
575 | } | 630 | } |
576 | struct S; | 631 | struct S; |
577 | impl Foo for S { | 632 | impl Foo for S { |
578 | <|>fn valid(some: u32) -> bool { false } | 633 | $0fn valid(some: u32) -> bool { false } |
579 | }", | 634 | }"#, |
635 | ) | ||
636 | } | ||
637 | |||
638 | #[test] | ||
639 | fn test_generic_single_default_parameter() { | ||
640 | check_assist( | ||
641 | add_missing_impl_members, | ||
642 | r#" | ||
643 | trait Foo<T = Self> { | ||
644 | fn bar(&self, other: &T); | ||
645 | } | ||
646 | |||
647 | struct S; | ||
648 | impl Foo for S { <|> }"#, | ||
649 | r#" | ||
650 | trait Foo<T = Self> { | ||
651 | fn bar(&self, other: &T); | ||
652 | } | ||
653 | |||
654 | struct S; | ||
655 | impl Foo for S { | ||
656 | fn bar(&self, other: &Self) { | ||
657 | ${0:todo!()} | ||
658 | } | ||
659 | }"#, | ||
660 | ) | ||
661 | } | ||
662 | |||
663 | #[test] | ||
664 | fn test_generic_default_parameter_is_second() { | ||
665 | check_assist( | ||
666 | add_missing_impl_members, | ||
667 | r#" | ||
668 | trait Foo<T1, T2 = Self> { | ||
669 | fn bar(&self, this: &T1, that: &T2); | ||
670 | } | ||
671 | |||
672 | struct S<T>; | ||
673 | impl Foo<T> for S<T> { <|> }"#, | ||
674 | r#" | ||
675 | trait Foo<T1, T2 = Self> { | ||
676 | fn bar(&self, this: &T1, that: &T2); | ||
677 | } | ||
678 | |||
679 | struct S<T>; | ||
680 | impl Foo<T> for S<T> { | ||
681 | fn bar(&self, this: &T, that: &Self) { | ||
682 | ${0:todo!()} | ||
683 | } | ||
684 | }"#, | ||
580 | ) | 685 | ) |
581 | } | 686 | } |
582 | } | 687 | } |
diff --git a/crates/ra_assists/src/handlers/add_new.rs b/crates/ra_assists/src/handlers/add_new.rs index 0f9174a29..837aa8377 100644 --- a/crates/ra_assists/src/handlers/add_new.rs +++ b/crates/ra_assists/src/handlers/add_new.rs | |||
@@ -3,11 +3,11 @@ use ra_syntax::{ | |||
3 | ast::{ | 3 | ast::{ |
4 | self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner, | 4 | self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner, |
5 | }, | 5 | }, |
6 | TextSize, T, | 6 | T, |
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 | // |
@@ -25,11 +25,11 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
25 | // } | 25 | // } |
26 | // | 26 | // |
27 | // impl<T: Clone> Ctx<T> { | 27 | // impl<T: Clone> Ctx<T> { |
28 | // fn new(data: T) -> Self { Self { data } } | 28 | // fn $0new(data: T) -> Self { Self { data } } |
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,33 +41,27 @@ 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, |builder| { |
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() { |
50 | buf.push('\n'); | 49 | buf.push('\n'); |
51 | } | 50 | } |
52 | 51 | ||
53 | let vis = strukt.visibility().map(|v| format!("{} ", v)); | 52 | let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v)); |
54 | let vis = vis.as_deref().unwrap_or(""); | ||
55 | 53 | ||
56 | let params = field_list | 54 | let params = field_list |
57 | .fields() | 55 | .fields() |
58 | .filter_map(|f| { | 56 | .filter_map(|f| { |
59 | Some(format!( | 57 | Some(format!("{}: {}", f.name()?.syntax(), f.ascribed_type()?.syntax())) |
60 | "{}: {}", | ||
61 | f.name()?.syntax().text(), | ||
62 | f.ascribed_type()?.syntax().text() | ||
63 | )) | ||
64 | }) | 58 | }) |
65 | .sep_by(", "); | 59 | .sep_by(", "); |
66 | let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", "); | 60 | let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", "); |
67 | 61 | ||
68 | format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields); | 62 | format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields); |
69 | 63 | ||
70 | let (start_offset, end_offset) = impl_def | 64 | let start_offset = impl_def |
71 | .and_then(|impl_def| { | 65 | .and_then(|impl_def| { |
72 | buf.push('\n'); | 66 | buf.push('\n'); |
73 | let start = impl_def | 67 | let start = impl_def |
@@ -77,17 +71,20 @@ pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> { | |||
77 | .text_range() | 71 | .text_range() |
78 | .end(); | 72 | .end(); |
79 | 73 | ||
80 | Some((start, TextSize::of("\n"))) | 74 | Some(start) |
81 | }) | 75 | }) |
82 | .unwrap_or_else(|| { | 76 | .unwrap_or_else(|| { |
83 | buf = generate_impl_text(&strukt, &buf); | 77 | buf = generate_impl_text(&strukt, &buf); |
84 | let start = strukt.syntax().text_range().end(); | 78 | strukt.syntax().text_range().end() |
85 | |||
86 | (start, TextSize::of("\n}\n")) | ||
87 | }); | 79 | }); |
88 | 80 | ||
89 | edit.set_cursor(start_offset + TextSize::of(&buf) - end_offset); | 81 | match ctx.config.snippet_cap { |
90 | edit.insert(start_offset, buf); | 82 | None => builder.insert(start_offset, buf), |
83 | Some(cap) => { | ||
84 | buf = buf.replace("fn new", "fn $0new"); | ||
85 | builder.insert_snippet(cap, start_offset, buf); | ||
86 | } | ||
87 | } | ||
91 | }) | 88 | }) |
92 | } | 89 | } |
93 | 90 | ||
@@ -124,7 +121,7 @@ fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String { | |||
124 | // | 121 | // |
125 | // FIXME: change the new fn checking to a more semantic approach when that's more | 122 | // FIXME: change the new fn checking to a more semantic approach when that's more |
126 | // viable (e.g. we process proc macros, etc) | 123 | // viable (e.g. we process proc macros, etc) |
127 | fn find_struct_impl(ctx: &AssistCtx, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> { | 124 | fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> { |
128 | let db = ctx.db; | 125 | let db = ctx.db; |
129 | let module = strukt.syntax().ancestors().find(|node| { | 126 | let module = strukt.syntax().ancestors().find(|node| { |
130 | ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) | 127 | ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) |
@@ -162,8 +159,8 @@ fn find_struct_impl(ctx: &AssistCtx, strukt: &ast::StructDef) -> Option<Option<a | |||
162 | 159 | ||
163 | fn has_new_fn(imp: &ast::ImplDef) -> bool { | 160 | fn has_new_fn(imp: &ast::ImplDef) -> bool { |
164 | if let Some(il) = imp.item_list() { | 161 | if let Some(il) = imp.item_list() { |
165 | for item in il.impl_items() { | 162 | for item in il.assoc_items() { |
166 | if let ast::ImplItem::FnDef(f) = item { | 163 | if let ast::AssocItem::FnDef(f) = item { |
167 | if let Some(name) = f.name() { | 164 | if let Some(name) = f.name() { |
168 | if name.text().eq_ignore_ascii_case("new") { | 165 | if name.text().eq_ignore_ascii_case("new") { |
169 | return true; | 166 | return true; |
@@ -178,7 +175,7 @@ fn has_new_fn(imp: &ast::ImplDef) -> bool { | |||
178 | 175 | ||
179 | #[cfg(test)] | 176 | #[cfg(test)] |
180 | mod tests { | 177 | mod tests { |
181 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | 178 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
182 | 179 | ||
183 | use super::*; | 180 | use super::*; |
184 | 181 | ||
@@ -192,7 +189,7 @@ mod tests { | |||
192 | "struct Foo {} | 189 | "struct Foo {} |
193 | 190 | ||
194 | impl Foo { | 191 | impl Foo { |
195 | fn new() -> Self { Self { } }<|> | 192 | fn $0new() -> Self { Self { } } |
196 | } | 193 | } |
197 | ", | 194 | ", |
198 | ); | 195 | ); |
@@ -202,7 +199,7 @@ impl Foo { | |||
202 | "struct Foo<T: Clone> {} | 199 | "struct Foo<T: Clone> {} |
203 | 200 | ||
204 | impl<T: Clone> Foo<T> { | 201 | impl<T: Clone> Foo<T> { |
205 | fn new() -> Self { Self { } }<|> | 202 | fn $0new() -> Self { Self { } } |
206 | } | 203 | } |
207 | ", | 204 | ", |
208 | ); | 205 | ); |
@@ -212,7 +209,7 @@ impl<T: Clone> Foo<T> { | |||
212 | "struct Foo<'a, T: Foo<'a>> {} | 209 | "struct Foo<'a, T: Foo<'a>> {} |
213 | 210 | ||
214 | impl<'a, T: Foo<'a>> Foo<'a, T> { | 211 | impl<'a, T: Foo<'a>> Foo<'a, T> { |
215 | fn new() -> Self { Self { } }<|> | 212 | fn $0new() -> Self { Self { } } |
216 | } | 213 | } |
217 | ", | 214 | ", |
218 | ); | 215 | ); |
@@ -222,7 +219,7 @@ impl<'a, T: Foo<'a>> Foo<'a, T> { | |||
222 | "struct Foo { baz: String } | 219 | "struct Foo { baz: String } |
223 | 220 | ||
224 | impl Foo { | 221 | impl Foo { |
225 | fn new(baz: String) -> Self { Self { baz } }<|> | 222 | fn $0new(baz: String) -> Self { Self { baz } } |
226 | } | 223 | } |
227 | ", | 224 | ", |
228 | ); | 225 | ); |
@@ -232,7 +229,7 @@ impl Foo { | |||
232 | "struct Foo { baz: String, qux: Vec<i32> } | 229 | "struct Foo { baz: String, qux: Vec<i32> } |
233 | 230 | ||
234 | impl Foo { | 231 | impl Foo { |
235 | fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|> | 232 | fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } } |
236 | } | 233 | } |
237 | ", | 234 | ", |
238 | ); | 235 | ); |
@@ -244,7 +241,7 @@ impl Foo { | |||
244 | "struct Foo { pub baz: String, pub qux: Vec<i32> } | 241 | "struct Foo { pub baz: String, pub qux: Vec<i32> } |
245 | 242 | ||
246 | impl Foo { | 243 | impl Foo { |
247 | fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|> | 244 | fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } } |
248 | } | 245 | } |
249 | ", | 246 | ", |
250 | ); | 247 | ); |
@@ -259,7 +256,7 @@ impl Foo {} | |||
259 | "struct Foo {} | 256 | "struct Foo {} |
260 | 257 | ||
261 | impl Foo { | 258 | impl Foo { |
262 | fn new() -> Self { Self { } }<|> | 259 | fn $0new() -> Self { Self { } } |
263 | } | 260 | } |
264 | ", | 261 | ", |
265 | ); | 262 | ); |
@@ -274,7 +271,7 @@ impl Foo { | |||
274 | "struct Foo {} | 271 | "struct Foo {} |
275 | 272 | ||
276 | impl Foo { | 273 | impl Foo { |
277 | fn new() -> Self { Self { } }<|> | 274 | fn $0new() -> Self { Self { } } |
278 | 275 | ||
279 | fn qux(&self) {} | 276 | fn qux(&self) {} |
280 | } | 277 | } |
@@ -295,7 +292,7 @@ impl Foo { | |||
295 | "struct Foo {} | 292 | "struct Foo {} |
296 | 293 | ||
297 | impl Foo { | 294 | impl Foo { |
298 | fn new() -> Self { Self { } }<|> | 295 | fn $0new() -> Self { Self { } } |
299 | 296 | ||
300 | fn qux(&self) {} | 297 | fn qux(&self) {} |
301 | fn baz() -> i32 { | 298 | fn baz() -> i32 { |
@@ -312,7 +309,7 @@ impl Foo { | |||
312 | "pub struct Foo {} | 309 | "pub struct Foo {} |
313 | 310 | ||
314 | impl Foo { | 311 | impl Foo { |
315 | pub fn new() -> Self { Self { } }<|> | 312 | pub fn $0new() -> Self { Self { } } |
316 | } | 313 | } |
317 | ", | 314 | ", |
318 | ); | 315 | ); |
@@ -322,7 +319,7 @@ impl Foo { | |||
322 | "pub(crate) struct Foo {} | 319 | "pub(crate) struct Foo {} |
323 | 320 | ||
324 | impl Foo { | 321 | impl Foo { |
325 | pub(crate) fn new() -> Self { Self { } }<|> | 322 | pub(crate) fn $0new() -> Self { Self { } } |
326 | } | 323 | } |
327 | ", | 324 | ", |
328 | ); | 325 | ); |
@@ -415,7 +412,7 @@ pub struct Source<T> { | |||
415 | } | 412 | } |
416 | 413 | ||
417 | impl<T> Source<T> { | 414 | impl<T> Source<T> { |
418 | pub fn new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }<|> | 415 | pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } } |
419 | 416 | ||
420 | pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { | 417 | pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { |
421 | Source { file_id: self.file_id, ast: f(self.ast) } | 418 | Source { file_id: self.file_id, ast: f(self.ast) } |
diff --git a/crates/ra_assists/src/handlers/add_turbo_fish.rs b/crates/ra_assists/src/handlers/add_turbo_fish.rs new file mode 100644 index 000000000..26acf81f2 --- /dev/null +++ b/crates/ra_assists/src/handlers/add_turbo_fish.rs | |||
@@ -0,0 +1,134 @@ | |||
1 | use ra_ide_db::defs::{classify_name_ref, Definition, NameRefClass}; | ||
2 | use ra_syntax::{ast, AstNode, SyntaxKind, T}; | ||
3 | use test_utils::mark; | ||
4 | |||
5 | use crate::{ | ||
6 | assist_context::{AssistContext, Assists}, | ||
7 | AssistId, | ||
8 | }; | ||
9 | |||
10 | // Assist: add_turbo_fish | ||
11 | // | ||
12 | // Adds `::<_>` to a call of a generic method or function. | ||
13 | // | ||
14 | // ``` | ||
15 | // fn make<T>() -> T { todo!() } | ||
16 | // fn main() { | ||
17 | // let x = make<|>(); | ||
18 | // } | ||
19 | // ``` | ||
20 | // -> | ||
21 | // ``` | ||
22 | // fn make<T>() -> T { todo!() } | ||
23 | // fn main() { | ||
24 | // let x = make::<${0:_}>(); | ||
25 | // } | ||
26 | // ``` | ||
27 | pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
28 | let ident = ctx.find_token_at_offset(SyntaxKind::IDENT)?; | ||
29 | let next_token = ident.next_token()?; | ||
30 | if next_token.kind() == T![::] { | ||
31 | mark::hit!(add_turbo_fish_one_fish_is_enough); | ||
32 | return None; | ||
33 | } | ||
34 | let name_ref = ast::NameRef::cast(ident.parent())?; | ||
35 | let def = match classify_name_ref(&ctx.sema, &name_ref)? { | ||
36 | NameRefClass::Definition(def) => def, | ||
37 | NameRefClass::FieldShorthand { .. } => return None, | ||
38 | }; | ||
39 | let fun = match def { | ||
40 | Definition::ModuleDef(hir::ModuleDef::Function(it)) => it, | ||
41 | _ => return None, | ||
42 | }; | ||
43 | let generics = hir::GenericDef::Function(fun).params(ctx.sema.db); | ||
44 | if generics.is_empty() { | ||
45 | mark::hit!(add_turbo_fish_non_generic); | ||
46 | return None; | ||
47 | } | ||
48 | acc.add(AssistId("add_turbo_fish"), "Add `::<>`", ident.text_range(), |builder| { | ||
49 | match ctx.config.snippet_cap { | ||
50 | Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"), | ||
51 | None => builder.insert(ident.text_range().end(), "::<_>"), | ||
52 | } | ||
53 | }) | ||
54 | } | ||
55 | |||
56 | #[cfg(test)] | ||
57 | mod tests { | ||
58 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
59 | |||
60 | use super::*; | ||
61 | use test_utils::mark; | ||
62 | |||
63 | #[test] | ||
64 | fn add_turbo_fish_function() { | ||
65 | check_assist( | ||
66 | add_turbo_fish, | ||
67 | r#" | ||
68 | fn make<T>() -> T {} | ||
69 | fn main() { | ||
70 | make<|>(); | ||
71 | } | ||
72 | "#, | ||
73 | r#" | ||
74 | fn make<T>() -> T {} | ||
75 | fn main() { | ||
76 | make::<${0:_}>(); | ||
77 | } | ||
78 | "#, | ||
79 | ); | ||
80 | } | ||
81 | |||
82 | #[test] | ||
83 | fn add_turbo_fish_method() { | ||
84 | check_assist( | ||
85 | add_turbo_fish, | ||
86 | r#" | ||
87 | struct S; | ||
88 | impl S { | ||
89 | fn make<T>(&self) -> T {} | ||
90 | } | ||
91 | fn main() { | ||
92 | S.make<|>(); | ||
93 | } | ||
94 | "#, | ||
95 | r#" | ||
96 | struct S; | ||
97 | impl S { | ||
98 | fn make<T>(&self) -> T {} | ||
99 | } | ||
100 | fn main() { | ||
101 | S.make::<${0:_}>(); | ||
102 | } | ||
103 | "#, | ||
104 | ); | ||
105 | } | ||
106 | |||
107 | #[test] | ||
108 | fn add_turbo_fish_one_fish_is_enough() { | ||
109 | mark::check!(add_turbo_fish_one_fish_is_enough); | ||
110 | check_assist_not_applicable( | ||
111 | add_turbo_fish, | ||
112 | r#" | ||
113 | fn make<T>() -> T {} | ||
114 | fn main() { | ||
115 | make<|>::<()>(); | ||
116 | } | ||
117 | "#, | ||
118 | ); | ||
119 | } | ||
120 | |||
121 | #[test] | ||
122 | fn add_turbo_fish_non_generic() { | ||
123 | mark::check!(add_turbo_fish_non_generic); | ||
124 | check_assist_not_applicable( | ||
125 | add_turbo_fish, | ||
126 | r#" | ||
127 | fn make() -> () {} | ||
128 | fn main() { | ||
129 | make<|>(); | ||
130 | } | ||
131 | "#, | ||
132 | ); | ||
133 | } | ||
134 | } | ||
diff --git a/crates/ra_assists/src/handlers/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs index 260b9e073..233e8fb8e 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,26 +59,26 @@ 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() { |
67 | check_assist(apply_demorgan, "fn f() { !x &&<|> !x }", "fn f() { !(x ||<|> x) }") | 66 | check_assist(apply_demorgan, "fn f() { !x &&<|> !x }", "fn f() { !(x || x) }") |
68 | } | 67 | } |
69 | 68 | ||
70 | #[test] | 69 | #[test] |
71 | fn demorgan_turns_or_into_and() { | 70 | fn demorgan_turns_or_into_and() { |
72 | check_assist(apply_demorgan, "fn f() { !x ||<|> !x }", "fn f() { !(x &&<|> x) }") | 71 | check_assist(apply_demorgan, "fn f() { !x ||<|> !x }", "fn f() { !(x && x) }") |
73 | } | 72 | } |
74 | 73 | ||
75 | #[test] | 74 | #[test] |
76 | fn demorgan_removes_inequality() { | 75 | fn demorgan_removes_inequality() { |
77 | check_assist(apply_demorgan, "fn f() { x != x ||<|> !x }", "fn f() { !(x == x &&<|> x) }") | 76 | check_assist(apply_demorgan, "fn f() { x != x ||<|> !x }", "fn f() { !(x == x && x) }") |
78 | } | 77 | } |
79 | 78 | ||
80 | #[test] | 79 | #[test] |
81 | fn demorgan_general_case() { | 80 | fn demorgan_general_case() { |
82 | check_assist(apply_demorgan, "fn f() { x ||<|> x }", "fn f() { !(!x &&<|> !x) }") | 81 | check_assist(apply_demorgan, "fn f() { x ||<|> x }", "fn f() { !(!x && !x) }") |
83 | } | 82 | } |
84 | 83 | ||
85 | #[test] | 84 | #[test] |
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs index 99682e023..edf96d50e 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,25 +34,32 @@ 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() { |
45 | return None; | 41 | return None; |
46 | } | 42 | } |
47 | 43 | ||
48 | let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message()); | 44 | let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range; |
45 | let group = auto_import_assets.get_import_group_message(); | ||
49 | for import in proposed_imports { | 46 | for import in proposed_imports { |
50 | group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { | 47 | acc.add_group( |
51 | edit.target(auto_import_assets.syntax_under_caret.text_range()); | 48 | &group, |
52 | insert_use_statement( | 49 | AssistId("auto_import"), |
53 | &auto_import_assets.syntax_under_caret, | 50 | format!("Import `{}`", &import), |
54 | &import, | 51 | range, |
55 | edit.text_edit_builder(), | 52 | |builder| { |
56 | ); | 53 | insert_use_statement( |
57 | }); | 54 | &auto_import_assets.syntax_under_caret, |
58 | } | 55 | &import, |
59 | group.finish() | 56 | ctx, |
57 | builder.text_edit_builder(), | ||
58 | ); | ||
59 | }, | ||
60 | ); | ||
61 | } | ||
62 | Some(()) | ||
60 | } | 63 | } |
61 | 64 | ||
62 | #[derive(Debug)] | 65 | #[derive(Debug)] |
@@ -67,15 +70,15 @@ struct AutoImportAssets { | |||
67 | } | 70 | } |
68 | 71 | ||
69 | impl AutoImportAssets { | 72 | impl AutoImportAssets { |
70 | fn new(ctx: &AssistCtx) -> Option<Self> { | 73 | fn new(ctx: &AssistContext) -> Option<Self> { |
71 | if let Some(path_under_caret) = ctx.find_node_at_offset::<ast::Path>() { | 74 | if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() { |
72 | Self::for_regular_path(path_under_caret, &ctx) | 75 | Self::for_regular_path(path_under_caret, &ctx) |
73 | } else { | 76 | } else { |
74 | Self::for_method_call(ctx.find_node_at_offset()?, &ctx) | 77 | Self::for_method_call(ctx.find_node_at_offset_with_descend()?, &ctx) |
75 | } | 78 | } |
76 | } | 79 | } |
77 | 80 | ||
78 | fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistCtx) -> Option<Self> { | 81 | fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistContext) -> Option<Self> { |
79 | let syntax_under_caret = method_call.syntax().to_owned(); | 82 | let syntax_under_caret = method_call.syntax().to_owned(); |
80 | let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?; | 83 | let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?; |
81 | Some(Self { | 84 | Some(Self { |
@@ -85,7 +88,7 @@ impl AutoImportAssets { | |||
85 | }) | 88 | }) |
86 | } | 89 | } |
87 | 90 | ||
88 | fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistCtx) -> Option<Self> { | 91 | fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> { |
89 | let syntax_under_caret = path_under_caret.syntax().to_owned(); | 92 | let syntax_under_caret = path_under_caret.syntax().to_owned(); |
90 | if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() { | 93 | if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() { |
91 | return None; | 94 | return None; |
@@ -108,8 +111,8 @@ impl AutoImportAssets { | |||
108 | } | 111 | } |
109 | } | 112 | } |
110 | 113 | ||
111 | fn get_import_group_message(&self) -> String { | 114 | fn get_import_group_message(&self) -> GroupLabel { |
112 | match &self.import_candidate { | 115 | let name = match &self.import_candidate { |
113 | ImportCandidate::UnqualifiedName(name) => format!("Import {}", name), | 116 | ImportCandidate::UnqualifiedName(name) => format!("Import {}", name), |
114 | ImportCandidate::QualifierStart(qualifier_start) => { | 117 | ImportCandidate::QualifierStart(qualifier_start) => { |
115 | format!("Import {}", qualifier_start) | 118 | format!("Import {}", qualifier_start) |
@@ -120,7 +123,8 @@ impl AutoImportAssets { | |||
120 | ImportCandidate::TraitMethod(_, trait_method_name) => { | 123 | ImportCandidate::TraitMethod(_, trait_method_name) => { |
121 | format!("Import a trait for method {}", trait_method_name) | 124 | format!("Import a trait for method {}", trait_method_name) |
122 | } | 125 | } |
123 | } | 126 | }; |
127 | GroupLabel(name) | ||
124 | } | 128 | } |
125 | 129 | ||
126 | fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> { | 130 | fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> { |
@@ -280,7 +284,7 @@ impl ImportCandidate { | |||
280 | #[cfg(test)] | 284 | #[cfg(test)] |
281 | mod tests { | 285 | mod tests { |
282 | use super::*; | 286 | use super::*; |
283 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | 287 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
284 | 288 | ||
285 | #[test] | 289 | #[test] |
286 | fn applicable_when_found_an_import() { | 290 | fn applicable_when_found_an_import() { |
@@ -294,7 +298,7 @@ mod tests { | |||
294 | } | 298 | } |
295 | ", | 299 | ", |
296 | r" | 300 | r" |
297 | <|>use PubMod::PubStruct; | 301 | use PubMod::PubStruct; |
298 | 302 | ||
299 | PubStruct | 303 | PubStruct |
300 | 304 | ||
@@ -306,6 +310,35 @@ mod tests { | |||
306 | } | 310 | } |
307 | 311 | ||
308 | #[test] | 312 | #[test] |
313 | fn applicable_when_found_an_import_in_macros() { | ||
314 | check_assist( | ||
315 | auto_import, | ||
316 | r" | ||
317 | macro_rules! foo { | ||
318 | ($i:ident) => { fn foo(a: $i) {} } | ||
319 | } | ||
320 | foo!(Pub<|>Struct); | ||
321 | |||
322 | pub mod PubMod { | ||
323 | pub struct PubStruct; | ||
324 | } | ||
325 | ", | ||
326 | r" | ||
327 | use PubMod::PubStruct; | ||
328 | |||
329 | macro_rules! foo { | ||
330 | ($i:ident) => { fn foo(a: $i) {} } | ||
331 | } | ||
332 | foo!(PubStruct); | ||
333 | |||
334 | pub mod PubMod { | ||
335 | pub struct PubStruct; | ||
336 | } | ||
337 | ", | ||
338 | ); | ||
339 | } | ||
340 | |||
341 | #[test] | ||
309 | fn auto_imports_are_merged() { | 342 | fn auto_imports_are_merged() { |
310 | check_assist( | 343 | check_assist( |
311 | auto_import, | 344 | auto_import, |
@@ -327,7 +360,7 @@ mod tests { | |||
327 | use PubMod::{PubStruct2, PubStruct1}; | 360 | use PubMod::{PubStruct2, PubStruct1}; |
328 | 361 | ||
329 | struct Test { | 362 | struct Test { |
330 | test: Pub<|>Struct2<u8>, | 363 | test: PubStruct2<u8>, |
331 | } | 364 | } |
332 | 365 | ||
333 | pub mod PubMod { | 366 | pub mod PubMod { |
@@ -358,9 +391,9 @@ mod tests { | |||
358 | } | 391 | } |
359 | ", | 392 | ", |
360 | r" | 393 | r" |
361 | use PubMod1::PubStruct; | 394 | use PubMod3::PubStruct; |
362 | 395 | ||
363 | PubSt<|>ruct | 396 | PubStruct |
364 | 397 | ||
365 | pub mod PubMod1 { | 398 | pub mod PubMod1 { |
366 | pub struct PubStruct; | 399 | pub struct PubStruct; |
@@ -441,7 +474,7 @@ mod tests { | |||
441 | r" | 474 | r" |
442 | use PubMod::test_function; | 475 | use PubMod::test_function; |
443 | 476 | ||
444 | test_function<|> | 477 | test_function |
445 | 478 | ||
446 | pub mod PubMod { | 479 | pub mod PubMod { |
447 | pub fn test_function() {}; | 480 | pub fn test_function() {}; |
@@ -468,7 +501,7 @@ mod tests { | |||
468 | r"use crate_with_macro::foo; | 501 | r"use crate_with_macro::foo; |
469 | 502 | ||
470 | fn main() { | 503 | fn main() { |
471 | foo<|> | 504 | foo |
472 | } | 505 | } |
473 | ", | 506 | ", |
474 | ); | 507 | ); |
@@ -554,7 +587,7 @@ fn main() { | |||
554 | } | 587 | } |
555 | 588 | ||
556 | fn main() { | 589 | fn main() { |
557 | TestStruct::test_function<|> | 590 | TestStruct::test_function |
558 | } | 591 | } |
559 | ", | 592 | ", |
560 | ); | 593 | ); |
@@ -587,7 +620,7 @@ fn main() { | |||
587 | } | 620 | } |
588 | 621 | ||
589 | fn main() { | 622 | fn main() { |
590 | TestStruct::TEST_CONST<|> | 623 | TestStruct::TEST_CONST |
591 | } | 624 | } |
592 | ", | 625 | ", |
593 | ); | 626 | ); |
@@ -626,7 +659,7 @@ fn main() { | |||
626 | } | 659 | } |
627 | 660 | ||
628 | fn main() { | 661 | fn main() { |
629 | test_mod::TestStruct::test_function<|> | 662 | test_mod::TestStruct::test_function |
630 | } | 663 | } |
631 | ", | 664 | ", |
632 | ); | 665 | ); |
@@ -697,7 +730,7 @@ fn main() { | |||
697 | } | 730 | } |
698 | 731 | ||
699 | fn main() { | 732 | fn main() { |
700 | test_mod::TestStruct::TEST_CONST<|> | 733 | test_mod::TestStruct::TEST_CONST |
701 | } | 734 | } |
702 | ", | 735 | ", |
703 | ); | 736 | ); |
@@ -770,7 +803,7 @@ fn main() { | |||
770 | 803 | ||
771 | fn main() { | 804 | fn main() { |
772 | let test_struct = test_mod::TestStruct {}; | 805 | let test_struct = test_mod::TestStruct {}; |
773 | test_struct.test_meth<|>od() | 806 | test_struct.test_method() |
774 | } | 807 | } |
775 | ", | 808 | ", |
776 | ); | 809 | ); |
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..c6baa0a57 --- /dev/null +++ b/crates/ra_assists/src/handlers/change_return_type_to_result.rs | |||
@@ -0,0 +1,961 @@ | |||
1 | use ra_syntax::{ | ||
2 | ast::{self, BlockExpr, Expr, LoopBodyOwner}, | ||
3 | AstNode, SyntaxNode, | ||
4 | }; | ||
5 | |||
6 | use crate::{AssistContext, AssistId, Assists}; | ||
7 | |||
8 | // Assist: change_return_type_to_result | ||
9 | // | ||
10 | // Change the function's return type to Result. | ||
11 | // | ||
12 | // ``` | ||
13 | // fn foo() -> i32<|> { 42i32 } | ||
14 | // ``` | ||
15 | // -> | ||
16 | // ``` | ||
17 | // fn foo() -> Result<i32, ${0:_}> { Ok(42i32) } | ||
18 | // ``` | ||
19 | pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
20 | let ret_type = ctx.find_node_at_offset::<ast::RetType>()?; | ||
21 | // FIXME: extend to lambdas as well | ||
22 | let fn_def = ret_type.syntax().parent().and_then(ast::FnDef::cast)?; | ||
23 | |||
24 | let type_ref = &ret_type.type_ref()?; | ||
25 | if type_ref.syntax().text().to_string().starts_with("Result<") { | ||
26 | return None; | ||
27 | } | ||
28 | |||
29 | let block_expr = &fn_def.body()?; | ||
30 | |||
31 | acc.add( | ||
32 | AssistId("change_return_type_to_result"), | ||
33 | "Change return type to Result", | ||
34 | type_ref.syntax().text_range(), | ||
35 | |builder| { | ||
36 | let mut tail_return_expr_collector = TailReturnCollector::new(); | ||
37 | tail_return_expr_collector.collect_jump_exprs(block_expr, false); | ||
38 | tail_return_expr_collector.collect_tail_exprs(block_expr); | ||
39 | |||
40 | for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap { | ||
41 | builder.replace_node_and_indent(&ret_expr_arg, format!("Ok({})", ret_expr_arg)); | ||
42 | } | ||
43 | |||
44 | match ctx.config.snippet_cap { | ||
45 | Some(cap) => { | ||
46 | let snippet = format!("Result<{}, ${{0:_}}>", type_ref); | ||
47 | builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet) | ||
48 | } | ||
49 | None => builder | ||
50 | .replace(type_ref.syntax().text_range(), format!("Result<{}, _>", type_ref)), | ||
51 | } | ||
52 | }, | ||
53 | ) | ||
54 | } | ||
55 | |||
56 | struct TailReturnCollector { | ||
57 | exprs_to_wrap: Vec<SyntaxNode>, | ||
58 | } | ||
59 | |||
60 | impl TailReturnCollector { | ||
61 | fn new() -> Self { | ||
62 | Self { exprs_to_wrap: vec![] } | ||
63 | } | ||
64 | /// Collect all`return` expression | ||
65 | fn collect_jump_exprs(&mut self, block_expr: &BlockExpr, collect_break: bool) { | ||
66 | let statements = block_expr.statements(); | ||
67 | for stmt in statements { | ||
68 | let expr = match &stmt { | ||
69 | ast::Stmt::ExprStmt(stmt) => stmt.expr(), | ||
70 | ast::Stmt::LetStmt(stmt) => stmt.initializer(), | ||
71 | }; | ||
72 | if let Some(expr) = &expr { | ||
73 | self.handle_exprs(expr, collect_break); | ||
74 | } | ||
75 | } | ||
76 | |||
77 | // Browse tail expressions for each block | ||
78 | if let Some(expr) = block_expr.expr() { | ||
79 | if let Some(last_exprs) = get_tail_expr_from_block(&expr) { | ||
80 | for last_expr in last_exprs { | ||
81 | let last_expr = match last_expr { | ||
82 | NodeType::Node(expr) | NodeType::Leaf(expr) => expr, | ||
83 | }; | ||
84 | |||
85 | if let Some(last_expr) = Expr::cast(last_expr.clone()) { | ||
86 | self.handle_exprs(&last_expr, collect_break); | ||
87 | } else if let Some(expr_stmt) = ast::Stmt::cast(last_expr) { | ||
88 | let expr_stmt = match &expr_stmt { | ||
89 | ast::Stmt::ExprStmt(stmt) => stmt.expr(), | ||
90 | ast::Stmt::LetStmt(stmt) => stmt.initializer(), | ||
91 | }; | ||
92 | if let Some(expr) = &expr_stmt { | ||
93 | self.handle_exprs(expr, collect_break); | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | } | ||
98 | } | ||
99 | } | ||
100 | |||
101 | fn handle_exprs(&mut self, expr: &Expr, collect_break: bool) { | ||
102 | match expr { | ||
103 | Expr::BlockExpr(block_expr) => { | ||
104 | self.collect_jump_exprs(&block_expr, collect_break); | ||
105 | } | ||
106 | Expr::ReturnExpr(ret_expr) => { | ||
107 | if let Some(ret_expr_arg) = &ret_expr.expr() { | ||
108 | self.exprs_to_wrap.push(ret_expr_arg.syntax().clone()); | ||
109 | } | ||
110 | } | ||
111 | Expr::BreakExpr(break_expr) if collect_break => { | ||
112 | if let Some(break_expr_arg) = &break_expr.expr() { | ||
113 | self.exprs_to_wrap.push(break_expr_arg.syntax().clone()); | ||
114 | } | ||
115 | } | ||
116 | Expr::IfExpr(if_expr) => { | ||
117 | for block in if_expr.blocks() { | ||
118 | self.collect_jump_exprs(&block, collect_break); | ||
119 | } | ||
120 | } | ||
121 | Expr::LoopExpr(loop_expr) => { | ||
122 | if let Some(block_expr) = loop_expr.loop_body() { | ||
123 | self.collect_jump_exprs(&block_expr, collect_break); | ||
124 | } | ||
125 | } | ||
126 | Expr::ForExpr(for_expr) => { | ||
127 | if let Some(block_expr) = for_expr.loop_body() { | ||
128 | self.collect_jump_exprs(&block_expr, collect_break); | ||
129 | } | ||
130 | } | ||
131 | Expr::WhileExpr(while_expr) => { | ||
132 | if let Some(block_expr) = while_expr.loop_body() { | ||
133 | self.collect_jump_exprs(&block_expr, collect_break); | ||
134 | } | ||
135 | } | ||
136 | Expr::MatchExpr(match_expr) => { | ||
137 | if let Some(arm_list) = match_expr.match_arm_list() { | ||
138 | arm_list.arms().filter_map(|match_arm| match_arm.expr()).for_each(|expr| { | ||
139 | self.handle_exprs(&expr, collect_break); | ||
140 | }); | ||
141 | } | ||
142 | } | ||
143 | _ => {} | ||
144 | } | ||
145 | } | ||
146 | |||
147 | fn collect_tail_exprs(&mut self, block: &BlockExpr) { | ||
148 | if let Some(expr) = block.expr() { | ||
149 | self.handle_exprs(&expr, true); | ||
150 | self.fetch_tail_exprs(&expr); | ||
151 | } | ||
152 | } | ||
153 | |||
154 | fn fetch_tail_exprs(&mut self, expr: &Expr) { | ||
155 | if let Some(exprs) = get_tail_expr_from_block(expr) { | ||
156 | for node_type in &exprs { | ||
157 | match node_type { | ||
158 | NodeType::Leaf(expr) => { | ||
159 | self.exprs_to_wrap.push(expr.clone()); | ||
160 | } | ||
161 | NodeType::Node(expr) => match &Expr::cast(expr.clone()) { | ||
162 | Some(last_expr) => { | ||
163 | self.fetch_tail_exprs(last_expr); | ||
164 | } | ||
165 | None => { | ||
166 | self.exprs_to_wrap.push(expr.clone()); | ||
167 | } | ||
168 | }, | ||
169 | } | ||
170 | } | ||
171 | } | ||
172 | } | ||
173 | } | ||
174 | |||
175 | #[derive(Debug)] | ||
176 | enum NodeType { | ||
177 | Leaf(SyntaxNode), | ||
178 | Node(SyntaxNode), | ||
179 | } | ||
180 | |||
181 | /// Get a tail expression inside a block | ||
182 | fn get_tail_expr_from_block(expr: &Expr) -> Option<Vec<NodeType>> { | ||
183 | match expr { | ||
184 | Expr::IfExpr(if_expr) => { | ||
185 | let mut nodes = vec![]; | ||
186 | for block in if_expr.blocks() { | ||
187 | if let Some(block_expr) = block.expr() { | ||
188 | if let Some(tail_exprs) = get_tail_expr_from_block(&block_expr) { | ||
189 | nodes.extend(tail_exprs); | ||
190 | } | ||
191 | } else if let Some(last_expr) = block.syntax().last_child() { | ||
192 | nodes.push(NodeType::Node(last_expr)); | ||
193 | } else { | ||
194 | nodes.push(NodeType::Node(block.syntax().clone())); | ||
195 | } | ||
196 | } | ||
197 | Some(nodes) | ||
198 | } | ||
199 | Expr::LoopExpr(loop_expr) => { | ||
200 | loop_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)]) | ||
201 | } | ||
202 | Expr::ForExpr(for_expr) => { | ||
203 | for_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)]) | ||
204 | } | ||
205 | Expr::WhileExpr(while_expr) => { | ||
206 | while_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)]) | ||
207 | } | ||
208 | Expr::BlockExpr(block_expr) => { | ||
209 | block_expr.expr().map(|lc| vec![NodeType::Node(lc.syntax().clone())]) | ||
210 | } | ||
211 | Expr::MatchExpr(match_expr) => { | ||
212 | let arm_list = match_expr.match_arm_list()?; | ||
213 | let arms: Vec<NodeType> = arm_list | ||
214 | .arms() | ||
215 | .filter_map(|match_arm| match_arm.expr()) | ||
216 | .map(|expr| match expr { | ||
217 | Expr::ReturnExpr(ret_expr) => NodeType::Node(ret_expr.syntax().clone()), | ||
218 | Expr::BreakExpr(break_expr) => NodeType::Node(break_expr.syntax().clone()), | ||
219 | _ => match expr.syntax().last_child() { | ||
220 | Some(last_expr) => NodeType::Node(last_expr), | ||
221 | None => NodeType::Node(expr.syntax().clone()), | ||
222 | }, | ||
223 | }) | ||
224 | .collect(); | ||
225 | |||
226 | Some(arms) | ||
227 | } | ||
228 | Expr::BreakExpr(expr) => expr.expr().map(|e| vec![NodeType::Leaf(e.syntax().clone())]), | ||
229 | Expr::ReturnExpr(ret_expr) => Some(vec![NodeType::Node(ret_expr.syntax().clone())]), | ||
230 | Expr::CallExpr(call_expr) => Some(vec![NodeType::Leaf(call_expr.syntax().clone())]), | ||
231 | Expr::Literal(lit_expr) => Some(vec![NodeType::Leaf(lit_expr.syntax().clone())]), | ||
232 | Expr::TupleExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), | ||
233 | Expr::ArrayExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), | ||
234 | Expr::ParenExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), | ||
235 | Expr::PathExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), | ||
236 | Expr::Label(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), | ||
237 | Expr::RecordLit(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), | ||
238 | Expr::IndexExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), | ||
239 | Expr::MethodCallExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), | ||
240 | Expr::AwaitExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), | ||
241 | Expr::CastExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), | ||
242 | Expr::RefExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), | ||
243 | Expr::PrefixExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), | ||
244 | Expr::RangeExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), | ||
245 | Expr::BinExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), | ||
246 | Expr::MacroCall(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), | ||
247 | Expr::BoxExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), | ||
248 | _ => None, | ||
249 | } | ||
250 | } | ||
251 | |||
252 | #[cfg(test)] | ||
253 | mod tests { | ||
254 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
255 | |||
256 | use super::*; | ||
257 | |||
258 | #[test] | ||
259 | fn change_return_type_to_result_simple() { | ||
260 | check_assist( | ||
261 | change_return_type_to_result, | ||
262 | r#"fn foo() -> i3<|>2 { | ||
263 | let test = "test"; | ||
264 | return 42i32; | ||
265 | }"#, | ||
266 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
267 | let test = "test"; | ||
268 | return Ok(42i32); | ||
269 | }"#, | ||
270 | ); | ||
271 | } | ||
272 | |||
273 | #[test] | ||
274 | fn change_return_type_to_result_simple_return_type() { | ||
275 | check_assist( | ||
276 | change_return_type_to_result, | ||
277 | r#"fn foo() -> i32<|> { | ||
278 | let test = "test"; | ||
279 | return 42i32; | ||
280 | }"#, | ||
281 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
282 | let test = "test"; | ||
283 | return Ok(42i32); | ||
284 | }"#, | ||
285 | ); | ||
286 | } | ||
287 | |||
288 | #[test] | ||
289 | fn change_return_type_to_result_simple_return_type_bad_cursor() { | ||
290 | check_assist_not_applicable( | ||
291 | change_return_type_to_result, | ||
292 | r#"fn foo() -> i32 { | ||
293 | let test = "test";<|> | ||
294 | return 42i32; | ||
295 | }"#, | ||
296 | ); | ||
297 | } | ||
298 | |||
299 | #[test] | ||
300 | fn change_return_type_to_result_simple_with_cursor() { | ||
301 | check_assist( | ||
302 | change_return_type_to_result, | ||
303 | r#"fn foo() -> <|>i32 { | ||
304 | let test = "test"; | ||
305 | return 42i32; | ||
306 | }"#, | ||
307 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
308 | let test = "test"; | ||
309 | return Ok(42i32); | ||
310 | }"#, | ||
311 | ); | ||
312 | } | ||
313 | |||
314 | #[test] | ||
315 | fn change_return_type_to_result_simple_with_tail() { | ||
316 | check_assist( | ||
317 | change_return_type_to_result, | ||
318 | r#"fn foo() -><|> i32 { | ||
319 | let test = "test"; | ||
320 | 42i32 | ||
321 | }"#, | ||
322 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
323 | let test = "test"; | ||
324 | Ok(42i32) | ||
325 | }"#, | ||
326 | ); | ||
327 | } | ||
328 | |||
329 | #[test] | ||
330 | fn change_return_type_to_result_simple_with_tail_only() { | ||
331 | check_assist( | ||
332 | change_return_type_to_result, | ||
333 | r#"fn foo() -> i32<|> { | ||
334 | 42i32 | ||
335 | }"#, | ||
336 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
337 | Ok(42i32) | ||
338 | }"#, | ||
339 | ); | ||
340 | } | ||
341 | #[test] | ||
342 | fn change_return_type_to_result_simple_with_tail_block_like() { | ||
343 | check_assist( | ||
344 | change_return_type_to_result, | ||
345 | r#"fn foo() -> i32<|> { | ||
346 | if true { | ||
347 | 42i32 | ||
348 | } else { | ||
349 | 24i32 | ||
350 | } | ||
351 | }"#, | ||
352 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
353 | if true { | ||
354 | Ok(42i32) | ||
355 | } else { | ||
356 | Ok(24i32) | ||
357 | } | ||
358 | }"#, | ||
359 | ); | ||
360 | } | ||
361 | |||
362 | #[test] | ||
363 | fn change_return_type_to_result_simple_with_nested_if() { | ||
364 | check_assist( | ||
365 | change_return_type_to_result, | ||
366 | r#"fn foo() -> i32<|> { | ||
367 | if true { | ||
368 | if false { | ||
369 | 1 | ||
370 | } else { | ||
371 | 2 | ||
372 | } | ||
373 | } else { | ||
374 | 24i32 | ||
375 | } | ||
376 | }"#, | ||
377 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
378 | if true { | ||
379 | if false { | ||
380 | Ok(1) | ||
381 | } else { | ||
382 | Ok(2) | ||
383 | } | ||
384 | } else { | ||
385 | Ok(24i32) | ||
386 | } | ||
387 | }"#, | ||
388 | ); | ||
389 | } | ||
390 | |||
391 | #[test] | ||
392 | fn change_return_type_to_result_simple_with_await() { | ||
393 | check_assist( | ||
394 | change_return_type_to_result, | ||
395 | r#"async fn foo() -> i<|>32 { | ||
396 | if true { | ||
397 | if false { | ||
398 | 1.await | ||
399 | } else { | ||
400 | 2.await | ||
401 | } | ||
402 | } else { | ||
403 | 24i32.await | ||
404 | } | ||
405 | }"#, | ||
406 | r#"async fn foo() -> Result<i32, ${0:_}> { | ||
407 | if true { | ||
408 | if false { | ||
409 | Ok(1.await) | ||
410 | } else { | ||
411 | Ok(2.await) | ||
412 | } | ||
413 | } else { | ||
414 | Ok(24i32.await) | ||
415 | } | ||
416 | }"#, | ||
417 | ); | ||
418 | } | ||
419 | |||
420 | #[test] | ||
421 | fn change_return_type_to_result_simple_with_array() { | ||
422 | check_assist( | ||
423 | change_return_type_to_result, | ||
424 | r#"fn foo() -> [i32;<|> 3] { | ||
425 | [1, 2, 3] | ||
426 | }"#, | ||
427 | r#"fn foo() -> Result<[i32; 3], ${0:_}> { | ||
428 | Ok([1, 2, 3]) | ||
429 | }"#, | ||
430 | ); | ||
431 | } | ||
432 | |||
433 | #[test] | ||
434 | fn change_return_type_to_result_simple_with_cast() { | ||
435 | check_assist( | ||
436 | change_return_type_to_result, | ||
437 | r#"fn foo() -<|>> i32 { | ||
438 | if true { | ||
439 | if false { | ||
440 | 1 as i32 | ||
441 | } else { | ||
442 | 2 as i32 | ||
443 | } | ||
444 | } else { | ||
445 | 24 as i32 | ||
446 | } | ||
447 | }"#, | ||
448 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
449 | if true { | ||
450 | if false { | ||
451 | Ok(1 as i32) | ||
452 | } else { | ||
453 | Ok(2 as i32) | ||
454 | } | ||
455 | } else { | ||
456 | Ok(24 as i32) | ||
457 | } | ||
458 | }"#, | ||
459 | ); | ||
460 | } | ||
461 | |||
462 | #[test] | ||
463 | fn change_return_type_to_result_simple_with_tail_block_like_match() { | ||
464 | check_assist( | ||
465 | change_return_type_to_result, | ||
466 | r#"fn foo() -> i32<|> { | ||
467 | let my_var = 5; | ||
468 | match my_var { | ||
469 | 5 => 42i32, | ||
470 | _ => 24i32, | ||
471 | } | ||
472 | }"#, | ||
473 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
474 | let my_var = 5; | ||
475 | match my_var { | ||
476 | 5 => Ok(42i32), | ||
477 | _ => Ok(24i32), | ||
478 | } | ||
479 | }"#, | ||
480 | ); | ||
481 | } | ||
482 | |||
483 | #[test] | ||
484 | fn change_return_type_to_result_simple_with_loop_with_tail() { | ||
485 | check_assist( | ||
486 | change_return_type_to_result, | ||
487 | r#"fn foo() -> i32<|> { | ||
488 | let my_var = 5; | ||
489 | loop { | ||
490 | println!("test"); | ||
491 | 5 | ||
492 | } | ||
493 | |||
494 | my_var | ||
495 | }"#, | ||
496 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
497 | let my_var = 5; | ||
498 | loop { | ||
499 | println!("test"); | ||
500 | 5 | ||
501 | } | ||
502 | |||
503 | Ok(my_var) | ||
504 | }"#, | ||
505 | ); | ||
506 | } | ||
507 | |||
508 | #[test] | ||
509 | fn change_return_type_to_result_simple_with_loop_in_let_stmt() { | ||
510 | check_assist( | ||
511 | change_return_type_to_result, | ||
512 | r#"fn foo() -> i32<|> { | ||
513 | let my_var = let x = loop { | ||
514 | break 1; | ||
515 | }; | ||
516 | |||
517 | my_var | ||
518 | }"#, | ||
519 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
520 | let my_var = let x = loop { | ||
521 | break 1; | ||
522 | }; | ||
523 | |||
524 | Ok(my_var) | ||
525 | }"#, | ||
526 | ); | ||
527 | } | ||
528 | |||
529 | #[test] | ||
530 | fn change_return_type_to_result_simple_with_tail_block_like_match_return_expr() { | ||
531 | check_assist( | ||
532 | change_return_type_to_result, | ||
533 | r#"fn foo() -> i32<|> { | ||
534 | let my_var = 5; | ||
535 | let res = match my_var { | ||
536 | 5 => 42i32, | ||
537 | _ => return 24i32, | ||
538 | }; | ||
539 | |||
540 | res | ||
541 | }"#, | ||
542 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
543 | let my_var = 5; | ||
544 | let res = match my_var { | ||
545 | 5 => 42i32, | ||
546 | _ => return Ok(24i32), | ||
547 | }; | ||
548 | |||
549 | Ok(res) | ||
550 | }"#, | ||
551 | ); | ||
552 | |||
553 | check_assist( | ||
554 | change_return_type_to_result, | ||
555 | r#"fn foo() -> i32<|> { | ||
556 | let my_var = 5; | ||
557 | let res = if my_var == 5 { | ||
558 | 42i32 | ||
559 | } else { | ||
560 | return 24i32; | ||
561 | }; | ||
562 | |||
563 | res | ||
564 | }"#, | ||
565 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
566 | let my_var = 5; | ||
567 | let res = if my_var == 5 { | ||
568 | 42i32 | ||
569 | } else { | ||
570 | return Ok(24i32); | ||
571 | }; | ||
572 | |||
573 | Ok(res) | ||
574 | }"#, | ||
575 | ); | ||
576 | } | ||
577 | |||
578 | #[test] | ||
579 | fn change_return_type_to_result_simple_with_tail_block_like_match_deeper() { | ||
580 | check_assist( | ||
581 | change_return_type_to_result, | ||
582 | r#"fn foo() -> i32<|> { | ||
583 | let my_var = 5; | ||
584 | match my_var { | ||
585 | 5 => { | ||
586 | if true { | ||
587 | 42i32 | ||
588 | } else { | ||
589 | 25i32 | ||
590 | } | ||
591 | }, | ||
592 | _ => { | ||
593 | let test = "test"; | ||
594 | if test == "test" { | ||
595 | return bar(); | ||
596 | } | ||
597 | 53i32 | ||
598 | }, | ||
599 | } | ||
600 | }"#, | ||
601 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
602 | let my_var = 5; | ||
603 | match my_var { | ||
604 | 5 => { | ||
605 | if true { | ||
606 | Ok(42i32) | ||
607 | } else { | ||
608 | Ok(25i32) | ||
609 | } | ||
610 | }, | ||
611 | _ => { | ||
612 | let test = "test"; | ||
613 | if test == "test" { | ||
614 | return Ok(bar()); | ||
615 | } | ||
616 | Ok(53i32) | ||
617 | }, | ||
618 | } | ||
619 | }"#, | ||
620 | ); | ||
621 | } | ||
622 | |||
623 | #[test] | ||
624 | fn change_return_type_to_result_simple_with_tail_block_like_early_return() { | ||
625 | check_assist( | ||
626 | change_return_type_to_result, | ||
627 | r#"fn foo() -> i<|>32 { | ||
628 | let test = "test"; | ||
629 | if test == "test" { | ||
630 | return 24i32; | ||
631 | } | ||
632 | 53i32 | ||
633 | }"#, | ||
634 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
635 | let test = "test"; | ||
636 | if test == "test" { | ||
637 | return Ok(24i32); | ||
638 | } | ||
639 | Ok(53i32) | ||
640 | }"#, | ||
641 | ); | ||
642 | } | ||
643 | |||
644 | #[test] | ||
645 | fn change_return_type_to_result_simple_with_closure() { | ||
646 | check_assist( | ||
647 | change_return_type_to_result, | ||
648 | r#"fn foo(the_field: u32) -><|> u32 { | ||
649 | let true_closure = || { | ||
650 | return true; | ||
651 | }; | ||
652 | if the_field < 5 { | ||
653 | let mut i = 0; | ||
654 | |||
655 | |||
656 | if true_closure() { | ||
657 | return 99; | ||
658 | } else { | ||
659 | return 0; | ||
660 | } | ||
661 | } | ||
662 | |||
663 | the_field | ||
664 | }"#, | ||
665 | r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> { | ||
666 | let true_closure = || { | ||
667 | return true; | ||
668 | }; | ||
669 | if the_field < 5 { | ||
670 | let mut i = 0; | ||
671 | |||
672 | |||
673 | if true_closure() { | ||
674 | return Ok(99); | ||
675 | } else { | ||
676 | return Ok(0); | ||
677 | } | ||
678 | } | ||
679 | |||
680 | Ok(the_field) | ||
681 | }"#, | ||
682 | ); | ||
683 | |||
684 | check_assist( | ||
685 | change_return_type_to_result, | ||
686 | r#"fn foo(the_field: u32) -> u32<|> { | ||
687 | let true_closure = || { | ||
688 | return true; | ||
689 | }; | ||
690 | if the_field < 5 { | ||
691 | let mut i = 0; | ||
692 | |||
693 | |||
694 | if true_closure() { | ||
695 | return 99; | ||
696 | } else { | ||
697 | return 0; | ||
698 | } | ||
699 | } | ||
700 | let t = None; | ||
701 | |||
702 | t.unwrap_or_else(|| the_field) | ||
703 | }"#, | ||
704 | r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> { | ||
705 | let true_closure = || { | ||
706 | return true; | ||
707 | }; | ||
708 | if the_field < 5 { | ||
709 | let mut i = 0; | ||
710 | |||
711 | |||
712 | if true_closure() { | ||
713 | return Ok(99); | ||
714 | } else { | ||
715 | return Ok(0); | ||
716 | } | ||
717 | } | ||
718 | let t = None; | ||
719 | |||
720 | Ok(t.unwrap_or_else(|| the_field)) | ||
721 | }"#, | ||
722 | ); | ||
723 | } | ||
724 | |||
725 | #[test] | ||
726 | fn change_return_type_to_result_simple_with_weird_forms() { | ||
727 | check_assist( | ||
728 | change_return_type_to_result, | ||
729 | r#"fn foo() -> i32<|> { | ||
730 | let test = "test"; | ||
731 | if test == "test" { | ||
732 | return 24i32; | ||
733 | } | ||
734 | let mut i = 0; | ||
735 | loop { | ||
736 | if i == 1 { | ||
737 | break 55; | ||
738 | } | ||
739 | i += 1; | ||
740 | } | ||
741 | }"#, | ||
742 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
743 | let test = "test"; | ||
744 | if test == "test" { | ||
745 | return Ok(24i32); | ||
746 | } | ||
747 | let mut i = 0; | ||
748 | loop { | ||
749 | if i == 1 { | ||
750 | break Ok(55); | ||
751 | } | ||
752 | i += 1; | ||
753 | } | ||
754 | }"#, | ||
755 | ); | ||
756 | |||
757 | check_assist( | ||
758 | change_return_type_to_result, | ||
759 | r#"fn foo() -> i32<|> { | ||
760 | let test = "test"; | ||
761 | if test == "test" { | ||
762 | return 24i32; | ||
763 | } | ||
764 | let mut i = 0; | ||
765 | loop { | ||
766 | loop { | ||
767 | if i == 1 { | ||
768 | break 55; | ||
769 | } | ||
770 | i += 1; | ||
771 | } | ||
772 | } | ||
773 | }"#, | ||
774 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
775 | let test = "test"; | ||
776 | if test == "test" { | ||
777 | return Ok(24i32); | ||
778 | } | ||
779 | let mut i = 0; | ||
780 | loop { | ||
781 | loop { | ||
782 | if i == 1 { | ||
783 | break Ok(55); | ||
784 | } | ||
785 | i += 1; | ||
786 | } | ||
787 | } | ||
788 | }"#, | ||
789 | ); | ||
790 | |||
791 | check_assist( | ||
792 | change_return_type_to_result, | ||
793 | r#"fn foo() -> i3<|>2 { | ||
794 | let test = "test"; | ||
795 | let other = 5; | ||
796 | if test == "test" { | ||
797 | let res = match other { | ||
798 | 5 => 43, | ||
799 | _ => return 56, | ||
800 | }; | ||
801 | } | ||
802 | let mut i = 0; | ||
803 | loop { | ||
804 | loop { | ||
805 | if i == 1 { | ||
806 | break 55; | ||
807 | } | ||
808 | i += 1; | ||
809 | } | ||
810 | } | ||
811 | }"#, | ||
812 | r#"fn foo() -> Result<i32, ${0:_}> { | ||
813 | let test = "test"; | ||
814 | let other = 5; | ||
815 | if test == "test" { | ||
816 | let res = match other { | ||
817 | 5 => 43, | ||
818 | _ => return Ok(56), | ||
819 | }; | ||
820 | } | ||
821 | let mut i = 0; | ||
822 | loop { | ||
823 | loop { | ||
824 | if i == 1 { | ||
825 | break Ok(55); | ||
826 | } | ||
827 | i += 1; | ||
828 | } | ||
829 | } | ||
830 | }"#, | ||
831 | ); | ||
832 | |||
833 | check_assist( | ||
834 | change_return_type_to_result, | ||
835 | r#"fn foo(the_field: u32) -> u32<|> { | ||
836 | if the_field < 5 { | ||
837 | let mut i = 0; | ||
838 | loop { | ||
839 | if i > 5 { | ||
840 | return 55u32; | ||
841 | } | ||
842 | i += 3; | ||
843 | } | ||
844 | |||
845 | match i { | ||
846 | 5 => return 99, | ||
847 | _ => return 0, | ||
848 | }; | ||
849 | } | ||
850 | |||
851 | the_field | ||
852 | }"#, | ||
853 | r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> { | ||
854 | if the_field < 5 { | ||
855 | let mut i = 0; | ||
856 | loop { | ||
857 | if i > 5 { | ||
858 | return Ok(55u32); | ||
859 | } | ||
860 | i += 3; | ||
861 | } | ||
862 | |||
863 | match i { | ||
864 | 5 => return Ok(99), | ||
865 | _ => return Ok(0), | ||
866 | }; | ||
867 | } | ||
868 | |||
869 | Ok(the_field) | ||
870 | }"#, | ||
871 | ); | ||
872 | |||
873 | check_assist( | ||
874 | change_return_type_to_result, | ||
875 | r#"fn foo(the_field: u32) -> u3<|>2 { | ||
876 | if the_field < 5 { | ||
877 | let mut i = 0; | ||
878 | |||
879 | match i { | ||
880 | 5 => return 99, | ||
881 | _ => return 0, | ||
882 | } | ||
883 | } | ||
884 | |||
885 | the_field | ||
886 | }"#, | ||
887 | r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> { | ||
888 | if the_field < 5 { | ||
889 | let mut i = 0; | ||
890 | |||
891 | match i { | ||
892 | 5 => return Ok(99), | ||
893 | _ => return Ok(0), | ||
894 | } | ||
895 | } | ||
896 | |||
897 | Ok(the_field) | ||
898 | }"#, | ||
899 | ); | ||
900 | |||
901 | check_assist( | ||
902 | change_return_type_to_result, | ||
903 | r#"fn foo(the_field: u32) -> u32<|> { | ||
904 | if the_field < 5 { | ||
905 | let mut i = 0; | ||
906 | |||
907 | if i == 5 { | ||
908 | return 99 | ||
909 | } else { | ||
910 | return 0 | ||
911 | } | ||
912 | } | ||
913 | |||
914 | the_field | ||
915 | }"#, | ||
916 | r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> { | ||
917 | if the_field < 5 { | ||
918 | let mut i = 0; | ||
919 | |||
920 | if i == 5 { | ||
921 | return Ok(99) | ||
922 | } else { | ||
923 | return Ok(0) | ||
924 | } | ||
925 | } | ||
926 | |||
927 | Ok(the_field) | ||
928 | }"#, | ||
929 | ); | ||
930 | |||
931 | check_assist( | ||
932 | change_return_type_to_result, | ||
933 | r#"fn foo(the_field: u32) -> <|>u32 { | ||
934 | if the_field < 5 { | ||
935 | let mut i = 0; | ||
936 | |||
937 | if i == 5 { | ||
938 | return 99; | ||
939 | } else { | ||
940 | return 0; | ||
941 | } | ||
942 | } | ||
943 | |||
944 | the_field | ||
945 | }"#, | ||
946 | r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> { | ||
947 | if the_field < 5 { | ||
948 | let mut i = 0; | ||
949 | |||
950 | if i == 5 { | ||
951 | return Ok(99); | ||
952 | } else { | ||
953 | return Ok(0); | ||
954 | } | ||
955 | } | ||
956 | |||
957 | Ok(the_field) | ||
958 | }"#, | ||
959 | ); | ||
960 | } | ||
961 | } | ||
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs index 44f6a1dae..c21d75be0 100644 --- a/crates/ra_assists/src/handlers/change_visibility.rs +++ b/crates/ra_assists/src/handlers/change_visibility.rs | |||
@@ -7,9 +7,9 @@ use ra_syntax::{ | |||
7 | }, | 7 | }, |
8 | SyntaxNode, TextSize, T, | 8 | SyntaxNode, TextSize, T, |
9 | }; | 9 | }; |
10 | use test_utils::mark; | ||
10 | 11 | ||
11 | use crate::{Assist, AssistCtx, AssistId}; | 12 | use crate::{AssistContext, AssistId, Assists}; |
12 | use test_utils::tested_by; | ||
13 | 13 | ||
14 | // Assist: change_visibility | 14 | // Assist: change_visibility |
15 | // | 15 | // |
@@ -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, |
@@ -47,23 +47,27 @@ fn add_vis(ctx: AssistCtx) -> Option<Assist> { | |||
47 | return None; | 47 | return None; |
48 | } | 48 | } |
49 | (vis_offset(&parent), keyword.text_range()) | 49 | (vis_offset(&parent), keyword.text_range()) |
50 | } else { | 50 | } else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() { |
51 | let field_name: ast::Name = ctx.find_node_at_offset()?; | ||
52 | let field = field_name.syntax().ancestors().find_map(ast::RecordFieldDef::cast)?; | 51 | let field = field_name.syntax().ancestors().find_map(ast::RecordFieldDef::cast)?; |
53 | if field.name()? != field_name { | 52 | if field.name()? != field_name { |
54 | tested_by!(change_visibility_field_false_positive); | 53 | mark::hit!(change_visibility_field_false_positive); |
55 | return None; | 54 | return None; |
56 | } | 55 | } |
57 | if field.visibility().is_some() { | 56 | if field.visibility().is_some() { |
58 | return None; | 57 | return None; |
59 | } | 58 | } |
60 | (vis_offset(field.syntax()), field_name.syntax().text_range()) | 59 | (vis_offset(field.syntax()), field_name.syntax().text_range()) |
60 | } else if let Some(field) = ctx.find_node_at_offset::<ast::TupleFieldDef>() { | ||
61 | if field.visibility().is_some() { | ||
62 | return None; | ||
63 | } | ||
64 | (vis_offset(field.syntax()), field.syntax().text_range()) | ||
65 | } else { | ||
66 | return None; | ||
61 | }; | 67 | }; |
62 | 68 | ||
63 | 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| { |
64 | edit.target(target); | ||
65 | edit.insert(offset, "pub(crate) "); | 70 | edit.insert(offset, "pub(crate) "); |
66 | edit.set_cursor(offset); | ||
67 | }) | 71 | }) |
68 | } | 72 | } |
69 | 73 | ||
@@ -78,49 +82,49 @@ fn vis_offset(node: &SyntaxNode) -> TextSize { | |||
78 | .unwrap_or_else(|| node.text_range().start()) | 82 | .unwrap_or_else(|| node.text_range().start()) |
79 | } | 83 | } |
80 | 84 | ||
81 | fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> { | 85 | fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> { |
82 | if vis.syntax().text() == "pub" { | 86 | if vis.syntax().text() == "pub" { |
83 | return ctx.add_assist( | 87 | let target = vis.syntax().text_range(); |
88 | return acc.add( | ||
84 | AssistId("change_visibility"), | 89 | AssistId("change_visibility"), |
85 | "Change Visibility to pub(crate)", | 90 | "Change Visibility to pub(crate)", |
91 | target, | ||
86 | |edit| { | 92 | |edit| { |
87 | edit.target(vis.syntax().text_range()); | ||
88 | edit.replace(vis.syntax().text_range(), "pub(crate)"); | 93 | edit.replace(vis.syntax().text_range(), "pub(crate)"); |
89 | edit.set_cursor(vis.syntax().text_range().start()) | ||
90 | }, | 94 | }, |
91 | ); | 95 | ); |
92 | } | 96 | } |
93 | if vis.syntax().text() == "pub(crate)" { | 97 | if vis.syntax().text() == "pub(crate)" { |
94 | return ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub", |edit| { | 98 | let target = vis.syntax().text_range(); |
95 | edit.target(vis.syntax().text_range()); | 99 | return acc.add( |
96 | edit.replace(vis.syntax().text_range(), "pub"); | 100 | AssistId("change_visibility"), |
97 | edit.set_cursor(vis.syntax().text_range().start()); | 101 | "Change visibility to pub", |
98 | }); | 102 | target, |
103 | |edit| { | ||
104 | edit.replace(vis.syntax().text_range(), "pub"); | ||
105 | }, | ||
106 | ); | ||
99 | } | 107 | } |
100 | None | 108 | None |
101 | } | 109 | } |
102 | 110 | ||
103 | #[cfg(test)] | 111 | #[cfg(test)] |
104 | mod tests { | 112 | mod tests { |
105 | use test_utils::covers; | 113 | use test_utils::mark; |
106 | 114 | ||
107 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | 115 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
108 | 116 | ||
109 | use super::*; | 117 | use super::*; |
110 | 118 | ||
111 | #[test] | 119 | #[test] |
112 | fn change_visibility_adds_pub_crate_to_items() { | 120 | fn change_visibility_adds_pub_crate_to_items() { |
113 | check_assist(change_visibility, "<|>fn foo() {}", "<|>pub(crate) fn foo() {}"); | 121 | check_assist(change_visibility, "<|>fn foo() {}", "pub(crate) fn foo() {}"); |
114 | check_assist(change_visibility, "f<|>n foo() {}", "<|>pub(crate) fn foo() {}"); | 122 | check_assist(change_visibility, "f<|>n foo() {}", "pub(crate) fn foo() {}"); |
115 | check_assist(change_visibility, "<|>struct Foo {}", "<|>pub(crate) struct Foo {}"); | 123 | check_assist(change_visibility, "<|>struct Foo {}", "pub(crate) struct Foo {}"); |
116 | check_assist(change_visibility, "<|>mod foo {}", "<|>pub(crate) mod foo {}"); | 124 | check_assist(change_visibility, "<|>mod foo {}", "pub(crate) mod foo {}"); |
117 | check_assist(change_visibility, "<|>trait Foo {}", "<|>pub(crate) trait Foo {}"); | 125 | check_assist(change_visibility, "<|>trait Foo {}", "pub(crate) trait Foo {}"); |
118 | check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}"); | 126 | check_assist(change_visibility, "m<|>od {}", "pub(crate) mod {}"); |
119 | check_assist( | 127 | check_assist(change_visibility, "unsafe f<|>n foo() {}", "pub(crate) unsafe fn foo() {}"); |
120 | change_visibility, | ||
121 | "unsafe f<|>n foo() {}", | ||
122 | "<|>pub(crate) unsafe fn foo() {}", | ||
123 | ); | ||
124 | } | 128 | } |
125 | 129 | ||
126 | #[test] | 130 | #[test] |
@@ -128,13 +132,14 @@ mod tests { | |||
128 | check_assist( | 132 | check_assist( |
129 | change_visibility, | 133 | change_visibility, |
130 | r"struct S { <|>field: u32 }", | 134 | r"struct S { <|>field: u32 }", |
131 | r"struct S { <|>pub(crate) field: u32 }", | 135 | r"struct S { pub(crate) field: u32 }", |
132 | ) | 136 | ); |
137 | check_assist(change_visibility, r"struct S ( <|>u32 )", r"struct S ( pub(crate) u32 )"); | ||
133 | } | 138 | } |
134 | 139 | ||
135 | #[test] | 140 | #[test] |
136 | fn change_visibility_field_false_positive() { | 141 | fn change_visibility_field_false_positive() { |
137 | covers!(change_visibility_field_false_positive); | 142 | mark::check!(change_visibility_field_false_positive); |
138 | check_assist_not_applicable( | 143 | check_assist_not_applicable( |
139 | change_visibility, | 144 | change_visibility, |
140 | r"struct S { field: [(); { let <|>x = ();}] }", | 145 | r"struct S { field: [(); { let <|>x = ();}] }", |
@@ -143,17 +148,17 @@ mod tests { | |||
143 | 148 | ||
144 | #[test] | 149 | #[test] |
145 | fn change_visibility_pub_to_pub_crate() { | 150 | fn change_visibility_pub_to_pub_crate() { |
146 | check_assist(change_visibility, "<|>pub fn foo() {}", "<|>pub(crate) fn foo() {}") | 151 | check_assist(change_visibility, "<|>pub fn foo() {}", "pub(crate) fn foo() {}") |
147 | } | 152 | } |
148 | 153 | ||
149 | #[test] | 154 | #[test] |
150 | fn change_visibility_pub_crate_to_pub() { | 155 | fn change_visibility_pub_crate_to_pub() { |
151 | check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "<|>pub fn foo() {}") | 156 | check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "pub fn foo() {}") |
152 | } | 157 | } |
153 | 158 | ||
154 | #[test] | 159 | #[test] |
155 | fn change_visibility_const() { | 160 | fn change_visibility_const() { |
156 | check_assist(change_visibility, "<|>const FOO = 3u8;", "<|>pub(crate) const FOO = 3u8;"); | 161 | check_assist(change_visibility, "<|>const FOO = 3u8;", "pub(crate) const FOO = 3u8;"); |
157 | } | 162 | } |
158 | 163 | ||
159 | #[test] | 164 | #[test] |
@@ -174,12 +179,21 @@ mod tests { | |||
174 | // comments | 179 | // comments |
175 | 180 | ||
176 | #[derive(Debug)] | 181 | #[derive(Debug)] |
177 | <|>pub(crate) struct Foo; | 182 | pub(crate) struct Foo; |
178 | ", | 183 | ", |
179 | ) | 184 | ) |
180 | } | 185 | } |
181 | 186 | ||
182 | #[test] | 187 | #[test] |
188 | fn not_applicable_for_enum_variants() { | ||
189 | check_assist_not_applicable( | ||
190 | change_visibility, | ||
191 | r"mod foo { pub enum Foo {Foo1} } | ||
192 | fn main() { foo::Foo::Foo1<|> } ", | ||
193 | ); | ||
194 | } | ||
195 | |||
196 | #[test] | ||
183 | fn change_visibility_target() { | 197 | fn change_visibility_target() { |
184 | check_assist_target(change_visibility, "<|>fn foo() {}", "fn"); | 198 | check_assist_target(change_visibility, "<|>fn foo() {}", "fn"); |
185 | check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)"); | 199 | check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)"); |
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs index ea6c56f8c..4cc75a7ce 100644 --- a/crates/ra_assists/src/handlers/early_return.rs +++ b/crates/ra_assists/src/handlers/early_return.rs | |||
@@ -2,14 +2,18 @@ use std::{iter::once, ops::RangeInclusive}; | |||
2 | 2 | ||
3 | use ra_syntax::{ | 3 | use ra_syntax::{ |
4 | algo::replace_children, | 4 | algo::replace_children, |
5 | ast::{self, edit::IndentLevel, make, Block, Pat::TupleStructPat}, | 5 | ast::{ |
6 | self, | ||
7 | edit::{AstNodeEdit, IndentLevel}, | ||
8 | make, | ||
9 | }, | ||
6 | AstNode, | 10 | AstNode, |
7 | SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE}, | 11 | SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE}, |
8 | SyntaxNode, | 12 | SyntaxNode, |
9 | }; | 13 | }; |
10 | 14 | ||
11 | use crate::{ | 15 | use crate::{ |
12 | assist_ctx::{Assist, AssistCtx}, | 16 | assist_context::{AssistContext, Assists}, |
13 | utils::invert_boolean_expression, | 17 | utils::invert_boolean_expression, |
14 | AssistId, | 18 | AssistId, |
15 | }; | 19 | }; |
@@ -36,7 +40,7 @@ use crate::{ | |||
36 | // bar(); | 40 | // bar(); |
37 | // } | 41 | // } |
38 | // ``` | 42 | // ``` |
39 | pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> { | 43 | pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
40 | let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; | 44 | let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; |
41 | if if_expr.else_branch().is_some() { | 45 | if if_expr.else_branch().is_some() { |
42 | return None; | 46 | return None; |
@@ -47,7 +51,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> { | |||
47 | // Check if there is an IfLet that we can handle. | 51 | // Check if there is an IfLet that we can handle. |
48 | let if_let_pat = match cond.pat() { | 52 | let if_let_pat = match cond.pat() { |
49 | None => None, // No IfLet, supported. | 53 | None => None, // No IfLet, supported. |
50 | Some(TupleStructPat(pat)) if pat.args().count() == 1 => { | 54 | Some(ast::Pat::TupleStructPat(pat)) if pat.args().count() == 1 => { |
51 | let path = pat.path()?; | 55 | let path = pat.path()?; |
52 | match path.qualifier() { | 56 | match path.qualifier() { |
53 | None => { | 57 | None => { |
@@ -61,9 +65,9 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> { | |||
61 | }; | 65 | }; |
62 | 66 | ||
63 | let cond_expr = cond.expr()?; | 67 | let cond_expr = cond.expr()?; |
64 | let then_block = if_expr.then_branch()?.block()?; | 68 | let then_block = if_expr.then_branch()?; |
65 | 69 | ||
66 | let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::Block::cast)?; | 70 | let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?; |
67 | 71 | ||
68 | if parent_block.expr()? != if_expr.clone().into() { | 72 | if parent_block.expr()? != if_expr.clone().into() { |
69 | return None; | 73 | return None; |
@@ -80,7 +84,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> { | |||
80 | return None; | 84 | return None; |
81 | } | 85 | } |
82 | 86 | ||
83 | let parent_container = parent_block.syntax().parent()?.parent()?; | 87 | let parent_container = parent_block.syntax().parent()?; |
84 | 88 | ||
85 | let early_expression: ast::Expr = match parent_container.kind() { | 89 | let early_expression: ast::Expr = match parent_container.kind() { |
86 | WHILE_EXPR | LOOP_EXPR => make::expr_continue(), | 90 | WHILE_EXPR | LOOP_EXPR => make::expr_continue(), |
@@ -93,9 +97,9 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> { | |||
93 | } | 97 | } |
94 | 98 | ||
95 | then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; | 99 | then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; |
96 | let cursor_position = ctx.frange.range.start(); | ||
97 | 100 | ||
98 | ctx.add_assist(AssistId("convert_to_guarded_return"), "Convert to guarded return", |edit| { | 101 | let target = if_expr.syntax().text_range(); |
102 | acc.add(AssistId("convert_to_guarded_return"), "Convert to guarded return", target, |edit| { | ||
99 | let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); | 103 | let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); |
100 | let new_block = match if_let_pat { | 104 | let new_block = match if_let_pat { |
101 | None => { | 105 | None => { |
@@ -104,8 +108,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> { | |||
104 | let then_branch = | 108 | let then_branch = |
105 | make::block_expr(once(make::expr_stmt(early_expression).into()), None); | 109 | make::block_expr(once(make::expr_stmt(early_expression).into()), None); |
106 | let cond = invert_boolean_expression(cond_expr); | 110 | let cond = invert_boolean_expression(cond_expr); |
107 | let e = make::expr_if(make::condition(cond, None), then_branch); | 111 | make::expr_if(make::condition(cond, None), then_branch).indent(if_indent_level) |
108 | if_indent_level.increase_indent(e) | ||
109 | }; | 112 | }; |
110 | replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) | 113 | replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) |
111 | } | 114 | } |
@@ -139,21 +142,19 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> { | |||
139 | make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), | 142 | make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), |
140 | Some(match_expr), | 143 | Some(match_expr), |
141 | ); | 144 | ); |
142 | let let_stmt = if_indent_level.increase_indent(let_stmt); | 145 | let let_stmt = let_stmt.indent(if_indent_level); |
143 | replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) | 146 | replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) |
144 | } | 147 | } |
145 | }; | 148 | }; |
146 | edit.target(if_expr.syntax().text_range()); | 149 | edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap()); |
147 | edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap()); | ||
148 | edit.set_cursor(cursor_position); | ||
149 | 150 | ||
150 | fn replace( | 151 | fn replace( |
151 | new_expr: &SyntaxNode, | 152 | new_expr: &SyntaxNode, |
152 | then_block: &Block, | 153 | then_block: &ast::BlockExpr, |
153 | parent_block: &Block, | 154 | parent_block: &ast::BlockExpr, |
154 | if_expr: &ast::IfExpr, | 155 | if_expr: &ast::IfExpr, |
155 | ) -> SyntaxNode { | 156 | ) -> SyntaxNode { |
156 | let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone()); | 157 | let then_block_items = then_block.dedent(IndentLevel::from(1)); |
157 | let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); | 158 | let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); |
158 | let end_of_then = | 159 | let end_of_then = |
159 | if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { | 160 | if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { |
@@ -182,7 +183,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> { | |||
182 | 183 | ||
183 | #[cfg(test)] | 184 | #[cfg(test)] |
184 | mod tests { | 185 | mod tests { |
185 | use crate::helpers::{check_assist, check_assist_not_applicable}; | 186 | use crate::tests::{check_assist, check_assist_not_applicable}; |
186 | 187 | ||
187 | use super::*; | 188 | use super::*; |
188 | 189 | ||
@@ -204,7 +205,7 @@ mod tests { | |||
204 | r#" | 205 | r#" |
205 | fn main() { | 206 | fn main() { |
206 | bar(); | 207 | bar(); |
207 | if<|> !true { | 208 | if !true { |
208 | return; | 209 | return; |
209 | } | 210 | } |
210 | foo(); | 211 | foo(); |
@@ -234,7 +235,7 @@ mod tests { | |||
234 | r#" | 235 | r#" |
235 | fn main(n: Option<String>) { | 236 | fn main(n: Option<String>) { |
236 | bar(); | 237 | bar(); |
237 | le<|>t n = match n { | 238 | let n = match n { |
238 | Some(it) => it, | 239 | Some(it) => it, |
239 | _ => return, | 240 | _ => return, |
240 | }; | 241 | }; |
@@ -260,7 +261,7 @@ mod tests { | |||
260 | "#, | 261 | "#, |
261 | r#" | 262 | r#" |
262 | fn main() { | 263 | fn main() { |
263 | le<|>t x = match Err(92) { | 264 | let x = match Err(92) { |
264 | Ok(it) => it, | 265 | Ok(it) => it, |
265 | _ => return, | 266 | _ => return, |
266 | }; | 267 | }; |
@@ -288,7 +289,7 @@ mod tests { | |||
288 | r#" | 289 | r#" |
289 | fn main(n: Option<String>) { | 290 | fn main(n: Option<String>) { |
290 | bar(); | 291 | bar(); |
291 | le<|>t n = match n { | 292 | let n = match n { |
292 | Ok(it) => it, | 293 | Ok(it) => it, |
293 | _ => return, | 294 | _ => return, |
294 | }; | 295 | }; |
@@ -318,7 +319,7 @@ mod tests { | |||
318 | r#" | 319 | r#" |
319 | fn main() { | 320 | fn main() { |
320 | while true { | 321 | while true { |
321 | if<|> !true { | 322 | if !true { |
322 | continue; | 323 | continue; |
323 | } | 324 | } |
324 | foo(); | 325 | foo(); |
@@ -346,7 +347,7 @@ mod tests { | |||
346 | r#" | 347 | r#" |
347 | fn main() { | 348 | fn main() { |
348 | while true { | 349 | while true { |
349 | le<|>t n = match n { | 350 | let n = match n { |
350 | Some(it) => it, | 351 | Some(it) => it, |
351 | _ => continue, | 352 | _ => continue, |
352 | }; | 353 | }; |
@@ -375,7 +376,7 @@ mod tests { | |||
375 | r#" | 376 | r#" |
376 | fn main() { | 377 | fn main() { |
377 | loop { | 378 | loop { |
378 | if<|> !true { | 379 | if !true { |
379 | continue; | 380 | continue; |
380 | } | 381 | } |
381 | foo(); | 382 | foo(); |
@@ -403,7 +404,7 @@ mod tests { | |||
403 | r#" | 404 | r#" |
404 | fn main() { | 405 | fn main() { |
405 | loop { | 406 | loop { |
406 | le<|>t n = match n { | 407 | let n = match n { |
407 | Some(it) => it, | 408 | Some(it) => it, |
408 | _ => continue, | 409 | _ => continue, |
409 | }; | 410 | }; |
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs index 8d1af9933..cc303285b 100644 --- a/crates/ra_assists/src/handlers/fill_match_arms.rs +++ b/crates/ra_assists/src/handlers/fill_match_arms.rs | |||
@@ -4,8 +4,12 @@ use hir::{Adt, HasSource, ModuleDef, Semantics}; | |||
4 | use itertools::Itertools; | 4 | 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 | use test_utils::mark; | ||
7 | 8 | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | 9 | use crate::{ |
10 | utils::{render_snippet, Cursor, FamousDefs}, | ||
11 | AssistContext, AssistId, Assists, | ||
12 | }; | ||
9 | 13 | ||
10 | // Assist: fill_match_arms | 14 | // Assist: fill_match_arms |
11 | // | 15 | // |
@@ -26,12 +30,12 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
26 | // | 30 | // |
27 | // fn handle(action: Action) { | 31 | // fn handle(action: Action) { |
28 | // match action { | 32 | // match action { |
29 | // Action::Move { distance } => {} | 33 | // $0Action::Move { distance } => {} |
30 | // Action::Stop => {} | 34 | // Action::Stop => {} |
31 | // } | 35 | // } |
32 | // } | 36 | // } |
33 | // ``` | 37 | // ``` |
34 | pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { | 38 | pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
35 | let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?; | 39 | let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?; |
36 | let match_arm_list = match_expr.match_arm_list()?; | 40 | let match_arm_list = match_expr.match_arm_list()?; |
37 | 41 | ||
@@ -49,12 +53,18 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
49 | let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { | 53 | let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { |
50 | let variants = enum_def.variants(ctx.db); | 54 | let variants = enum_def.variants(ctx.db); |
51 | 55 | ||
52 | variants | 56 | let mut variants = variants |
53 | .into_iter() | 57 | .into_iter() |
54 | .filter_map(|variant| build_pat(ctx.db, module, variant)) | 58 | .filter_map(|variant| build_pat(ctx.db, module, variant)) |
55 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) | 59 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) |
56 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) | 60 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) |
57 | .collect() | 61 | .collect::<Vec<_>>(); |
62 | if Some(enum_def) == FamousDefs(&ctx.sema, module.krate()).core_option_Option() { | ||
63 | // Match `Some` variant first. | ||
64 | mark::hit!(option_order); | ||
65 | variants.reverse() | ||
66 | } | ||
67 | variants | ||
58 | } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) { | 68 | } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) { |
59 | // Partial fill not currently supported for tuple of enums. | 69 | // Partial fill not currently supported for tuple of enums. |
60 | if !arms.is_empty() { | 70 | if !arms.is_empty() { |
@@ -92,12 +102,24 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
92 | return None; | 102 | return None; |
93 | } | 103 | } |
94 | 104 | ||
95 | ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| { | 105 | let target = match_expr.syntax().text_range(); |
96 | let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms); | 106 | acc.add(AssistId("fill_match_arms"), "Fill match arms", target, |builder| { |
97 | 107 | let new_arm_list = match_arm_list.remove_placeholder(); | |
98 | edit.target(match_expr.syntax().text_range()); | 108 | let n_old_arms = new_arm_list.arms().count(); |
99 | edit.set_cursor(expr.syntax().text_range().start()); | 109 | let new_arm_list = new_arm_list.append_arms(missing_arms); |
100 | edit.replace_ast(match_arm_list, new_arm_list); | 110 | let first_new_arm = new_arm_list.arms().nth(n_old_arms); |
111 | let old_range = match_arm_list.syntax().text_range(); | ||
112 | match (first_new_arm, ctx.config.snippet_cap) { | ||
113 | (Some(first_new_arm), Some(cap)) => { | ||
114 | let snippet = render_snippet( | ||
115 | cap, | ||
116 | new_arm_list.syntax(), | ||
117 | Cursor::Before(first_new_arm.syntax()), | ||
118 | ); | ||
119 | builder.replace_snippet(cap, old_range, snippet); | ||
120 | } | ||
121 | _ => builder.replace(old_range, new_arm_list.to_string()), | ||
122 | } | ||
101 | }) | 123 | }) |
102 | } | 124 | } |
103 | 125 | ||
@@ -168,7 +190,12 @@ fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> O | |||
168 | 190 | ||
169 | #[cfg(test)] | 191 | #[cfg(test)] |
170 | mod tests { | 192 | mod tests { |
171 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | 193 | use test_utils::mark; |
194 | |||
195 | use crate::{ | ||
196 | tests::{check_assist, check_assist_not_applicable, check_assist_target}, | ||
197 | utils::FamousDefs, | ||
198 | }; | ||
172 | 199 | ||
173 | use super::fill_match_arms; | 200 | use super::fill_match_arms; |
174 | 201 | ||
@@ -215,12 +242,12 @@ mod tests { | |||
215 | r#" | 242 | r#" |
216 | enum A { | 243 | enum A { |
217 | As, | 244 | As, |
218 | Bs{x:i32, y:Option<i32>}, | 245 | Bs { x: i32, y: Option<i32> }, |
219 | Cs(i32, Option<i32>), | 246 | Cs(i32, Option<i32>), |
220 | } | 247 | } |
221 | fn main() { | 248 | fn main() { |
222 | match A::As<|> { | 249 | match A::As<|> { |
223 | A::Bs{x,y:Some(_)} => {} | 250 | A::Bs { x, y: Some(_) } => {} |
224 | A::Cs(_, Some(_)) => {} | 251 | A::Cs(_, Some(_)) => {} |
225 | } | 252 | } |
226 | } | 253 | } |
@@ -228,14 +255,14 @@ mod tests { | |||
228 | r#" | 255 | r#" |
229 | enum A { | 256 | enum A { |
230 | As, | 257 | As, |
231 | Bs{x:i32, y:Option<i32>}, | 258 | Bs { x: i32, y: Option<i32> }, |
232 | Cs(i32, Option<i32>), | 259 | Cs(i32, Option<i32>), |
233 | } | 260 | } |
234 | fn main() { | 261 | fn main() { |
235 | match <|>A::As { | 262 | match A::As { |
236 | A::Bs{x,y:Some(_)} => {} | 263 | A::Bs { x, y: Some(_) } => {} |
237 | A::Cs(_, Some(_)) => {} | 264 | A::Cs(_, Some(_)) => {} |
238 | A::As => {} | 265 | $0A::As => {} |
239 | } | 266 | } |
240 | } | 267 | } |
241 | "#, | 268 | "#, |
@@ -265,9 +292,9 @@ mod tests { | |||
265 | Cs(Option<i32>), | 292 | Cs(Option<i32>), |
266 | } | 293 | } |
267 | fn main() { | 294 | fn main() { |
268 | match <|>A::As { | 295 | match A::As { |
269 | A::Cs(_) | A::Bs => {} | 296 | A::Cs(_) | A::Bs => {} |
270 | A::As => {} | 297 | $0A::As => {} |
271 | } | 298 | } |
272 | } | 299 | } |
273 | "#, | 300 | "#, |
@@ -311,11 +338,11 @@ mod tests { | |||
311 | Ys, | 338 | Ys, |
312 | } | 339 | } |
313 | fn main() { | 340 | fn main() { |
314 | match <|>A::As { | 341 | match A::As { |
315 | A::Bs if 0 < 1 => {} | 342 | A::Bs if 0 < 1 => {} |
316 | A::Ds(_value) => { let x = 1; } | 343 | A::Ds(_value) => { let x = 1; } |
317 | A::Es(B::Xs) => (), | 344 | A::Es(B::Xs) => (), |
318 | A::As => {} | 345 | $0A::As => {} |
319 | A::Cs => {} | 346 | A::Cs => {} |
320 | } | 347 | } |
321 | } | 348 | } |
@@ -333,7 +360,7 @@ mod tests { | |||
333 | Bs, | 360 | Bs, |
334 | Cs(String), | 361 | Cs(String), |
335 | Ds(String, String), | 362 | Ds(String, String), |
336 | Es{ x: usize, y: usize } | 363 | Es { x: usize, y: usize } |
337 | } | 364 | } |
338 | 365 | ||
339 | fn main() { | 366 | fn main() { |
@@ -347,13 +374,13 @@ mod tests { | |||
347 | Bs, | 374 | Bs, |
348 | Cs(String), | 375 | Cs(String), |
349 | Ds(String, String), | 376 | Ds(String, String), |
350 | Es{ x: usize, y: usize } | 377 | Es { x: usize, y: usize } |
351 | } | 378 | } |
352 | 379 | ||
353 | fn main() { | 380 | fn main() { |
354 | let a = A::As; | 381 | let a = A::As; |
355 | match <|>a { | 382 | match a { |
356 | A::As => {} | 383 | $0A::As => {} |
357 | A::Bs => {} | 384 | A::Bs => {} |
358 | A::Cs(_) => {} | 385 | A::Cs(_) => {} |
359 | A::Ds(_, _) => {} | 386 | A::Ds(_, _) => {} |
@@ -369,14 +396,8 @@ mod tests { | |||
369 | check_assist( | 396 | check_assist( |
370 | fill_match_arms, | 397 | fill_match_arms, |
371 | r#" | 398 | r#" |
372 | enum A { | 399 | enum A { One, Two } |
373 | One, | 400 | enum B { One, Two } |
374 | Two, | ||
375 | } | ||
376 | enum B { | ||
377 | One, | ||
378 | Two, | ||
379 | } | ||
380 | 401 | ||
381 | fn main() { | 402 | fn main() { |
382 | let a = A::One; | 403 | let a = A::One; |
@@ -385,20 +406,14 @@ mod tests { | |||
385 | } | 406 | } |
386 | "#, | 407 | "#, |
387 | r#" | 408 | r#" |
388 | enum A { | 409 | enum A { One, Two } |
389 | One, | 410 | enum B { One, Two } |
390 | Two, | ||
391 | } | ||
392 | enum B { | ||
393 | One, | ||
394 | Two, | ||
395 | } | ||
396 | 411 | ||
397 | fn main() { | 412 | fn main() { |
398 | let a = A::One; | 413 | let a = A::One; |
399 | let b = B::One; | 414 | let b = B::One; |
400 | match <|>(a, b) { | 415 | match (a, b) { |
401 | (A::One, B::One) => {} | 416 | $0(A::One, B::One) => {} |
402 | (A::One, B::Two) => {} | 417 | (A::One, B::Two) => {} |
403 | (A::Two, B::One) => {} | 418 | (A::Two, B::One) => {} |
404 | (A::Two, B::Two) => {} | 419 | (A::Two, B::Two) => {} |
@@ -413,14 +428,8 @@ mod tests { | |||
413 | check_assist( | 428 | check_assist( |
414 | fill_match_arms, | 429 | fill_match_arms, |
415 | r#" | 430 | r#" |
416 | enum A { | 431 | enum A { One, Two } |
417 | One, | 432 | enum B { One, Two } |
418 | Two, | ||
419 | } | ||
420 | enum B { | ||
421 | One, | ||
422 | Two, | ||
423 | } | ||
424 | 433 | ||
425 | fn main() { | 434 | fn main() { |
426 | let a = A::One; | 435 | let a = A::One; |
@@ -429,20 +438,14 @@ mod tests { | |||
429 | } | 438 | } |
430 | "#, | 439 | "#, |
431 | r#" | 440 | r#" |
432 | enum A { | 441 | enum A { One, Two } |
433 | One, | 442 | enum B { One, Two } |
434 | Two, | ||
435 | } | ||
436 | enum B { | ||
437 | One, | ||
438 | Two, | ||
439 | } | ||
440 | 443 | ||
441 | fn main() { | 444 | fn main() { |
442 | let a = A::One; | 445 | let a = A::One; |
443 | let b = B::One; | 446 | let b = B::One; |
444 | match <|>(&a, &b) { | 447 | match (&a, &b) { |
445 | (A::One, B::One) => {} | 448 | $0(A::One, B::One) => {} |
446 | (A::One, B::Two) => {} | 449 | (A::One, B::Two) => {} |
447 | (A::Two, B::One) => {} | 450 | (A::Two, B::One) => {} |
448 | (A::Two, B::Two) => {} | 451 | (A::Two, B::Two) => {} |
@@ -457,14 +460,8 @@ mod tests { | |||
457 | check_assist_not_applicable( | 460 | check_assist_not_applicable( |
458 | fill_match_arms, | 461 | fill_match_arms, |
459 | r#" | 462 | r#" |
460 | enum A { | 463 | enum A { One, Two } |
461 | One, | 464 | enum B { One, Two } |
462 | Two, | ||
463 | } | ||
464 | enum B { | ||
465 | One, | ||
466 | Two, | ||
467 | } | ||
468 | 465 | ||
469 | fn main() { | 466 | fn main() { |
470 | let a = A::One; | 467 | let a = A::One; |
@@ -482,14 +479,8 @@ mod tests { | |||
482 | check_assist_not_applicable( | 479 | check_assist_not_applicable( |
483 | fill_match_arms, | 480 | fill_match_arms, |
484 | r#" | 481 | r#" |
485 | enum A { | 482 | enum A { One, Two } |
486 | One, | 483 | enum B { One, Two } |
487 | Two, | ||
488 | } | ||
489 | enum B { | ||
490 | One, | ||
491 | Two, | ||
492 | } | ||
493 | 484 | ||
494 | fn main() { | 485 | fn main() { |
495 | let a = A::One; | 486 | let a = A::One; |
@@ -513,10 +504,7 @@ mod tests { | |||
513 | check_assist_not_applicable( | 504 | check_assist_not_applicable( |
514 | fill_match_arms, | 505 | fill_match_arms, |
515 | r#" | 506 | r#" |
516 | enum A { | 507 | enum A { One, Two } |
517 | One, | ||
518 | Two, | ||
519 | } | ||
520 | 508 | ||
521 | fn main() { | 509 | fn main() { |
522 | let a = A::One; | 510 | let a = A::One; |
@@ -532,9 +520,7 @@ mod tests { | |||
532 | check_assist( | 520 | check_assist( |
533 | fill_match_arms, | 521 | fill_match_arms, |
534 | r#" | 522 | r#" |
535 | enum A { | 523 | enum A { As } |
536 | As, | ||
537 | } | ||
538 | 524 | ||
539 | fn foo(a: &A) { | 525 | fn foo(a: &A) { |
540 | match a<|> { | 526 | match a<|> { |
@@ -542,13 +528,11 @@ mod tests { | |||
542 | } | 528 | } |
543 | "#, | 529 | "#, |
544 | r#" | 530 | r#" |
545 | enum A { | 531 | enum A { As } |
546 | As, | ||
547 | } | ||
548 | 532 | ||
549 | fn foo(a: &A) { | 533 | fn foo(a: &A) { |
550 | match <|>a { | 534 | match a { |
551 | A::As => {} | 535 | $0A::As => {} |
552 | } | 536 | } |
553 | } | 537 | } |
554 | "#, | 538 | "#, |
@@ -558,7 +542,7 @@ mod tests { | |||
558 | fill_match_arms, | 542 | fill_match_arms, |
559 | r#" | 543 | r#" |
560 | enum A { | 544 | enum A { |
561 | Es{ x: usize, y: usize } | 545 | Es { x: usize, y: usize } |
562 | } | 546 | } |
563 | 547 | ||
564 | fn foo(a: &mut A) { | 548 | fn foo(a: &mut A) { |
@@ -568,12 +552,12 @@ mod tests { | |||
568 | "#, | 552 | "#, |
569 | r#" | 553 | r#" |
570 | enum A { | 554 | enum A { |
571 | Es{ x: usize, y: usize } | 555 | Es { x: usize, y: usize } |
572 | } | 556 | } |
573 | 557 | ||
574 | fn foo(a: &mut A) { | 558 | fn foo(a: &mut A) { |
575 | match <|>a { | 559 | match a { |
576 | A::Es { x, y } => {} | 560 | $0A::Es { x, y } => {} |
577 | } | 561 | } |
578 | } | 562 | } |
579 | "#, | 563 | "#, |
@@ -612,8 +596,8 @@ mod tests { | |||
612 | enum E { X, Y } | 596 | enum E { X, Y } |
613 | 597 | ||
614 | fn main() { | 598 | fn main() { |
615 | match <|>E::X { | 599 | match E::X { |
616 | E::X => {} | 600 | $0E::X => {} |
617 | E::Y => {} | 601 | E::Y => {} |
618 | } | 602 | } |
619 | } | 603 | } |
@@ -640,8 +624,8 @@ mod tests { | |||
640 | use foo::E::X; | 624 | use foo::E::X; |
641 | 625 | ||
642 | fn main() { | 626 | fn main() { |
643 | match <|>X { | 627 | match X { |
644 | X => {} | 628 | $0X => {} |
645 | foo::E::Y => {} | 629 | foo::E::Y => {} |
646 | } | 630 | } |
647 | } | 631 | } |
@@ -654,10 +638,7 @@ mod tests { | |||
654 | check_assist( | 638 | check_assist( |
655 | fill_match_arms, | 639 | fill_match_arms, |
656 | r#" | 640 | r#" |
657 | enum A { | 641 | enum A { One, Two } |
658 | One, | ||
659 | Two, | ||
660 | } | ||
661 | fn foo(a: A) { | 642 | fn foo(a: A) { |
662 | match a { | 643 | match a { |
663 | // foo bar baz<|> | 644 | // foo bar baz<|> |
@@ -667,16 +648,13 @@ mod tests { | |||
667 | } | 648 | } |
668 | "#, | 649 | "#, |
669 | r#" | 650 | r#" |
670 | enum A { | 651 | enum A { One, Two } |
671 | One, | ||
672 | Two, | ||
673 | } | ||
674 | fn foo(a: A) { | 652 | fn foo(a: A) { |
675 | match <|>a { | 653 | match a { |
676 | // foo bar baz | 654 | // foo bar baz |
677 | A::One => {} | 655 | A::One => {} |
678 | // This is where the rest should be | 656 | // This is where the rest should be |
679 | A::Two => {} | 657 | $0A::Two => {} |
680 | } | 658 | } |
681 | } | 659 | } |
682 | "#, | 660 | "#, |
@@ -688,10 +666,7 @@ mod tests { | |||
688 | check_assist( | 666 | check_assist( |
689 | fill_match_arms, | 667 | fill_match_arms, |
690 | r#" | 668 | r#" |
691 | enum A { | 669 | enum A { One, Two } |
692 | One, | ||
693 | Two, | ||
694 | } | ||
695 | fn foo(a: A) { | 670 | fn foo(a: A) { |
696 | match a { | 671 | match a { |
697 | // foo bar baz<|> | 672 | // foo bar baz<|> |
@@ -699,14 +674,11 @@ mod tests { | |||
699 | } | 674 | } |
700 | "#, | 675 | "#, |
701 | r#" | 676 | r#" |
702 | enum A { | 677 | enum A { One, Two } |
703 | One, | ||
704 | Two, | ||
705 | } | ||
706 | fn foo(a: A) { | 678 | fn foo(a: A) { |
707 | match <|>a { | 679 | match a { |
708 | // foo bar baz | 680 | // foo bar baz |
709 | A::One => {} | 681 | $0A::One => {} |
710 | A::Two => {} | 682 | A::Two => {} |
711 | } | 683 | } |
712 | } | 684 | } |
@@ -729,12 +701,37 @@ mod tests { | |||
729 | r#" | 701 | r#" |
730 | enum A { One, Two, } | 702 | enum A { One, Two, } |
731 | fn foo(a: A) { | 703 | fn foo(a: A) { |
732 | match <|>a { | 704 | match a { |
733 | A::One => {} | 705 | $0A::One => {} |
734 | A::Two => {} | 706 | A::Two => {} |
735 | } | 707 | } |
736 | } | 708 | } |
737 | "#, | 709 | "#, |
738 | ); | 710 | ); |
739 | } | 711 | } |
712 | |||
713 | #[test] | ||
714 | fn option_order() { | ||
715 | mark::check!(option_order); | ||
716 | let before = r#" | ||
717 | fn foo(opt: Option<i32>) { | ||
718 | match opt<|> { | ||
719 | } | ||
720 | }"#; | ||
721 | let before = | ||
722 | &format!("//- main.rs crate:main deps:core\n{}{}", before, FamousDefs::FIXTURE); | ||
723 | |||
724 | check_assist( | ||
725 | fill_match_arms, | ||
726 | before, | ||
727 | r#" | ||
728 | fn foo(opt: Option<i32>) { | ||
729 | match opt { | ||
730 | $0Some(_) => {} | ||
731 | None => {} | ||
732 | } | ||
733 | } | ||
734 | "#, | ||
735 | ); | ||
736 | } | ||
740 | } | 737 | } |
diff --git a/crates/ra_assists/src/handlers/fix_visibility.rs b/crates/ra_assists/src/handlers/fix_visibility.rs new file mode 100644 index 000000000..9ec42f568 --- /dev/null +++ b/crates/ra_assists/src/handlers/fix_visibility.rs | |||
@@ -0,0 +1,559 @@ | |||
1 | use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; | ||
2 | use ra_db::FileId; | ||
3 | use ra_syntax::{ | ||
4 | ast, AstNode, | ||
5 | SyntaxKind::{ATTR, COMMENT, WHITESPACE}, | ||
6 | SyntaxNode, TextRange, TextSize, | ||
7 | }; | ||
8 | |||
9 | use crate::{AssistContext, AssistId, Assists}; | ||
10 | |||
11 | // FIXME: this really should be a fix for diagnostic, rather than an assist. | ||
12 | |||
13 | // Assist: fix_visibility | ||
14 | // | ||
15 | // Makes inaccessible item public. | ||
16 | // | ||
17 | // ``` | ||
18 | // mod m { | ||
19 | // fn frobnicate() {} | ||
20 | // } | ||
21 | // fn main() { | ||
22 | // m::frobnicate<|>() {} | ||
23 | // } | ||
24 | // ``` | ||
25 | // -> | ||
26 | // ``` | ||
27 | // mod m { | ||
28 | // $0pub(crate) fn frobnicate() {} | ||
29 | // } | ||
30 | // fn main() { | ||
31 | // m::frobnicate() {} | ||
32 | // } | ||
33 | // ``` | ||
34 | pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
35 | add_vis_to_referenced_module_def(acc, ctx) | ||
36 | .or_else(|| add_vis_to_referenced_record_field(acc, ctx)) | ||
37 | } | ||
38 | |||
39 | fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
40 | let path: ast::Path = ctx.find_node_at_offset()?; | ||
41 | let path_res = ctx.sema.resolve_path(&path)?; | ||
42 | let def = match path_res { | ||
43 | PathResolution::Def(def) => def, | ||
44 | _ => return None, | ||
45 | }; | ||
46 | |||
47 | let current_module = ctx.sema.scope(&path.syntax()).module()?; | ||
48 | let target_module = def.module(ctx.db)?; | ||
49 | |||
50 | let vis = target_module.visibility_of(ctx.db, &def)?; | ||
51 | if vis.is_visible_from(ctx.db, current_module.into()) { | ||
52 | return None; | ||
53 | }; | ||
54 | |||
55 | let (offset, target, target_file, target_name) = target_data_for_def(ctx.db, def)?; | ||
56 | |||
57 | let missing_visibility = | ||
58 | if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; | ||
59 | |||
60 | let assist_label = match target_name { | ||
61 | None => format!("Change visibility to {}", missing_visibility), | ||
62 | Some(name) => format!("Change visibility of {} to {}", name, missing_visibility), | ||
63 | }; | ||
64 | |||
65 | acc.add(AssistId("fix_visibility"), assist_label, target, |builder| { | ||
66 | builder.set_file(target_file); | ||
67 | match ctx.config.snippet_cap { | ||
68 | Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), | ||
69 | None => builder.insert(offset, format!("{} ", missing_visibility)), | ||
70 | } | ||
71 | }) | ||
72 | } | ||
73 | |||
74 | fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
75 | let record_field: ast::RecordField = ctx.find_node_at_offset()?; | ||
76 | let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?; | ||
77 | |||
78 | let current_module = ctx.sema.scope(record_field.syntax()).module()?; | ||
79 | let visibility = record_field_def.visibility(ctx.db); | ||
80 | if visibility.is_visible_from(ctx.db, current_module.into()) { | ||
81 | return None; | ||
82 | } | ||
83 | |||
84 | let parent = record_field_def.parent_def(ctx.db); | ||
85 | let parent_name = parent.name(ctx.db); | ||
86 | let target_module = parent.module(ctx.db); | ||
87 | |||
88 | let in_file_source = record_field_def.source(ctx.db); | ||
89 | let (offset, target) = match in_file_source.value { | ||
90 | hir::FieldSource::Named(it) => { | ||
91 | let s = it.syntax(); | ||
92 | (vis_offset(s), s.text_range()) | ||
93 | } | ||
94 | hir::FieldSource::Pos(it) => { | ||
95 | let s = it.syntax(); | ||
96 | (vis_offset(s), s.text_range()) | ||
97 | } | ||
98 | }; | ||
99 | |||
100 | let missing_visibility = | ||
101 | if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; | ||
102 | let target_file = in_file_source.file_id.original_file(ctx.db); | ||
103 | |||
104 | let target_name = record_field_def.name(ctx.db); | ||
105 | let assist_label = | ||
106 | format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility); | ||
107 | |||
108 | acc.add(AssistId("fix_visibility"), assist_label, target, |builder| { | ||
109 | builder.set_file(target_file); | ||
110 | match ctx.config.snippet_cap { | ||
111 | Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), | ||
112 | None => builder.insert(offset, format!("{} ", missing_visibility)), | ||
113 | } | ||
114 | }) | ||
115 | } | ||
116 | |||
117 | fn target_data_for_def( | ||
118 | db: &dyn HirDatabase, | ||
119 | def: hir::ModuleDef, | ||
120 | ) -> Option<(TextSize, TextRange, FileId, Option<hir::Name>)> { | ||
121 | fn offset_target_and_file_id<S, Ast>( | ||
122 | db: &dyn HirDatabase, | ||
123 | x: S, | ||
124 | ) -> (TextSize, TextRange, FileId) | ||
125 | where | ||
126 | S: HasSource<Ast = Ast>, | ||
127 | Ast: AstNode, | ||
128 | { | ||
129 | let source = x.source(db); | ||
130 | let in_file_syntax = source.syntax(); | ||
131 | let file_id = in_file_syntax.file_id; | ||
132 | let syntax = in_file_syntax.value; | ||
133 | (vis_offset(syntax), syntax.text_range(), file_id.original_file(db.upcast())) | ||
134 | } | ||
135 | |||
136 | let target_name; | ||
137 | let (offset, target, target_file) = match def { | ||
138 | hir::ModuleDef::Function(f) => { | ||
139 | target_name = Some(f.name(db)); | ||
140 | offset_target_and_file_id(db, f) | ||
141 | } | ||
142 | hir::ModuleDef::Adt(adt) => { | ||
143 | target_name = Some(adt.name(db)); | ||
144 | match adt { | ||
145 | hir::Adt::Struct(s) => offset_target_and_file_id(db, s), | ||
146 | hir::Adt::Union(u) => offset_target_and_file_id(db, u), | ||
147 | hir::Adt::Enum(e) => offset_target_and_file_id(db, e), | ||
148 | } | ||
149 | } | ||
150 | hir::ModuleDef::Const(c) => { | ||
151 | target_name = c.name(db); | ||
152 | offset_target_and_file_id(db, c) | ||
153 | } | ||
154 | hir::ModuleDef::Static(s) => { | ||
155 | target_name = s.name(db); | ||
156 | offset_target_and_file_id(db, s) | ||
157 | } | ||
158 | hir::ModuleDef::Trait(t) => { | ||
159 | target_name = Some(t.name(db)); | ||
160 | offset_target_and_file_id(db, t) | ||
161 | } | ||
162 | hir::ModuleDef::TypeAlias(t) => { | ||
163 | target_name = Some(t.name(db)); | ||
164 | offset_target_and_file_id(db, t) | ||
165 | } | ||
166 | hir::ModuleDef::Module(m) => { | ||
167 | target_name = m.name(db); | ||
168 | let in_file_source = m.declaration_source(db)?; | ||
169 | let file_id = in_file_source.file_id.original_file(db.upcast()); | ||
170 | let syntax = in_file_source.value.syntax(); | ||
171 | (vis_offset(syntax), syntax.text_range(), file_id) | ||
172 | } | ||
173 | // Enum variants can't be private, we can't modify builtin types | ||
174 | hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None, | ||
175 | }; | ||
176 | |||
177 | Some((offset, target, target_file, target_name)) | ||
178 | } | ||
179 | |||
180 | fn vis_offset(node: &SyntaxNode) -> TextSize { | ||
181 | node.children_with_tokens() | ||
182 | .skip_while(|it| match it.kind() { | ||
183 | WHITESPACE | COMMENT | ATTR => true, | ||
184 | _ => false, | ||
185 | }) | ||
186 | .next() | ||
187 | .map(|it| it.text_range().start()) | ||
188 | .unwrap_or_else(|| node.text_range().start()) | ||
189 | } | ||
190 | |||
191 | #[cfg(test)] | ||
192 | mod tests { | ||
193 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
194 | |||
195 | use super::*; | ||
196 | |||
197 | #[test] | ||
198 | fn fix_visibility_of_fn() { | ||
199 | check_assist( | ||
200 | fix_visibility, | ||
201 | r"mod foo { fn foo() {} } | ||
202 | fn main() { foo::foo<|>() } ", | ||
203 | r"mod foo { $0pub(crate) fn foo() {} } | ||
204 | fn main() { foo::foo() } ", | ||
205 | ); | ||
206 | check_assist_not_applicable( | ||
207 | fix_visibility, | ||
208 | r"mod foo { pub fn foo() {} } | ||
209 | fn main() { foo::foo<|>() } ", | ||
210 | ) | ||
211 | } | ||
212 | |||
213 | #[test] | ||
214 | fn fix_visibility_of_adt_in_submodule() { | ||
215 | check_assist( | ||
216 | fix_visibility, | ||
217 | r"mod foo { struct Foo; } | ||
218 | fn main() { foo::Foo<|> } ", | ||
219 | r"mod foo { $0pub(crate) struct Foo; } | ||
220 | fn main() { foo::Foo } ", | ||
221 | ); | ||
222 | check_assist_not_applicable( | ||
223 | fix_visibility, | ||
224 | r"mod foo { pub struct Foo; } | ||
225 | fn main() { foo::Foo<|> } ", | ||
226 | ); | ||
227 | check_assist( | ||
228 | fix_visibility, | ||
229 | r"mod foo { enum Foo; } | ||
230 | fn main() { foo::Foo<|> } ", | ||
231 | r"mod foo { $0pub(crate) enum Foo; } | ||
232 | fn main() { foo::Foo } ", | ||
233 | ); | ||
234 | check_assist_not_applicable( | ||
235 | fix_visibility, | ||
236 | r"mod foo { pub enum Foo; } | ||
237 | fn main() { foo::Foo<|> } ", | ||
238 | ); | ||
239 | check_assist( | ||
240 | fix_visibility, | ||
241 | r"mod foo { union Foo; } | ||
242 | fn main() { foo::Foo<|> } ", | ||
243 | r"mod foo { $0pub(crate) union Foo; } | ||
244 | fn main() { foo::Foo } ", | ||
245 | ); | ||
246 | check_assist_not_applicable( | ||
247 | fix_visibility, | ||
248 | r"mod foo { pub union Foo; } | ||
249 | fn main() { foo::Foo<|> } ", | ||
250 | ); | ||
251 | } | ||
252 | |||
253 | #[test] | ||
254 | fn fix_visibility_of_adt_in_other_file() { | ||
255 | check_assist( | ||
256 | fix_visibility, | ||
257 | r" | ||
258 | //- /main.rs | ||
259 | mod foo; | ||
260 | fn main() { foo::Foo<|> } | ||
261 | |||
262 | //- /foo.rs | ||
263 | struct Foo; | ||
264 | ", | ||
265 | r"$0pub(crate) struct Foo; | ||
266 | |||
267 | ", | ||
268 | ); | ||
269 | } | ||
270 | |||
271 | #[test] | ||
272 | fn fix_visibility_of_struct_field() { | ||
273 | check_assist( | ||
274 | fix_visibility, | ||
275 | r"mod foo { pub struct Foo { bar: (), } } | ||
276 | fn main() { foo::Foo { <|>bar: () }; } ", | ||
277 | r"mod foo { pub struct Foo { $0pub(crate) bar: (), } } | ||
278 | fn main() { foo::Foo { bar: () }; } ", | ||
279 | ); | ||
280 | check_assist( | ||
281 | fix_visibility, | ||
282 | r"//- /lib.rs | ||
283 | mod foo; | ||
284 | fn main() { foo::Foo { <|>bar: () }; } | ||
285 | //- /foo.rs | ||
286 | pub struct Foo { bar: () } | ||
287 | ", | ||
288 | r"pub struct Foo { $0pub(crate) bar: () } | ||
289 | |||
290 | ", | ||
291 | ); | ||
292 | check_assist_not_applicable( | ||
293 | fix_visibility, | ||
294 | r"mod foo { pub struct Foo { pub bar: (), } } | ||
295 | fn main() { foo::Foo { <|>bar: () }; } ", | ||
296 | ); | ||
297 | check_assist_not_applicable( | ||
298 | fix_visibility, | ||
299 | r"//- /lib.rs | ||
300 | mod foo; | ||
301 | fn main() { foo::Foo { <|>bar: () }; } | ||
302 | //- /foo.rs | ||
303 | pub struct Foo { pub bar: () } | ||
304 | ", | ||
305 | ); | ||
306 | } | ||
307 | |||
308 | #[test] | ||
309 | fn fix_visibility_of_enum_variant_field() { | ||
310 | check_assist( | ||
311 | fix_visibility, | ||
312 | r"mod foo { pub enum Foo { Bar { bar: () } } } | ||
313 | fn main() { foo::Foo::Bar { <|>bar: () }; } ", | ||
314 | r"mod foo { pub enum Foo { Bar { $0pub(crate) bar: () } } } | ||
315 | fn main() { foo::Foo::Bar { bar: () }; } ", | ||
316 | ); | ||
317 | check_assist( | ||
318 | fix_visibility, | ||
319 | r"//- /lib.rs | ||
320 | mod foo; | ||
321 | fn main() { foo::Foo::Bar { <|>bar: () }; } | ||
322 | //- /foo.rs | ||
323 | pub enum Foo { Bar { bar: () } } | ||
324 | ", | ||
325 | r"pub enum Foo { Bar { $0pub(crate) bar: () } } | ||
326 | |||
327 | ", | ||
328 | ); | ||
329 | check_assist_not_applicable( | ||
330 | fix_visibility, | ||
331 | r"mod foo { pub struct Foo { pub bar: (), } } | ||
332 | fn main() { foo::Foo { <|>bar: () }; } ", | ||
333 | ); | ||
334 | check_assist_not_applicable( | ||
335 | fix_visibility, | ||
336 | r"//- /lib.rs | ||
337 | mod foo; | ||
338 | fn main() { foo::Foo { <|>bar: () }; } | ||
339 | //- /foo.rs | ||
340 | pub struct Foo { pub bar: () } | ||
341 | ", | ||
342 | ); | ||
343 | } | ||
344 | |||
345 | #[test] | ||
346 | #[ignore] | ||
347 | // FIXME reenable this test when `Semantics::resolve_record_field` works with union fields | ||
348 | fn fix_visibility_of_union_field() { | ||
349 | check_assist( | ||
350 | fix_visibility, | ||
351 | r"mod foo { pub union Foo { bar: (), } } | ||
352 | fn main() { foo::Foo { <|>bar: () }; } ", | ||
353 | r"mod foo { pub union Foo { $0pub(crate) bar: (), } } | ||
354 | fn main() { foo::Foo { bar: () }; } ", | ||
355 | ); | ||
356 | check_assist( | ||
357 | fix_visibility, | ||
358 | r"//- /lib.rs | ||
359 | mod foo; | ||
360 | fn main() { foo::Foo { <|>bar: () }; } | ||
361 | //- /foo.rs | ||
362 | pub union Foo { bar: () } | ||
363 | ", | ||
364 | r"pub union Foo { $0pub(crate) bar: () } | ||
365 | |||
366 | ", | ||
367 | ); | ||
368 | check_assist_not_applicable( | ||
369 | fix_visibility, | ||
370 | r"mod foo { pub union Foo { pub bar: (), } } | ||
371 | fn main() { foo::Foo { <|>bar: () }; } ", | ||
372 | ); | ||
373 | check_assist_not_applicable( | ||
374 | fix_visibility, | ||
375 | r"//- /lib.rs | ||
376 | mod foo; | ||
377 | fn main() { foo::Foo { <|>bar: () }; } | ||
378 | //- /foo.rs | ||
379 | pub union Foo { pub bar: () } | ||
380 | ", | ||
381 | ); | ||
382 | } | ||
383 | |||
384 | #[test] | ||
385 | fn fix_visibility_of_const() { | ||
386 | check_assist( | ||
387 | fix_visibility, | ||
388 | r"mod foo { const FOO: () = (); } | ||
389 | fn main() { foo::FOO<|> } ", | ||
390 | r"mod foo { $0pub(crate) const FOO: () = (); } | ||
391 | fn main() { foo::FOO } ", | ||
392 | ); | ||
393 | check_assist_not_applicable( | ||
394 | fix_visibility, | ||
395 | r"mod foo { pub const FOO: () = (); } | ||
396 | fn main() { foo::FOO<|> } ", | ||
397 | ); | ||
398 | } | ||
399 | |||
400 | #[test] | ||
401 | fn fix_visibility_of_static() { | ||
402 | check_assist( | ||
403 | fix_visibility, | ||
404 | r"mod foo { static FOO: () = (); } | ||
405 | fn main() { foo::FOO<|> } ", | ||
406 | r"mod foo { $0pub(crate) static FOO: () = (); } | ||
407 | fn main() { foo::FOO } ", | ||
408 | ); | ||
409 | check_assist_not_applicable( | ||
410 | fix_visibility, | ||
411 | r"mod foo { pub static FOO: () = (); } | ||
412 | fn main() { foo::FOO<|> } ", | ||
413 | ); | ||
414 | } | ||
415 | |||
416 | #[test] | ||
417 | fn fix_visibility_of_trait() { | ||
418 | check_assist( | ||
419 | fix_visibility, | ||
420 | r"mod foo { trait Foo { fn foo(&self) {} } } | ||
421 | fn main() { let x: &dyn foo::<|>Foo; } ", | ||
422 | r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } } | ||
423 | fn main() { let x: &dyn foo::Foo; } ", | ||
424 | ); | ||
425 | check_assist_not_applicable( | ||
426 | fix_visibility, | ||
427 | r"mod foo { pub trait Foo { fn foo(&self) {} } } | ||
428 | fn main() { let x: &dyn foo::Foo<|>; } ", | ||
429 | ); | ||
430 | } | ||
431 | |||
432 | #[test] | ||
433 | fn fix_visibility_of_type_alias() { | ||
434 | check_assist( | ||
435 | fix_visibility, | ||
436 | r"mod foo { type Foo = (); } | ||
437 | fn main() { let x: foo::Foo<|>; } ", | ||
438 | r"mod foo { $0pub(crate) type Foo = (); } | ||
439 | fn main() { let x: foo::Foo; } ", | ||
440 | ); | ||
441 | check_assist_not_applicable( | ||
442 | fix_visibility, | ||
443 | r"mod foo { pub type Foo = (); } | ||
444 | fn main() { let x: foo::Foo<|>; } ", | ||
445 | ); | ||
446 | } | ||
447 | |||
448 | #[test] | ||
449 | fn fix_visibility_of_module() { | ||
450 | check_assist( | ||
451 | fix_visibility, | ||
452 | r"mod foo { mod bar { fn bar() {} } } | ||
453 | fn main() { foo::bar<|>::bar(); } ", | ||
454 | r"mod foo { $0pub(crate) mod bar { fn bar() {} } } | ||
455 | fn main() { foo::bar::bar(); } ", | ||
456 | ); | ||
457 | |||
458 | check_assist( | ||
459 | fix_visibility, | ||
460 | r" | ||
461 | //- /main.rs | ||
462 | mod foo; | ||
463 | fn main() { foo::bar<|>::baz(); } | ||
464 | |||
465 | //- /foo.rs | ||
466 | mod bar { | ||
467 | pub fn baz() {} | ||
468 | } | ||
469 | ", | ||
470 | r"$0pub(crate) mod bar { | ||
471 | pub fn baz() {} | ||
472 | } | ||
473 | |||
474 | ", | ||
475 | ); | ||
476 | |||
477 | check_assist_not_applicable( | ||
478 | fix_visibility, | ||
479 | r"mod foo { pub mod bar { pub fn bar() {} } } | ||
480 | fn main() { foo::bar<|>::bar(); } ", | ||
481 | ); | ||
482 | } | ||
483 | |||
484 | #[test] | ||
485 | fn fix_visibility_of_inline_module_in_other_file() { | ||
486 | check_assist( | ||
487 | fix_visibility, | ||
488 | r" | ||
489 | //- /main.rs | ||
490 | mod foo; | ||
491 | fn main() { foo::bar<|>::baz(); } | ||
492 | |||
493 | //- /foo.rs | ||
494 | mod bar; | ||
495 | |||
496 | //- /foo/bar.rs | ||
497 | pub fn baz() {} | ||
498 | } | ||
499 | ", | ||
500 | r"$0pub(crate) mod bar; | ||
501 | ", | ||
502 | ); | ||
503 | } | ||
504 | |||
505 | #[test] | ||
506 | fn fix_visibility_of_module_declaration_in_other_file() { | ||
507 | check_assist( | ||
508 | fix_visibility, | ||
509 | r"//- /main.rs | ||
510 | mod foo; | ||
511 | fn main() { foo::bar<|>>::baz(); } | ||
512 | |||
513 | //- /foo.rs | ||
514 | mod bar { | ||
515 | pub fn baz() {} | ||
516 | }", | ||
517 | r"$0pub(crate) mod bar { | ||
518 | pub fn baz() {} | ||
519 | } | ||
520 | ", | ||
521 | ); | ||
522 | } | ||
523 | |||
524 | #[test] | ||
525 | fn adds_pub_when_target_is_in_another_crate() { | ||
526 | check_assist( | ||
527 | fix_visibility, | ||
528 | r"//- /main.rs crate:a deps:foo | ||
529 | foo::Bar<|> | ||
530 | //- /lib.rs crate:foo | ||
531 | struct Bar;", | ||
532 | r"$0pub struct Bar; | ||
533 | ", | ||
534 | ) | ||
535 | } | ||
536 | |||
537 | #[test] | ||
538 | #[ignore] | ||
539 | // FIXME handle reexports properly | ||
540 | fn fix_visibility_of_reexport() { | ||
541 | check_assist( | ||
542 | fix_visibility, | ||
543 | r" | ||
544 | mod foo { | ||
545 | use bar::Baz; | ||
546 | mod bar { pub(super) struct Baz; } | ||
547 | } | ||
548 | foo::Baz<|> | ||
549 | ", | ||
550 | r" | ||
551 | mod foo { | ||
552 | $0pub(crate) use bar::Baz; | ||
553 | mod bar { pub(super) struct Baz; } | ||
554 | } | ||
555 | foo::Baz | ||
556 | ", | ||
557 | ) | ||
558 | } | ||
559 | } | ||
diff --git a/crates/ra_assists/src/handlers/flip_binexpr.rs b/crates/ra_assists/src/handlers/flip_binexpr.rs index 8030efb35..573196576 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() { |
@@ -86,17 +85,13 @@ mod tests { | |||
86 | check_assist( | 85 | check_assist( |
87 | flip_binexpr, | 86 | flip_binexpr, |
88 | "fn f() { let res = 1 ==<|> 2; }", | 87 | "fn f() { let res = 1 ==<|> 2; }", |
89 | "fn f() { let res = 2 ==<|> 1; }", | 88 | "fn f() { let res = 2 == 1; }", |
90 | ) | 89 | ) |
91 | } | 90 | } |
92 | 91 | ||
93 | #[test] | 92 | #[test] |
94 | fn flip_binexpr_works_for_gt() { | 93 | fn flip_binexpr_works_for_gt() { |
95 | check_assist( | 94 | check_assist(flip_binexpr, "fn f() { let res = 1 ><|> 2; }", "fn f() { let res = 2 < 1; }") |
96 | flip_binexpr, | ||
97 | "fn f() { let res = 1 ><|> 2; }", | ||
98 | "fn f() { let res = 2 <<|> 1; }", | ||
99 | ) | ||
100 | } | 95 | } |
101 | 96 | ||
102 | #[test] | 97 | #[test] |
@@ -104,7 +99,7 @@ mod tests { | |||
104 | check_assist( | 99 | check_assist( |
105 | flip_binexpr, | 100 | flip_binexpr, |
106 | "fn f() { let res = 1 <=<|> 2; }", | 101 | "fn f() { let res = 1 <=<|> 2; }", |
107 | "fn f() { let res = 2 >=<|> 1; }", | 102 | "fn f() { let res = 2 >= 1; }", |
108 | ) | 103 | ) |
109 | } | 104 | } |
110 | 105 | ||
@@ -113,7 +108,7 @@ mod tests { | |||
113 | check_assist( | 108 | check_assist( |
114 | flip_binexpr, | 109 | flip_binexpr, |
115 | "fn f() { let res = (1 + 1) ==<|> (2 + 2); }", | 110 | "fn f() { let res = (1 + 1) ==<|> (2 + 2); }", |
116 | "fn f() { let res = (2 + 2) ==<|> (1 + 1); }", | 111 | "fn f() { let res = (2 + 2) == (1 + 1); }", |
117 | ) | 112 | ) |
118 | } | 113 | } |
119 | 114 | ||
@@ -133,7 +128,7 @@ mod tests { | |||
133 | fn dyn_eq(&self, other: &dyn Diagnostic) -> bool { | 128 | fn dyn_eq(&self, other: &dyn Diagnostic) -> bool { |
134 | match other.downcast_ref::<Self>() { | 129 | match other.downcast_ref::<Self>() { |
135 | None => false, | 130 | None => false, |
136 | Some(it) => self ==<|> it, | 131 | Some(it) => self == it, |
137 | } | 132 | } |
138 | } | 133 | } |
139 | "#, | 134 | "#, |
diff --git a/crates/ra_assists/src/handlers/flip_comma.rs b/crates/ra_assists/src/handlers/flip_comma.rs index 1dacf29f8..a57a1c463 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,14 +38,14 @@ 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() { |
46 | check_assist( | 45 | check_assist( |
47 | flip_comma, | 46 | flip_comma, |
48 | "fn foo(x: i32,<|> y: Result<(), ()>) {}", | 47 | "fn foo(x: i32,<|> y: Result<(), ()>) {}", |
49 | "fn foo(y: Result<(), ()>,<|> x: i32) {}", | 48 | "fn foo(y: Result<(), ()>, x: i32) {}", |
50 | ) | 49 | ) |
51 | } | 50 | } |
52 | 51 | ||
diff --git a/crates/ra_assists/src/handlers/flip_trait_bound.rs b/crates/ra_assists/src/handlers/flip_trait_bound.rs index f56769624..0115adc8b 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() { |
@@ -60,7 +60,7 @@ mod tests { | |||
60 | check_assist( | 60 | check_assist( |
61 | flip_trait_bound, | 61 | flip_trait_bound, |
62 | "struct S<T> where T: A <|>+ B { }", | 62 | "struct S<T> where T: A <|>+ B { }", |
63 | "struct S<T> where T: B <|>+ A { }", | 63 | "struct S<T> where T: B + A { }", |
64 | ) | 64 | ) |
65 | } | 65 | } |
66 | 66 | ||
@@ -69,13 +69,13 @@ mod tests { | |||
69 | check_assist( | 69 | check_assist( |
70 | flip_trait_bound, | 70 | flip_trait_bound, |
71 | "impl X for S<T> where T: A +<|> B { }", | 71 | "impl X for S<T> where T: A +<|> B { }", |
72 | "impl X for S<T> where T: B +<|> A { }", | 72 | "impl X for S<T> where T: B + A { }", |
73 | ) | 73 | ) |
74 | } | 74 | } |
75 | 75 | ||
76 | #[test] | 76 | #[test] |
77 | fn flip_trait_bound_works_for_fn() { | 77 | fn flip_trait_bound_works_for_fn() { |
78 | check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B <|>+ A>(t: T) { }") | 78 | check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B + A>(t: T) { }") |
79 | } | 79 | } |
80 | 80 | ||
81 | #[test] | 81 | #[test] |
@@ -83,7 +83,7 @@ mod tests { | |||
83 | check_assist( | 83 | check_assist( |
84 | flip_trait_bound, | 84 | flip_trait_bound, |
85 | "fn f<T>(t: T) where T: A +<|> B { }", | 85 | "fn f<T>(t: T) where T: A +<|> B { }", |
86 | "fn f<T>(t: T) where T: B +<|> A { }", | 86 | "fn f<T>(t: T) where T: B + A { }", |
87 | ) | 87 | ) |
88 | } | 88 | } |
89 | 89 | ||
@@ -92,7 +92,7 @@ mod tests { | |||
92 | check_assist( | 92 | check_assist( |
93 | flip_trait_bound, | 93 | flip_trait_bound, |
94 | "fn f<T>(t: T) where T: A <|>+ 'static { }", | 94 | "fn f<T>(t: T) where T: A <|>+ 'static { }", |
95 | "fn f<T>(t: T) where T: 'static <|>+ A { }", | 95 | "fn f<T>(t: T) where T: 'static + A { }", |
96 | ) | 96 | ) |
97 | } | 97 | } |
98 | 98 | ||
@@ -101,7 +101,7 @@ mod tests { | |||
101 | check_assist( | 101 | check_assist( |
102 | flip_trait_bound, | 102 | flip_trait_bound, |
103 | "struct S<T> where T: A<T> <|>+ b_mod::B<T> + C<T> { }", | 103 | "struct S<T> where T: A<T> <|>+ b_mod::B<T> + C<T> { }", |
104 | "struct S<T> where T: b_mod::B<T> <|>+ A<T> + C<T> { }", | 104 | "struct S<T> where T: b_mod::B<T> + A<T> + C<T> { }", |
105 | ) | 105 | ) |
106 | } | 106 | } |
107 | 107 | ||
@@ -110,7 +110,7 @@ mod tests { | |||
110 | check_assist( | 110 | check_assist( |
111 | flip_trait_bound, | 111 | flip_trait_bound, |
112 | "struct S<T> where T: A + B + C + D + E + F +<|> G + H + I + J { }", | 112 | "struct S<T> where T: A + B + C + D + E + F +<|> G + H + I + J { }", |
113 | "struct S<T> where T: A + B + C + D + E + G +<|> F + H + I + J { }", | 113 | "struct S<T> where T: A + B + C + D + E + G + F + H + I + J { }", |
114 | ) | 114 | ) |
115 | } | 115 | } |
116 | } | 116 | } |
diff --git a/crates/ra_assists/src/handlers/inline_local_variable.rs b/crates/ra_assists/src/handlers/inline_local_variable.rs index f5702f6e0..d26e68847 100644 --- a/crates/ra_assists/src/handlers/inline_local_variable.rs +++ b/crates/ra_assists/src/handlers/inline_local_variable.rs | |||
@@ -3,9 +3,12 @@ use ra_syntax::{ | |||
3 | ast::{self, AstNode, AstToken}, | 3 | ast::{self, AstNode, AstToken}, |
4 | TextRange, | 4 | TextRange, |
5 | }; | 5 | }; |
6 | use test_utils::tested_by; | 6 | use test_utils::mark; |
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,18 +26,18 @@ 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, |
30 | _ => return None, | 33 | _ => return None, |
31 | }; | 34 | }; |
32 | if bind_pat.mut_token().is_some() { | 35 | if bind_pat.mut_token().is_some() { |
33 | tested_by!(test_not_inline_mut_variable); | 36 | mark::hit!(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 | mark::hit!(not_applicable_outside_of_bind_pat); |
38 | return None; | 41 | return None; |
39 | } | 42 | } |
40 | let initializer_expr = let_stmt.initializer()?; | 43 | let initializer_expr = let_stmt.initializer()?; |
@@ -43,7 +46,7 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> { | |||
43 | let def = Definition::Local(def); | 46 | let def = Definition::Local(def); |
44 | let refs = def.find_usages(ctx.db, None); | 47 | let refs = def.find_usages(ctx.db, None); |
45 | if refs.is_empty() { | 48 | if refs.is_empty() { |
46 | tested_by!(test_not_applicable_if_variable_unused); | 49 | mark::hit!(test_not_applicable_if_variable_unused); |
47 | return None; | 50 | return None; |
48 | }; | 51 | }; |
49 | 52 | ||
@@ -89,6 +92,7 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> { | |||
89 | | (ast::Expr::ParenExpr(_), _) | 92 | | (ast::Expr::ParenExpr(_), _) |
90 | | (ast::Expr::PathExpr(_), _) | 93 | | (ast::Expr::PathExpr(_), _) |
91 | | (ast::Expr::BlockExpr(_), _) | 94 | | (ast::Expr::BlockExpr(_), _) |
95 | | (ast::Expr::EffectExpr(_), _) | ||
92 | | (_, ast::Expr::CallExpr(_)) | 96 | | (_, ast::Expr::CallExpr(_)) |
93 | | (_, ast::Expr::TupleExpr(_)) | 97 | | (_, ast::Expr::TupleExpr(_)) |
94 | | (_, ast::Expr::ArrayExpr(_)) | 98 | | (_, ast::Expr::ArrayExpr(_)) |
@@ -105,26 +109,21 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> { | |||
105 | let init_str = initializer_expr.syntax().text().to_string(); | 109 | let init_str = initializer_expr.syntax().text().to_string(); |
106 | let init_in_paren = format!("({})", &init_str); | 110 | let init_in_paren = format!("({})", &init_str); |
107 | 111 | ||
108 | ctx.add_assist( | 112 | let target = bind_pat.syntax().text_range(); |
109 | AssistId("inline_local_variable"), | 113 | acc.add(AssistId("inline_local_variable"), "Inline variable", target, move |builder| { |
110 | "Inline variable", | 114 | builder.delete(delete_range); |
111 | move |edit: &mut ActionBuilder| { | 115 | for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { |
112 | edit.delete(delete_range); | 116 | let replacement = if should_wrap { init_in_paren.clone() } else { init_str.clone() }; |
113 | for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { | 117 | builder.replace(desc.file_range.range, replacement) |
114 | let replacement = | 118 | } |
115 | if should_wrap { init_in_paren.clone() } else { init_str.clone() }; | 119 | }) |
116 | edit.replace(desc.file_range.range, replacement) | ||
117 | } | ||
118 | edit.set_cursor(delete_range.start()) | ||
119 | }, | ||
120 | ) | ||
121 | } | 120 | } |
122 | 121 | ||
123 | #[cfg(test)] | 122 | #[cfg(test)] |
124 | mod tests { | 123 | mod tests { |
125 | use test_utils::covers; | 124 | use test_utils::mark; |
126 | 125 | ||
127 | use crate::helpers::{check_assist, check_assist_not_applicable}; | 126 | use crate::tests::{check_assist, check_assist_not_applicable}; |
128 | 127 | ||
129 | use super::*; | 128 | use super::*; |
130 | 129 | ||
@@ -149,7 +148,7 @@ fn foo() { | |||
149 | r" | 148 | r" |
150 | fn bar(a: usize) {} | 149 | fn bar(a: usize) {} |
151 | fn foo() { | 150 | fn foo() { |
152 | <|>1 + 1; | 151 | 1 + 1; |
153 | if 1 > 10 { | 152 | if 1 > 10 { |
154 | } | 153 | } |
155 | 154 | ||
@@ -183,7 +182,7 @@ fn foo() { | |||
183 | r" | 182 | r" |
184 | fn bar(a: usize) {} | 183 | fn bar(a: usize) {} |
185 | fn foo() { | 184 | fn foo() { |
186 | <|>(1 + 1) + 1; | 185 | (1 + 1) + 1; |
187 | if (1 + 1) > 10 { | 186 | if (1 + 1) > 10 { |
188 | } | 187 | } |
189 | 188 | ||
@@ -217,7 +216,7 @@ fn foo() { | |||
217 | r" | 216 | r" |
218 | fn bar(a: usize) {} | 217 | fn bar(a: usize) {} |
219 | fn foo() { | 218 | fn foo() { |
220 | <|>bar(1) + 1; | 219 | bar(1) + 1; |
221 | if bar(1) > 10 { | 220 | if bar(1) > 10 { |
222 | } | 221 | } |
223 | 222 | ||
@@ -251,7 +250,7 @@ fn foo() { | |||
251 | r" | 250 | r" |
252 | fn bar(a: usize): usize { a } | 251 | fn bar(a: usize): usize { a } |
253 | fn foo() { | 252 | fn foo() { |
254 | <|>(bar(1) as u64) + 1; | 253 | (bar(1) as u64) + 1; |
255 | if (bar(1) as u64) > 10 { | 254 | if (bar(1) as u64) > 10 { |
256 | } | 255 | } |
257 | 256 | ||
@@ -283,7 +282,7 @@ fn foo() { | |||
283 | }", | 282 | }", |
284 | r" | 283 | r" |
285 | fn foo() { | 284 | fn foo() { |
286 | <|>{ 10 + 1 } + 1; | 285 | { 10 + 1 } + 1; |
287 | if { 10 + 1 } > 10 { | 286 | if { 10 + 1 } > 10 { |
288 | } | 287 | } |
289 | 288 | ||
@@ -315,7 +314,7 @@ fn foo() { | |||
315 | }", | 314 | }", |
316 | r" | 315 | r" |
317 | fn foo() { | 316 | fn foo() { |
318 | <|>( 10 + 1 ) + 1; | 317 | ( 10 + 1 ) + 1; |
319 | if ( 10 + 1 ) > 10 { | 318 | if ( 10 + 1 ) > 10 { |
320 | } | 319 | } |
321 | 320 | ||
@@ -330,7 +329,7 @@ fn foo() { | |||
330 | 329 | ||
331 | #[test] | 330 | #[test] |
332 | fn test_not_inline_mut_variable() { | 331 | fn test_not_inline_mut_variable() { |
333 | covers!(test_not_inline_mut_variable); | 332 | mark::check!(test_not_inline_mut_variable); |
334 | check_assist_not_applicable( | 333 | check_assist_not_applicable( |
335 | inline_local_variable, | 334 | inline_local_variable, |
336 | r" | 335 | r" |
@@ -353,7 +352,7 @@ fn foo() { | |||
353 | }", | 352 | }", |
354 | r" | 353 | r" |
355 | fn foo() { | 354 | fn foo() { |
356 | <|>let b = bar(10 + 1) * 10; | 355 | let b = bar(10 + 1) * 10; |
357 | let c = bar(10 + 1) as usize; | 356 | let c = bar(10 + 1) as usize; |
358 | }", | 357 | }", |
359 | ); | 358 | ); |
@@ -373,7 +372,7 @@ fn foo() { | |||
373 | r" | 372 | r" |
374 | fn foo() { | 373 | fn foo() { |
375 | let x = vec![1, 2, 3]; | 374 | let x = vec![1, 2, 3]; |
376 | <|>let b = x[0] * 10; | 375 | let b = x[0] * 10; |
377 | let c = x[0] as usize; | 376 | let c = x[0] as usize; |
378 | }", | 377 | }", |
379 | ); | 378 | ); |
@@ -393,7 +392,7 @@ fn foo() { | |||
393 | r" | 392 | r" |
394 | fn foo() { | 393 | fn foo() { |
395 | let bar = vec![1]; | 394 | let bar = vec![1]; |
396 | <|>let b = bar.len() * 10; | 395 | let b = bar.len() * 10; |
397 | let c = bar.len() as usize; | 396 | let c = bar.len() as usize; |
398 | }", | 397 | }", |
399 | ); | 398 | ); |
@@ -421,7 +420,7 @@ struct Bar { | |||
421 | 420 | ||
422 | fn foo() { | 421 | fn foo() { |
423 | let bar = Bar { foo: 1 }; | 422 | let bar = Bar { foo: 1 }; |
424 | <|>let b = bar.foo * 10; | 423 | let b = bar.foo * 10; |
425 | let c = bar.foo as usize; | 424 | let c = bar.foo as usize; |
426 | }", | 425 | }", |
427 | ); | 426 | ); |
@@ -442,7 +441,7 @@ fn foo() -> Option<usize> { | |||
442 | r" | 441 | r" |
443 | fn foo() -> Option<usize> { | 442 | fn foo() -> Option<usize> { |
444 | let bar = Some(1); | 443 | let bar = Some(1); |
445 | <|>let b = bar? * 10; | 444 | let b = bar? * 10; |
446 | let c = bar? as usize; | 445 | let c = bar? as usize; |
447 | None | 446 | None |
448 | }", | 447 | }", |
@@ -462,7 +461,7 @@ fn foo() { | |||
462 | r" | 461 | r" |
463 | fn foo() { | 462 | fn foo() { |
464 | let bar = 10; | 463 | let bar = 10; |
465 | <|>let b = &bar * 10; | 464 | let b = &bar * 10; |
466 | }", | 465 | }", |
467 | ); | 466 | ); |
468 | } | 467 | } |
@@ -478,7 +477,7 @@ fn foo() { | |||
478 | }", | 477 | }", |
479 | r" | 478 | r" |
480 | fn foo() { | 479 | fn foo() { |
481 | <|>let b = (10, 20)[0]; | 480 | let b = (10, 20)[0]; |
482 | }", | 481 | }", |
483 | ); | 482 | ); |
484 | } | 483 | } |
@@ -494,7 +493,7 @@ fn foo() { | |||
494 | }", | 493 | }", |
495 | r" | 494 | r" |
496 | fn foo() { | 495 | fn foo() { |
497 | <|>let b = [1, 2, 3].len(); | 496 | let b = [1, 2, 3].len(); |
498 | }", | 497 | }", |
499 | ); | 498 | ); |
500 | } | 499 | } |
@@ -511,7 +510,7 @@ fn foo() { | |||
511 | }", | 510 | }", |
512 | r" | 511 | r" |
513 | fn foo() { | 512 | fn foo() { |
514 | <|>let b = (10 + 20) * 10; | 513 | let b = (10 + 20) * 10; |
515 | let c = (10 + 20) as usize; | 514 | let c = (10 + 20) as usize; |
516 | }", | 515 | }", |
517 | ); | 516 | ); |
@@ -531,7 +530,7 @@ fn foo() { | |||
531 | r" | 530 | r" |
532 | fn foo() { | 531 | fn foo() { |
533 | let d = 10; | 532 | let d = 10; |
534 | <|>let b = d * 10; | 533 | let b = d * 10; |
535 | let c = d as usize; | 534 | let c = d as usize; |
536 | }", | 535 | }", |
537 | ); | 536 | ); |
@@ -549,7 +548,7 @@ fn foo() { | |||
549 | }", | 548 | }", |
550 | r" | 549 | r" |
551 | fn foo() { | 550 | fn foo() { |
552 | <|>let b = { 10 } * 10; | 551 | let b = { 10 } * 10; |
553 | let c = { 10 } as usize; | 552 | let c = { 10 } as usize; |
554 | }", | 553 | }", |
555 | ); | 554 | ); |
@@ -569,7 +568,7 @@ fn foo() { | |||
569 | }", | 568 | }", |
570 | r" | 569 | r" |
571 | fn foo() { | 570 | fn foo() { |
572 | <|>let b = (10 + 20) * 10; | 571 | let b = (10 + 20) * 10; |
573 | let c = (10 + 20, 20); | 572 | let c = (10 + 20, 20); |
574 | let d = [10 + 20, 10]; | 573 | let d = [10 + 20, 10]; |
575 | let e = (10 + 20); | 574 | let e = (10 + 20); |
@@ -588,7 +587,7 @@ fn foo() { | |||
588 | }", | 587 | }", |
589 | r" | 588 | r" |
590 | fn foo() { | 589 | fn foo() { |
591 | <|>for i in vec![10, 20] {} | 590 | for i in vec![10, 20] {} |
592 | }", | 591 | }", |
593 | ); | 592 | ); |
594 | } | 593 | } |
@@ -604,7 +603,7 @@ fn foo() { | |||
604 | }", | 603 | }", |
605 | r" | 604 | r" |
606 | fn foo() { | 605 | fn foo() { |
607 | <|>while 1 > 0 {} | 606 | while 1 > 0 {} |
608 | }", | 607 | }", |
609 | ); | 608 | ); |
610 | } | 609 | } |
@@ -622,7 +621,7 @@ fn foo() { | |||
622 | }", | 621 | }", |
623 | r" | 622 | r" |
624 | fn foo() { | 623 | fn foo() { |
625 | <|>loop { | 624 | loop { |
626 | break 1 + 1; | 625 | break 1 + 1; |
627 | } | 626 | } |
628 | }", | 627 | }", |
@@ -640,7 +639,7 @@ fn foo() { | |||
640 | }", | 639 | }", |
641 | r" | 640 | r" |
642 | fn foo() { | 641 | fn foo() { |
643 | <|>return 1 > 0; | 642 | return 1 > 0; |
644 | }", | 643 | }", |
645 | ); | 644 | ); |
646 | } | 645 | } |
@@ -656,14 +655,14 @@ fn foo() { | |||
656 | }", | 655 | }", |
657 | r" | 656 | r" |
658 | fn foo() { | 657 | fn foo() { |
659 | <|>match 1 > 0 {} | 658 | match 1 > 0 {} |
660 | }", | 659 | }", |
661 | ); | 660 | ); |
662 | } | 661 | } |
663 | 662 | ||
664 | #[test] | 663 | #[test] |
665 | fn test_not_applicable_if_variable_unused() { | 664 | fn test_not_applicable_if_variable_unused() { |
666 | covers!(test_not_applicable_if_variable_unused); | 665 | mark::check!(test_not_applicable_if_variable_unused); |
667 | check_assist_not_applicable( | 666 | check_assist_not_applicable( |
668 | inline_local_variable, | 667 | inline_local_variable, |
669 | r" | 668 | r" |
@@ -676,7 +675,7 @@ fn foo() { | |||
676 | 675 | ||
677 | #[test] | 676 | #[test] |
678 | fn not_applicable_outside_of_bind_pat() { | 677 | fn not_applicable_outside_of_bind_pat() { |
679 | covers!(not_applicable_outside_of_bind_pat); | 678 | mark::check!(not_applicable_outside_of_bind_pat); |
680 | check_assist_not_applicable( | 679 | check_assist_not_applicable( |
681 | inline_local_variable, | 680 | inline_local_variable, |
682 | r" | 681 | r" |
diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/introduce_variable.rs index eda9ac296..31d6539f7 100644 --- a/crates/ra_assists/src/handlers/introduce_variable.rs +++ b/crates/ra_assists/src/handlers/introduce_variable.rs | |||
@@ -4,12 +4,12 @@ use ra_syntax::{ | |||
4 | BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, | 4 | BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, |
5 | WHITESPACE, | 5 | WHITESPACE, |
6 | }, | 6 | }, |
7 | SyntaxNode, TextSize, | 7 | SyntaxNode, |
8 | }; | 8 | }; |
9 | use stdx::format_to; | 9 | use stdx::format_to; |
10 | use test_utils::tested_by; | 10 | use test_utils::mark; |
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 | // |
@@ -23,17 +23,17 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
23 | // -> | 23 | // -> |
24 | // ``` | 24 | // ``` |
25 | // fn main() { | 25 | // fn main() { |
26 | // let var_name = (1 + 2); | 26 | // let $0var_name = (1 + 2); |
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 | } |
34 | let node = ctx.covering_element(); | 34 | let node = ctx.covering_element(); |
35 | if node.kind() == COMMENT { | 35 | if node.kind() == COMMENT { |
36 | tested_by!(introduce_var_in_comment_is_not_applicable); | 36 | mark::hit!(introduce_var_in_comment_is_not_applicable); |
37 | return None; | 37 | return None; |
38 | } | 38 | } |
39 | let expr = node.ancestors().find_map(valid_target_expr)?; | 39 | let expr = node.ancestors().find_map(valid_target_expr)?; |
@@ -42,17 +42,17 @@ 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 | if wrap_in_block { |
49 | buf.push_str("{ let var_name = "); | 50 | buf.push_str("{ let var_name = "); |
50 | TextSize::of("{ let ") | ||
51 | } else { | 51 | } else { |
52 | buf.push_str("let var_name = "); | 52 | buf.push_str("let var_name = "); |
53 | TextSize::of("let ") | ||
54 | }; | 53 | }; |
55 | format_to!(buf, "{}", expr.syntax()); | 54 | format_to!(buf, "{}", expr.syntax()); |
55 | |||
56 | let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); | 56 | let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); |
57 | let is_full_stmt = if let Some(expr_stmt) = &full_stmt { | 57 | let is_full_stmt = if let Some(expr_stmt) = &full_stmt { |
58 | Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) | 58 | Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) |
@@ -60,33 +60,47 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> { | |||
60 | false | 60 | false |
61 | }; | 61 | }; |
62 | if is_full_stmt { | 62 | if is_full_stmt { |
63 | tested_by!(test_introduce_var_expr_stmt); | 63 | mark::hit!(test_introduce_var_expr_stmt); |
64 | if full_stmt.unwrap().semicolon_token().is_none() { | 64 | if full_stmt.unwrap().semicolon_token().is_none() { |
65 | buf.push_str(";"); | 65 | buf.push_str(";"); |
66 | } | 66 | } |
67 | edit.replace(expr.syntax().text_range(), buf); | 67 | let offset = expr.syntax().text_range(); |
68 | } else { | 68 | match ctx.config.snippet_cap { |
69 | buf.push_str(";"); | 69 | Some(cap) => { |
70 | 70 | let snip = buf.replace("let var_name", "let $0var_name"); | |
71 | // We want to maintain the indent level, | 71 | edit.replace_snippet(cap, offset, snip) |
72 | // but we do not want to duplicate possible | 72 | } |
73 | // extra newlines in the indent block | 73 | None => edit.replace(offset, buf), |
74 | let text = indent.text(); | ||
75 | if text.starts_with('\n') { | ||
76 | buf.push_str("\n"); | ||
77 | buf.push_str(text.trim_start_matches('\n')); | ||
78 | } else { | ||
79 | buf.push_str(text); | ||
80 | } | 74 | } |
75 | return; | ||
76 | } | ||
81 | 77 | ||
82 | edit.target(expr.syntax().text_range()); | 78 | buf.push_str(";"); |
83 | edit.replace(expr.syntax().text_range(), "var_name".to_string()); | 79 | |
84 | edit.insert(anchor_stmt.text_range().start(), buf); | 80 | // We want to maintain the indent level, |
85 | if wrap_in_block { | 81 | // but we do not want to duplicate possible |
86 | edit.insert(anchor_stmt.text_range().end(), " }"); | 82 | // extra newlines in the indent block |
83 | let text = indent.text(); | ||
84 | if text.starts_with('\n') { | ||
85 | buf.push_str("\n"); | ||
86 | buf.push_str(text.trim_start_matches('\n')); | ||
87 | } else { | ||
88 | buf.push_str(text); | ||
89 | } | ||
90 | |||
91 | edit.replace(expr.syntax().text_range(), "var_name".to_string()); | ||
92 | let offset = anchor_stmt.text_range().start(); | ||
93 | match ctx.config.snippet_cap { | ||
94 | Some(cap) => { | ||
95 | let snip = buf.replace("let var_name", "let $0var_name"); | ||
96 | edit.insert_snippet(cap, offset, snip) | ||
87 | } | 97 | } |
98 | None => edit.insert(offset, buf), | ||
99 | } | ||
100 | |||
101 | if wrap_in_block { | ||
102 | edit.insert(anchor_stmt.text_range().end(), " }"); | ||
88 | } | 103 | } |
89 | edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset); | ||
90 | }) | 104 | }) |
91 | } | 105 | } |
92 | 106 | ||
@@ -111,9 +125,9 @@ fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> { | |||
111 | /// expression like a lambda or match arm. | 125 | /// expression like a lambda or match arm. |
112 | fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> { | 126 | fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> { |
113 | expr.syntax().ancestors().find_map(|node| { | 127 | expr.syntax().ancestors().find_map(|node| { |
114 | if let Some(expr) = node.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) { | 128 | if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) { |
115 | if expr.syntax() == &node { | 129 | if expr.syntax() == &node { |
116 | tested_by!(test_introduce_var_last_expr); | 130 | mark::hit!(test_introduce_var_last_expr); |
117 | return Some((node, false)); | 131 | return Some((node, false)); |
118 | } | 132 | } |
119 | } | 133 | } |
@@ -134,9 +148,9 @@ fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> { | |||
134 | 148 | ||
135 | #[cfg(test)] | 149 | #[cfg(test)] |
136 | mod tests { | 150 | mod tests { |
137 | use test_utils::covers; | 151 | use test_utils::mark; |
138 | 152 | ||
139 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | 153 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
140 | 154 | ||
141 | use super::*; | 155 | use super::*; |
142 | 156 | ||
@@ -144,37 +158,37 @@ mod tests { | |||
144 | fn test_introduce_var_simple() { | 158 | fn test_introduce_var_simple() { |
145 | check_assist( | 159 | check_assist( |
146 | introduce_variable, | 160 | introduce_variable, |
147 | " | 161 | r#" |
148 | fn foo() { | 162 | fn foo() { |
149 | foo(<|>1 + 1<|>); | 163 | foo(<|>1 + 1<|>); |
150 | }", | 164 | }"#, |
151 | " | 165 | r#" |
152 | fn foo() { | 166 | fn foo() { |
153 | let <|>var_name = 1 + 1; | 167 | let $0var_name = 1 + 1; |
154 | foo(var_name); | 168 | foo(var_name); |
155 | }", | 169 | }"#, |
156 | ); | 170 | ); |
157 | } | 171 | } |
158 | 172 | ||
159 | #[test] | 173 | #[test] |
160 | fn introduce_var_in_comment_is_not_applicable() { | 174 | fn introduce_var_in_comment_is_not_applicable() { |
161 | covers!(introduce_var_in_comment_is_not_applicable); | 175 | mark::check!(introduce_var_in_comment_is_not_applicable); |
162 | check_assist_not_applicable(introduce_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }"); | 176 | check_assist_not_applicable(introduce_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }"); |
163 | } | 177 | } |
164 | 178 | ||
165 | #[test] | 179 | #[test] |
166 | fn test_introduce_var_expr_stmt() { | 180 | fn test_introduce_var_expr_stmt() { |
167 | covers!(test_introduce_var_expr_stmt); | 181 | mark::check!(test_introduce_var_expr_stmt); |
168 | check_assist( | 182 | check_assist( |
169 | introduce_variable, | 183 | introduce_variable, |
170 | " | 184 | r#" |
171 | fn foo() { | 185 | fn foo() { |
172 | <|>1 + 1<|>; | 186 | <|>1 + 1<|>; |
173 | }", | 187 | }"#, |
174 | " | 188 | r#" |
175 | fn foo() { | 189 | fn foo() { |
176 | let <|>var_name = 1 + 1; | 190 | let $0var_name = 1 + 1; |
177 | }", | 191 | }"#, |
178 | ); | 192 | ); |
179 | check_assist( | 193 | check_assist( |
180 | introduce_variable, | 194 | introduce_variable, |
@@ -185,7 +199,7 @@ fn foo() { | |||
185 | }", | 199 | }", |
186 | " | 200 | " |
187 | fn foo() { | 201 | fn foo() { |
188 | let <|>var_name = { let x = 0; x }; | 202 | let $0var_name = { let x = 0; x }; |
189 | something_else(); | 203 | something_else(); |
190 | }", | 204 | }", |
191 | ); | 205 | ); |
@@ -201,7 +215,7 @@ fn foo() { | |||
201 | }", | 215 | }", |
202 | " | 216 | " |
203 | fn foo() { | 217 | fn foo() { |
204 | let <|>var_name = 1; | 218 | let $0var_name = 1; |
205 | var_name + 1; | 219 | var_name + 1; |
206 | }", | 220 | }", |
207 | ); | 221 | ); |
@@ -209,7 +223,7 @@ fn foo() { | |||
209 | 223 | ||
210 | #[test] | 224 | #[test] |
211 | fn test_introduce_var_last_expr() { | 225 | fn test_introduce_var_last_expr() { |
212 | covers!(test_introduce_var_last_expr); | 226 | mark::check!(test_introduce_var_last_expr); |
213 | check_assist( | 227 | check_assist( |
214 | introduce_variable, | 228 | introduce_variable, |
215 | " | 229 | " |
@@ -218,7 +232,7 @@ fn foo() { | |||
218 | }", | 232 | }", |
219 | " | 233 | " |
220 | fn foo() { | 234 | fn foo() { |
221 | let <|>var_name = 1 + 1; | 235 | let $0var_name = 1 + 1; |
222 | bar(var_name) | 236 | bar(var_name) |
223 | }", | 237 | }", |
224 | ); | 238 | ); |
@@ -230,7 +244,7 @@ fn foo() { | |||
230 | }", | 244 | }", |
231 | " | 245 | " |
232 | fn foo() { | 246 | fn foo() { |
233 | let <|>var_name = bar(1 + 1); | 247 | let $0var_name = bar(1 + 1); |
234 | var_name | 248 | var_name |
235 | }", | 249 | }", |
236 | ) | 250 | ) |
@@ -253,7 +267,7 @@ fn main() { | |||
253 | fn main() { | 267 | fn main() { |
254 | let x = true; | 268 | let x = true; |
255 | let tuple = match x { | 269 | let tuple = match x { |
256 | true => { let <|>var_name = 2 + 2; (var_name, true) } | 270 | true => { let $0var_name = 2 + 2; (var_name, true) } |
257 | _ => (0, false) | 271 | _ => (0, false) |
258 | }; | 272 | }; |
259 | } | 273 | } |
@@ -283,7 +297,7 @@ fn main() { | |||
283 | let tuple = match x { | 297 | let tuple = match x { |
284 | true => { | 298 | true => { |
285 | let y = 1; | 299 | let y = 1; |
286 | let <|>var_name = 2 + y; | 300 | let $0var_name = 2 + y; |
287 | (var_name, true) | 301 | (var_name, true) |
288 | } | 302 | } |
289 | _ => (0, false) | 303 | _ => (0, false) |
@@ -304,7 +318,7 @@ fn main() { | |||
304 | ", | 318 | ", |
305 | " | 319 | " |
306 | fn main() { | 320 | fn main() { |
307 | let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; | 321 | let lambda = |x: u32| { let $0var_name = x * 2; var_name }; |
308 | } | 322 | } |
309 | ", | 323 | ", |
310 | ); | 324 | ); |
@@ -321,7 +335,7 @@ fn main() { | |||
321 | ", | 335 | ", |
322 | " | 336 | " |
323 | fn main() { | 337 | fn main() { |
324 | let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; | 338 | let lambda = |x: u32| { let $0var_name = x * 2; var_name }; |
325 | } | 339 | } |
326 | ", | 340 | ", |
327 | ); | 341 | ); |
@@ -338,7 +352,7 @@ fn main() { | |||
338 | ", | 352 | ", |
339 | " | 353 | " |
340 | fn main() { | 354 | fn main() { |
341 | let <|>var_name = Some(true); | 355 | let $0var_name = Some(true); |
342 | let o = var_name; | 356 | let o = var_name; |
343 | } | 357 | } |
344 | ", | 358 | ", |
@@ -356,7 +370,7 @@ fn main() { | |||
356 | ", | 370 | ", |
357 | " | 371 | " |
358 | fn main() { | 372 | fn main() { |
359 | let <|>var_name = bar.foo(); | 373 | let $0var_name = bar.foo(); |
360 | let v = var_name; | 374 | let v = var_name; |
361 | } | 375 | } |
362 | ", | 376 | ", |
@@ -374,7 +388,7 @@ fn foo() -> u32 { | |||
374 | ", | 388 | ", |
375 | " | 389 | " |
376 | fn foo() -> u32 { | 390 | fn foo() -> u32 { |
377 | let <|>var_name = 2 + 2; | 391 | let $0var_name = 2 + 2; |
378 | return var_name; | 392 | return var_name; |
379 | } | 393 | } |
380 | ", | 394 | ", |
@@ -396,7 +410,7 @@ fn foo() -> u32 { | |||
396 | fn foo() -> u32 { | 410 | fn foo() -> u32 { |
397 | 411 | ||
398 | 412 | ||
399 | let <|>var_name = 2 + 2; | 413 | let $0var_name = 2 + 2; |
400 | return var_name; | 414 | return var_name; |
401 | } | 415 | } |
402 | ", | 416 | ", |
@@ -413,7 +427,7 @@ fn foo() -> u32 { | |||
413 | " | 427 | " |
414 | fn foo() -> u32 { | 428 | fn foo() -> u32 { |
415 | 429 | ||
416 | let <|>var_name = 2 + 2; | 430 | let $0var_name = 2 + 2; |
417 | return var_name; | 431 | return var_name; |
418 | } | 432 | } |
419 | ", | 433 | ", |
@@ -438,7 +452,7 @@ fn foo() -> u32 { | |||
438 | // bar | 452 | // bar |
439 | 453 | ||
440 | 454 | ||
441 | let <|>var_name = 2 + 2; | 455 | let $0var_name = 2 + 2; |
442 | return var_name; | 456 | return var_name; |
443 | } | 457 | } |
444 | ", | 458 | ", |
@@ -459,7 +473,7 @@ fn main() { | |||
459 | " | 473 | " |
460 | fn main() { | 474 | fn main() { |
461 | let result = loop { | 475 | let result = loop { |
462 | let <|>var_name = 2 + 2; | 476 | let $0var_name = 2 + 2; |
463 | break var_name; | 477 | break var_name; |
464 | }; | 478 | }; |
465 | } | 479 | } |
@@ -478,7 +492,7 @@ fn main() { | |||
478 | ", | 492 | ", |
479 | " | 493 | " |
480 | fn main() { | 494 | fn main() { |
481 | let <|>var_name = 0f32 as u32; | 495 | let $0var_name = 0f32 as u32; |
482 | let v = var_name; | 496 | let v = var_name; |
483 | } | 497 | } |
484 | ", | 498 | ", |
diff --git a/crates/ra_assists/src/handlers/invert_if.rs b/crates/ra_assists/src/handlers/invert_if.rs index 682e08512..59d278eb9 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,36 +44,35 @@ 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() { |
69 | check_assist( | 72 | check_assist( |
70 | invert_if, | 73 | invert_if, |
71 | "fn f() { i<|>f x != 3 { 1 } else { 3 + 2 } }", | 74 | "fn f() { i<|>f x != 3 { 1 } else { 3 + 2 } }", |
72 | "fn f() { i<|>f x == 3 { 3 + 2 } else { 1 } }", | 75 | "fn f() { if x == 3 { 3 + 2 } else { 1 } }", |
73 | ) | 76 | ) |
74 | } | 77 | } |
75 | 78 | ||
@@ -78,7 +81,7 @@ mod tests { | |||
78 | check_assist( | 81 | check_assist( |
79 | invert_if, | 82 | invert_if, |
80 | "fn f() { <|>if !cond { 3 * 2 } else { 1 } }", | 83 | "fn f() { <|>if !cond { 3 * 2 } else { 1 } }", |
81 | "fn f() { <|>if cond { 1 } else { 3 * 2 } }", | 84 | "fn f() { if cond { 1 } else { 3 * 2 } }", |
82 | ) | 85 | ) |
83 | } | 86 | } |
84 | 87 | ||
@@ -87,7 +90,7 @@ mod tests { | |||
87 | check_assist( | 90 | check_assist( |
88 | invert_if, | 91 | invert_if, |
89 | "fn f() { i<|>f cond { 3 * 2 } else { 1 } }", | 92 | "fn f() { i<|>f cond { 3 * 2 } else { 1 } }", |
90 | "fn f() { i<|>f !cond { 1 } else { 3 * 2 } }", | 93 | "fn f() { if !cond { 1 } else { 3 * 2 } }", |
91 | ) | 94 | ) |
92 | } | 95 | } |
93 | 96 | ||
diff --git a/crates/ra_assists/src/handlers/merge_imports.rs b/crates/ra_assists/src/handlers/merge_imports.rs index 4be1238f1..972d16241 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,9 @@ 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| { |
57 | // FIXME: we only need because our diff is imprecise | 60 | builder.rewrite(rewriter); |
58 | edit.set_cursor(offset); | ||
59 | }) | 61 | }) |
60 | } | 62 | } |
61 | 63 | ||
@@ -125,7 +127,7 @@ fn first_path(path: &ast::Path) -> ast::Path { | |||
125 | 127 | ||
126 | #[cfg(test)] | 128 | #[cfg(test)] |
127 | mod tests { | 129 | mod tests { |
128 | use crate::helpers::check_assist; | 130 | use crate::tests::check_assist; |
129 | 131 | ||
130 | use super::*; | 132 | use super::*; |
131 | 133 | ||
@@ -138,7 +140,7 @@ use std::fmt<|>::Debug; | |||
138 | use std::fmt::Display; | 140 | use std::fmt::Display; |
139 | ", | 141 | ", |
140 | r" | 142 | r" |
141 | use std::fmt<|>::{Debug, Display}; | 143 | use std::fmt::{Debug, Display}; |
142 | ", | 144 | ", |
143 | ) | 145 | ) |
144 | } | 146 | } |
@@ -152,7 +154,7 @@ use std::fmt::Debug; | |||
152 | use std::fmt<|>::Display; | 154 | use std::fmt<|>::Display; |
153 | ", | 155 | ", |
154 | r" | 156 | r" |
155 | use std::fmt:<|>:{Display, Debug}; | 157 | use std::fmt::{Display, Debug}; |
156 | ", | 158 | ", |
157 | ); | 159 | ); |
158 | } | 160 | } |
@@ -165,7 +167,7 @@ use std::fmt:<|>:{Display, Debug}; | |||
165 | use std::{fmt<|>::Debug, fmt::Display}; | 167 | use std::{fmt<|>::Debug, fmt::Display}; |
166 | ", | 168 | ", |
167 | r" | 169 | r" |
168 | use std::{fmt<|>::{Debug, Display}}; | 170 | use std::{fmt::{Debug, Display}}; |
169 | ", | 171 | ", |
170 | ); | 172 | ); |
171 | check_assist( | 173 | check_assist( |
@@ -174,7 +176,7 @@ use std::{fmt<|>::{Debug, Display}}; | |||
174 | use std::{fmt::Debug, fmt<|>::Display}; | 176 | use std::{fmt::Debug, fmt<|>::Display}; |
175 | ", | 177 | ", |
176 | r" | 178 | r" |
177 | use std::{fmt::<|>{Display, Debug}}; | 179 | use std::{fmt::{Display, Debug}}; |
178 | ", | 180 | ", |
179 | ); | 181 | ); |
180 | } | 182 | } |
@@ -188,7 +190,7 @@ use std<|>::cell::*; | |||
188 | use std::str; | 190 | use std::str; |
189 | ", | 191 | ", |
190 | r" | 192 | r" |
191 | use std<|>::{cell::*, str}; | 193 | use std::{cell::*, str}; |
192 | ", | 194 | ", |
193 | ) | 195 | ) |
194 | } | 196 | } |
@@ -202,7 +204,7 @@ use std<|>::cell::*; | |||
202 | use std::str::*; | 204 | use std::str::*; |
203 | ", | 205 | ", |
204 | r" | 206 | r" |
205 | use std<|>::{cell::*, str::*}; | 207 | use std::{cell::*, str::*}; |
206 | ", | 208 | ", |
207 | ) | 209 | ) |
208 | } | 210 | } |
@@ -218,7 +220,7 @@ use foo::baz; | |||
218 | /// Doc comment | 220 | /// Doc comment |
219 | ", | 221 | ", |
220 | r" | 222 | r" |
221 | use foo<|>::{bar, baz}; | 223 | use foo::{bar, baz}; |
222 | 224 | ||
223 | /// Doc comment | 225 | /// Doc comment |
224 | ", | 226 | ", |
@@ -237,7 +239,7 @@ use { | |||
237 | ", | 239 | ", |
238 | r" | 240 | r" |
239 | use { | 241 | use { |
240 | foo<|>::{bar, baz}, | 242 | foo::{bar, baz}, |
241 | }; | 243 | }; |
242 | ", | 244 | ", |
243 | ); | 245 | ); |
@@ -251,7 +253,7 @@ use { | |||
251 | ", | 253 | ", |
252 | r" | 254 | r" |
253 | use { | 255 | use { |
254 | foo::{bar<|>, baz}, | 256 | foo::{bar, baz}, |
255 | }; | 257 | }; |
256 | ", | 258 | ", |
257 | ); | 259 | ); |
@@ -268,7 +270,7 @@ use foo::<|>{ | |||
268 | }; | 270 | }; |
269 | ", | 271 | ", |
270 | r" | 272 | r" |
271 | use foo::{<|> | 273 | use foo::{ |
272 | FooBar, | 274 | FooBar, |
273 | bar::baz}; | 275 | bar::baz}; |
274 | ", | 276 | ", |
diff --git a/crates/ra_assists/src/handlers/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs index 5a77d3dbc..ca04ec671 100644 --- a/crates/ra_assists/src/handlers/merge_match_arms.rs +++ b/crates/ra_assists/src/handlers/merge_match_arms.rs | |||
@@ -3,10 +3,10 @@ use std::iter::successors; | |||
3 | use ra_syntax::{ | 3 | use ra_syntax::{ |
4 | algo::neighbor, | 4 | algo::neighbor, |
5 | ast::{self, AstNode}, | 5 | ast::{self, AstNode}, |
6 | Direction, TextSize, | 6 | Direction, |
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() { |
@@ -41,17 +41,6 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
41 | let current_expr = current_arm.expr()?; | 41 | let current_expr = current_arm.expr()?; |
42 | let current_text_range = current_arm.syntax().text_range(); | 42 | let current_text_range = current_arm.syntax().text_range(); |
43 | 43 | ||
44 | enum CursorPos { | ||
45 | InExpr(TextSize), | ||
46 | InPat(TextSize), | ||
47 | } | ||
48 | let cursor_pos = ctx.frange.range.start(); | ||
49 | let cursor_pos = if current_expr.syntax().text_range().contains(cursor_pos) { | ||
50 | CursorPos::InExpr(current_text_range.end() - cursor_pos) | ||
51 | } else { | ||
52 | CursorPos::InPat(cursor_pos) | ||
53 | }; | ||
54 | |||
55 | // We check if the following match arms match this one. We could, but don't, | 44 | // We check if the following match arms match this one. We could, but don't, |
56 | // compare to the previous match arm as well. | 45 | // compare to the previous match arm as well. |
57 | let arms_to_merge = successors(Some(current_arm), |it| neighbor(it, Direction::Next)) | 46 | let arms_to_merge = successors(Some(current_arm), |it| neighbor(it, Direction::Next)) |
@@ -70,7 +59,7 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
70 | return None; | 59 | return None; |
71 | } | 60 | } |
72 | 61 | ||
73 | ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| { | 62 | acc.add(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| { |
74 | let pats = if arms_to_merge.iter().any(contains_placeholder) { | 63 | let pats = if arms_to_merge.iter().any(contains_placeholder) { |
75 | "_".into() | 64 | "_".into() |
76 | } else { | 65 | } else { |
@@ -87,11 +76,6 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
87 | let start = arms_to_merge.first().unwrap().syntax().text_range().start(); | 76 | let start = arms_to_merge.first().unwrap().syntax().text_range().start(); |
88 | let end = arms_to_merge.last().unwrap().syntax().text_range().end(); | 77 | let end = arms_to_merge.last().unwrap().syntax().text_range().end(); |
89 | 78 | ||
90 | edit.target(current_text_range); | ||
91 | edit.set_cursor(match cursor_pos { | ||
92 | CursorPos::InExpr(back_offset) => start + TextSize::of(&arm) - back_offset, | ||
93 | CursorPos::InPat(offset) => offset, | ||
94 | }); | ||
95 | edit.replace(TextRange::new(start, end), arm); | 79 | edit.replace(TextRange::new(start, end), arm); |
96 | }) | 80 | }) |
97 | } | 81 | } |
@@ -105,7 +89,7 @@ fn contains_placeholder(a: &ast::MatchArm) -> bool { | |||
105 | 89 | ||
106 | #[cfg(test)] | 90 | #[cfg(test)] |
107 | mod tests { | 91 | mod tests { |
108 | use crate::helpers::{check_assist, check_assist_not_applicable}; | 92 | use crate::tests::{check_assist, check_assist_not_applicable}; |
109 | 93 | ||
110 | use super::*; | 94 | use super::*; |
111 | 95 | ||
@@ -133,7 +117,7 @@ mod tests { | |||
133 | fn main() { | 117 | fn main() { |
134 | let x = X::A; | 118 | let x = X::A; |
135 | let y = match x { | 119 | let y = match x { |
136 | X::A | X::B => { 1i32<|> } | 120 | X::A | X::B => { 1i32 } |
137 | X::C => { 2i32 } | 121 | X::C => { 2i32 } |
138 | } | 122 | } |
139 | } | 123 | } |
@@ -165,7 +149,7 @@ mod tests { | |||
165 | fn main() { | 149 | fn main() { |
166 | let x = X::A; | 150 | let x = X::A; |
167 | let y = match x { | 151 | let y = match x { |
168 | X::A | X::B | X::C | X::D => {<|> 1i32 }, | 152 | X::A | X::B | X::C | X::D => { 1i32 }, |
169 | X::E => { 2i32 }, | 153 | X::E => { 2i32 }, |
170 | } | 154 | } |
171 | } | 155 | } |
@@ -198,7 +182,7 @@ mod tests { | |||
198 | let x = X::A; | 182 | let x = X::A; |
199 | let y = match x { | 183 | let y = match x { |
200 | X::A => { 1i32 }, | 184 | X::A => { 1i32 }, |
201 | _ => { 2i<|>32 } | 185 | _ => { 2i32 } |
202 | } | 186 | } |
203 | } | 187 | } |
204 | "#, | 188 | "#, |
@@ -227,7 +211,7 @@ mod tests { | |||
227 | 211 | ||
228 | fn main() { | 212 | fn main() { |
229 | match X::A { | 213 | match X::A { |
230 | X::A<|> | X::B | X::C => 92, | 214 | X::A | X::B | X::C => 92, |
231 | X::D => 62, | 215 | X::D => 62, |
232 | _ => panic!(), | 216 | _ => panic!(), |
233 | } | 217 | } |
diff --git a/crates/ra_assists/src/handlers/move_bounds.rs b/crates/ra_assists/src/handlers/move_bounds.rs index 0f26884dc..be2a7eddc 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() { |
@@ -99,7 +99,7 @@ mod tests { | |||
99 | fn foo<T: u32, <|>F: FnOnce(T) -> T>() {} | 99 | fn foo<T: u32, <|>F: FnOnce(T) -> T>() {} |
100 | "#, | 100 | "#, |
101 | r#" | 101 | r#" |
102 | fn foo<T, <|>F>() where T: u32, F: FnOnce(T) -> T {} | 102 | fn foo<T, F>() where T: u32, F: FnOnce(T) -> T {} |
103 | "#, | 103 | "#, |
104 | ); | 104 | ); |
105 | } | 105 | } |
@@ -112,7 +112,7 @@ mod tests { | |||
112 | impl<U: u32, <|>T> A<U, T> {} | 112 | impl<U: u32, <|>T> A<U, T> {} |
113 | "#, | 113 | "#, |
114 | r#" | 114 | r#" |
115 | impl<U, <|>T> A<U, T> where U: u32 {} | 115 | impl<U, T> A<U, T> where U: u32 {} |
116 | "#, | 116 | "#, |
117 | ); | 117 | ); |
118 | } | 118 | } |
@@ -125,7 +125,7 @@ mod tests { | |||
125 | struct A<<|>T: Iterator<Item = u32>> {} | 125 | struct A<<|>T: Iterator<Item = u32>> {} |
126 | "#, | 126 | "#, |
127 | r#" | 127 | r#" |
128 | struct A<<|>T> where T: Iterator<Item = u32> {} | 128 | struct A<T> where T: Iterator<Item = u32> {} |
129 | "#, | 129 | "#, |
130 | ); | 130 | ); |
131 | } | 131 | } |
@@ -138,7 +138,7 @@ mod tests { | |||
138 | struct Pair<<|>T: u32>(T, T); | 138 | struct Pair<<|>T: u32>(T, T); |
139 | "#, | 139 | "#, |
140 | r#" | 140 | r#" |
141 | struct Pair<<|>T>(T, T) where T: u32; | 141 | struct Pair<T>(T, T) where T: u32; |
142 | "#, | 142 | "#, |
143 | ); | 143 | ); |
144 | } | 144 | } |
diff --git a/crates/ra_assists/src/handlers/move_guard.rs b/crates/ra_assists/src/handlers/move_guard.rs index d5ccdd91c..7edcf0748 100644 --- a/crates/ra_assists/src/handlers/move_guard.rs +++ b/crates/ra_assists/src/handlers/move_guard.rs | |||
@@ -1,10 +1,9 @@ | |||
1 | use ra_syntax::{ | 1 | use ra_syntax::{ |
2 | ast, | 2 | ast::{AstNode, IfExpr, MatchArm}, |
3 | ast::{AstNode, AstToken, IfExpr, MatchArm}, | 3 | SyntaxKind::WHITESPACE, |
4 | TextSize, | ||
5 | }; | 4 | }; |
6 | 5 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 6 | use crate::{AssistContext, AssistId, Assists}; |
8 | 7 | ||
9 | // Assist: move_guard_to_arm_body | 8 | // Assist: move_guard_to_arm_body |
10 | // | 9 | // |
@@ -31,7 +30,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
31 | // } | 30 | // } |
32 | // } | 31 | // } |
33 | // ``` | 32 | // ``` |
34 | pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> { | 33 | pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
35 | let match_arm = ctx.find_node_at_offset::<MatchArm>()?; | 34 | let match_arm = ctx.find_node_at_offset::<MatchArm>()?; |
36 | let guard = match_arm.guard()?; | 35 | let guard = match_arm.guard()?; |
37 | let space_before_guard = guard.syntax().prev_sibling_or_token(); | 36 | let space_before_guard = guard.syntax().prev_sibling_or_token(); |
@@ -40,26 +39,17 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> { | |||
40 | let arm_expr = match_arm.expr()?; | 39 | let arm_expr = match_arm.expr()?; |
41 | let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); | 40 | let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); |
42 | 41 | ||
43 | ctx.add_assist(AssistId("move_guard_to_arm_body"), "Move guard to arm body", |edit| { | 42 | let target = guard.syntax().text_range(); |
44 | edit.target(guard.syntax().text_range()); | 43 | 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()) { | 44 | match space_before_guard { |
46 | Some(tok) => { | 45 | Some(element) if element.kind() == WHITESPACE => { |
47 | if ast::Whitespace::cast(tok.clone()).is_some() { | 46 | edit.delete(element.text_range()); |
48 | let ele = tok.text_range(); | ||
49 | edit.delete(ele); | ||
50 | ele.len() | ||
51 | } else { | ||
52 | TextSize::from(0) | ||
53 | } | ||
54 | } | 47 | } |
55 | _ => TextSize::from(0), | 48 | _ => (), |
56 | }; | 49 | }; |
57 | 50 | ||
58 | edit.delete(guard.syntax().text_range()); | 51 | edit.delete(guard.syntax().text_range()); |
59 | edit.replace_node_and_indent(arm_expr.syntax(), buf); | 52 | edit.replace_node_and_indent(arm_expr.syntax(), buf); |
60 | edit.set_cursor( | ||
61 | arm_expr.syntax().text_range().start() + TextSize::from(3) - offseting_amount, | ||
62 | ); | ||
63 | }) | 53 | }) |
64 | } | 54 | } |
65 | 55 | ||
@@ -88,7 +78,7 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> { | |||
88 | // } | 78 | // } |
89 | // } | 79 | // } |
90 | // ``` | 80 | // ``` |
91 | pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> { | 81 | 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>()?; | 82 | let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?; |
93 | let match_pat = match_arm.pat()?; | 83 | let match_pat = match_arm.pat()?; |
94 | 84 | ||
@@ -108,14 +98,15 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> { | |||
108 | 98 | ||
109 | let buf = format!(" if {}", cond.syntax().text()); | 99 | let buf = format!(" if {}", cond.syntax().text()); |
110 | 100 | ||
111 | ctx.add_assist( | 101 | let target = if_expr.syntax().text_range(); |
102 | acc.add( | ||
112 | AssistId("move_arm_cond_to_match_guard"), | 103 | AssistId("move_arm_cond_to_match_guard"), |
113 | "Move condition to match guard", | 104 | "Move condition to match guard", |
105 | target, | ||
114 | |edit| { | 106 | |edit| { |
115 | edit.target(if_expr.syntax().text_range()); | 107 | let then_only_expr = then_block.statements().next().is_none(); |
116 | let then_only_expr = then_block.block().and_then(|it| it.statements().next()).is_none(); | ||
117 | 108 | ||
118 | match &then_block.block().and_then(|it| it.expr()) { | 109 | match &then_block.expr() { |
119 | Some(then_expr) if then_only_expr => { | 110 | Some(then_expr) if then_only_expr => { |
120 | edit.replace(if_expr.syntax().text_range(), then_expr.syntax().text()) | 111 | edit.replace(if_expr.syntax().text_range(), then_expr.syntax().text()) |
121 | } | 112 | } |
@@ -123,7 +114,6 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> { | |||
123 | } | 114 | } |
124 | 115 | ||
125 | edit.insert(match_pat.syntax().text_range().end(), buf); | 116 | edit.insert(match_pat.syntax().text_range().end(), buf); |
126 | edit.set_cursor(match_pat.syntax().text_range().end() + TextSize::from(1)); | ||
127 | }, | 117 | }, |
128 | ) | 118 | ) |
129 | } | 119 | } |
@@ -132,7 +122,7 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> { | |||
132 | mod tests { | 122 | mod tests { |
133 | use super::*; | 123 | use super::*; |
134 | 124 | ||
135 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | 125 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
136 | 126 | ||
137 | #[test] | 127 | #[test] |
138 | fn move_guard_to_arm_body_target() { | 128 | fn move_guard_to_arm_body_target() { |
@@ -171,7 +161,7 @@ mod tests { | |||
171 | let t = 'a'; | 161 | let t = 'a'; |
172 | let chars = "abcd"; | 162 | let chars = "abcd"; |
173 | match t { | 163 | match t { |
174 | '\r' => if chars.clone().next() == Some('\n') { <|>false }, | 164 | '\r' => if chars.clone().next() == Some('\n') { false }, |
175 | _ => true | 165 | _ => true |
176 | } | 166 | } |
177 | } | 167 | } |
@@ -194,7 +184,7 @@ mod tests { | |||
194 | r#" | 184 | r#" |
195 | fn f() { | 185 | fn f() { |
196 | match x { | 186 | match x { |
197 | y @ 4 | y @ 5 => if y > 5 { <|>true }, | 187 | y @ 4 | y @ 5 => if y > 5 { true }, |
198 | _ => false | 188 | _ => false |
199 | } | 189 | } |
200 | } | 190 | } |
@@ -221,7 +211,7 @@ mod tests { | |||
221 | let t = 'a'; | 211 | let t = 'a'; |
222 | let chars = "abcd"; | 212 | let chars = "abcd"; |
223 | match t { | 213 | match t { |
224 | '\r' <|>if chars.clone().next() == Some('\n') => false, | 214 | '\r' if chars.clone().next() == Some('\n') => false, |
225 | _ => true | 215 | _ => true |
226 | } | 216 | } |
227 | } | 217 | } |
@@ -265,7 +255,7 @@ mod tests { | |||
265 | let t = 'a'; | 255 | let t = 'a'; |
266 | let chars = "abcd"; | 256 | let chars = "abcd"; |
267 | match t { | 257 | match t { |
268 | '\r' <|>if chars.clone().next().is_some() => { }, | 258 | '\r' if chars.clone().next().is_some() => { }, |
269 | _ => true | 259 | _ => true |
270 | } | 260 | } |
271 | } | 261 | } |
@@ -295,7 +285,7 @@ mod tests { | |||
295 | let mut t = 'a'; | 285 | let mut t = 'a'; |
296 | let chars = "abcd"; | 286 | let chars = "abcd"; |
297 | match t { | 287 | match t { |
298 | '\r' <|>if chars.clone().next().is_some() => { | 288 | '\r' if chars.clone().next().is_some() => { |
299 | t = 'e'; | 289 | t = 'e'; |
300 | false | 290 | false |
301 | }, | 291 | }, |
diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs index 567400b9c..16002d2ac 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() { |
@@ -164,7 +164,7 @@ mod test { | |||
164 | "#, | 164 | "#, |
165 | r##" | 165 | r##" |
166 | fn f() { | 166 | fn f() { |
167 | let s = <|>r#"random | 167 | let s = r#"random |
168 | string"#; | 168 | string"#; |
169 | } | 169 | } |
170 | "##, | 170 | "##, |
@@ -182,7 +182,7 @@ string"#; | |||
182 | "#, | 182 | "#, |
183 | r##" | 183 | r##" |
184 | fn f() { | 184 | fn f() { |
185 | format!(<|>r#"x = {}"#, 92) | 185 | format!(r#"x = {}"#, 92) |
186 | } | 186 | } |
187 | "##, | 187 | "##, |
188 | ) | 188 | ) |
@@ -199,7 +199,7 @@ string"#; | |||
199 | "###, | 199 | "###, |
200 | r####" | 200 | r####" |
201 | fn f() { | 201 | fn f() { |
202 | let s = <|>r#"#random## | 202 | let s = r#"#random## |
203 | string"#; | 203 | string"#; |
204 | } | 204 | } |
205 | "####, | 205 | "####, |
@@ -217,7 +217,7 @@ string"#; | |||
217 | "###, | 217 | "###, |
218 | r####" | 218 | r####" |
219 | fn f() { | 219 | fn f() { |
220 | let s = <|>r###"#random"## | 220 | let s = r###"#random"## |
221 | string"###; | 221 | string"###; |
222 | } | 222 | } |
223 | "####, | 223 | "####, |
@@ -235,7 +235,7 @@ string"###; | |||
235 | "#, | 235 | "#, |
236 | r##" | 236 | r##" |
237 | fn f() { | 237 | fn f() { |
238 | let s = <|>r#"random string"#; | 238 | let s = r#"random string"#; |
239 | } | 239 | } |
240 | "##, | 240 | "##, |
241 | ) | 241 | ) |
@@ -289,7 +289,7 @@ string"###; | |||
289 | "#, | 289 | "#, |
290 | r##" | 290 | r##" |
291 | fn f() { | 291 | fn f() { |
292 | let s = <|>r#"random string"#; | 292 | let s = r#"random string"#; |
293 | } | 293 | } |
294 | "##, | 294 | "##, |
295 | ) | 295 | ) |
@@ -306,7 +306,7 @@ string"###; | |||
306 | "##, | 306 | "##, |
307 | r###" | 307 | r###" |
308 | fn f() { | 308 | fn f() { |
309 | let s = <|>r##"random"string"##; | 309 | let s = r##"random"string"##; |
310 | } | 310 | } |
311 | "###, | 311 | "###, |
312 | ) | 312 | ) |
@@ -348,7 +348,7 @@ string"###; | |||
348 | "##, | 348 | "##, |
349 | r#" | 349 | r#" |
350 | fn f() { | 350 | fn f() { |
351 | let s = <|>r"random string"; | 351 | let s = r"random string"; |
352 | } | 352 | } |
353 | "#, | 353 | "#, |
354 | ) | 354 | ) |
@@ -365,7 +365,7 @@ string"###; | |||
365 | "##, | 365 | "##, |
366 | r#" | 366 | r#" |
367 | fn f() { | 367 | fn f() { |
368 | let s = <|>r"random\"str\"ing"; | 368 | let s = r"random\"str\"ing"; |
369 | } | 369 | } |
370 | "#, | 370 | "#, |
371 | ) | 371 | ) |
@@ -382,7 +382,7 @@ string"###; | |||
382 | "###, | 382 | "###, |
383 | r##" | 383 | r##" |
384 | fn f() { | 384 | fn f() { |
385 | let s = <|>r#"random string"#; | 385 | let s = r#"random string"#; |
386 | } | 386 | } |
387 | "##, | 387 | "##, |
388 | ) | 388 | ) |
@@ -436,7 +436,7 @@ string"###; | |||
436 | "##, | 436 | "##, |
437 | r#" | 437 | r#" |
438 | fn f() { | 438 | fn f() { |
439 | let s = <|>"random string"; | 439 | let s = "random string"; |
440 | } | 440 | } |
441 | "#, | 441 | "#, |
442 | ) | 442 | ) |
@@ -453,7 +453,7 @@ string"###; | |||
453 | "##, | 453 | "##, |
454 | r#" | 454 | r#" |
455 | fn f() { | 455 | fn f() { |
456 | let s = <|>"random\"str\"ing"; | 456 | let s = "random\"str\"ing"; |
457 | } | 457 | } |
458 | "#, | 458 | "#, |
459 | ) | 459 | ) |
@@ -470,7 +470,7 @@ string"###; | |||
470 | "###, | 470 | "###, |
471 | r##" | 471 | r##" |
472 | fn f() { | 472 | fn f() { |
473 | let s = <|>"random string"; | 473 | let s = "random string"; |
474 | } | 474 | } |
475 | "##, | 475 | "##, |
476 | ) | 476 | ) |
diff --git a/crates/ra_assists/src/handlers/remove_dbg.rs b/crates/ra_assists/src/handlers/remove_dbg.rs index 4e5eb4350..961ee1731 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")? { |
@@ -29,26 +29,6 @@ pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> { | |||
29 | 29 | ||
30 | let macro_range = macro_call.syntax().text_range(); | 30 | let macro_range = macro_call.syntax().text_range(); |
31 | 31 | ||
32 | // If the cursor is inside the macro call, we'll try to maintain the cursor | ||
33 | // position by subtracting the length of dbg!( from the start of the file | ||
34 | // range, otherwise we'll default to using the start of the macro call | ||
35 | let cursor_pos = { | ||
36 | let file_range = ctx.frange.range; | ||
37 | |||
38 | let offset_start = file_range | ||
39 | .start() | ||
40 | .checked_sub(macro_range.start()) | ||
41 | .unwrap_or_else(|| TextSize::from(0)); | ||
42 | |||
43 | let dbg_size = TextSize::of("dbg!("); | ||
44 | |||
45 | if offset_start > dbg_size { | ||
46 | file_range.start() - dbg_size | ||
47 | } else { | ||
48 | macro_range.start() | ||
49 | } | ||
50 | }; | ||
51 | |||
52 | let macro_content = { | 32 | let macro_content = { |
53 | let macro_args = macro_call.token_tree()?.syntax().clone(); | 33 | let macro_args = macro_call.token_tree()?.syntax().clone(); |
54 | 34 | ||
@@ -57,10 +37,9 @@ pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> { | |||
57 | text.slice(without_parens).to_string() | 37 | text.slice(without_parens).to_string() |
58 | }; | 38 | }; |
59 | 39 | ||
60 | ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", |edit| { | 40 | let target = macro_call.syntax().text_range(); |
61 | edit.target(macro_call.syntax().text_range()); | 41 | acc.add(AssistId("remove_dbg"), "Remove dbg!()", target, |builder| { |
62 | edit.replace(macro_range, macro_content); | 42 | builder.replace(macro_range, macro_content); |
63 | edit.set_cursor(cursor_pos); | ||
64 | }) | 43 | }) |
65 | } | 44 | } |
66 | 45 | ||
@@ -90,17 +69,17 @@ fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<b | |||
90 | #[cfg(test)] | 69 | #[cfg(test)] |
91 | mod tests { | 70 | mod tests { |
92 | use super::*; | 71 | use super::*; |
93 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | 72 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
94 | 73 | ||
95 | #[test] | 74 | #[test] |
96 | fn test_remove_dbg() { | 75 | fn test_remove_dbg() { |
97 | check_assist(remove_dbg, "<|>dbg!(1 + 1)", "<|>1 + 1"); | 76 | check_assist(remove_dbg, "<|>dbg!(1 + 1)", "1 + 1"); |
98 | 77 | ||
99 | check_assist(remove_dbg, "dbg!<|>((1 + 1))", "<|>(1 + 1)"); | 78 | check_assist(remove_dbg, "dbg!<|>((1 + 1))", "(1 + 1)"); |
100 | 79 | ||
101 | check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 <|>+ 1"); | 80 | check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 + 1"); |
102 | 81 | ||
103 | check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = <|>1 + 1"); | 82 | check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = 1 + 1"); |
104 | 83 | ||
105 | check_assist( | 84 | check_assist( |
106 | remove_dbg, | 85 | remove_dbg, |
@@ -113,7 +92,7 @@ fn foo(n: usize) { | |||
113 | ", | 92 | ", |
114 | " | 93 | " |
115 | fn foo(n: usize) { | 94 | fn foo(n: usize) { |
116 | if let Some(_) = n.<|>checked_sub(4) { | 95 | if let Some(_) = n.checked_sub(4) { |
117 | // ... | 96 | // ... |
118 | } | 97 | } |
119 | } | 98 | } |
@@ -122,8 +101,8 @@ fn foo(n: usize) { | |||
122 | } | 101 | } |
123 | #[test] | 102 | #[test] |
124 | fn test_remove_dbg_with_brackets_and_braces() { | 103 | fn test_remove_dbg_with_brackets_and_braces() { |
125 | check_assist(remove_dbg, "dbg![<|>1 + 1]", "<|>1 + 1"); | 104 | check_assist(remove_dbg, "dbg![<|>1 + 1]", "1 + 1"); |
126 | check_assist(remove_dbg, "dbg!{<|>1 + 1}", "<|>1 + 1"); | 105 | check_assist(remove_dbg, "dbg!{<|>1 + 1}", "1 + 1"); |
127 | } | 106 | } |
128 | 107 | ||
129 | #[test] | 108 | #[test] |
diff --git a/crates/ra_assists/src/handlers/remove_mut.rs b/crates/ra_assists/src/handlers/remove_mut.rs index e598023b2..fe4eada03 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,8 +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 | edit.set_cursor(delete_from); | 29 | acc.add(AssistId("remove_mut"), "Remove `mut` keyword", target, |builder| { |
30 | edit.delete(TextRange::new(delete_from, delete_to)); | 30 | builder.delete(TextRange::new(delete_from, delete_to)); |
31 | }) | 31 | }) |
32 | } | 32 | } |
diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs index 5cbb98d73..30229edc2 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 | ||
@@ -149,7 +140,7 @@ mod tests { | |||
149 | "#, | 140 | "#, |
150 | r#" | 141 | r#" |
151 | struct Foo {foo: i32, bar: i32}; | 142 | struct Foo {foo: i32, bar: i32}; |
152 | const test: Foo = <|>Foo {foo: 1, bar: 0} | 143 | const test: Foo = Foo {foo: 1, bar: 0} |
153 | "#, | 144 | "#, |
154 | ) | 145 | ) |
155 | } | 146 | } |
@@ -173,7 +164,7 @@ mod tests { | |||
173 | 164 | ||
174 | fn f(f: Foo) -> { | 165 | fn f(f: Foo) -> { |
175 | match f { | 166 | match f { |
176 | <|>Foo { ref mut bar, baz: 0, .. } => (), | 167 | Foo { ref mut bar, baz: 0, .. } => (), |
177 | _ => () | 168 | _ => () |
178 | } | 169 | } |
179 | } | 170 | } |
@@ -211,7 +202,7 @@ mod tests { | |||
211 | impl Foo { | 202 | impl Foo { |
212 | fn new() -> Foo { | 203 | fn new() -> Foo { |
213 | let foo = String::new(); | 204 | let foo = String::new(); |
214 | <|>Foo { | 205 | Foo { |
215 | foo, | 206 | foo, |
216 | bar: foo.clone(), | 207 | bar: foo.clone(), |
217 | extra: "Extra field", | 208 | extra: "Extra field", |
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..e016f51c3 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 | |||
@@ -1,10 +1,14 @@ | |||
1 | use ra_fmt::unwrap_trivial_block; | 1 | use ra_fmt::unwrap_trivial_block; |
2 | use ra_syntax::{ | 2 | use ra_syntax::{ |
3 | ast::{self, edit::IndentLevel, make}, | 3 | ast::{ |
4 | self, | ||
5 | edit::{AstNodeEdit, IndentLevel}, | ||
6 | make, | ||
7 | }, | ||
4 | AstNode, | 8 | AstNode, |
5 | }; | 9 | }; |
6 | 10 | ||
7 | use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; | 11 | use crate::{utils::TryEnum, AssistContext, AssistId, Assists}; |
8 | 12 | ||
9 | // Assist: replace_if_let_with_match | 13 | // Assist: replace_if_let_with_match |
10 | // | 14 | // |
@@ -32,7 +36,7 @@ use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; | |||
32 | // } | 36 | // } |
33 | // } | 37 | // } |
34 | // ``` | 38 | // ``` |
35 | pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> { | 39 | 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()?; | 40 | let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; |
37 | let cond = if_expr.condition()?; | 41 | let cond = if_expr.condition()?; |
38 | let pat = cond.pat()?; | 42 | let pat = cond.pat()?; |
@@ -43,29 +47,27 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> { | |||
43 | ast::ElseBranch::IfExpr(_) => return None, | 47 | ast::ElseBranch::IfExpr(_) => return None, |
44 | }; | 48 | }; |
45 | 49 | ||
46 | let sema = ctx.sema; | 50 | let target = if_expr.syntax().text_range(); |
47 | ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", move |edit| { | 51 | acc.add(AssistId("replace_if_let_with_match"), "Replace with match", target, move |edit| { |
48 | let match_expr = { | 52 | let match_expr = { |
49 | let then_arm = { | 53 | let then_arm = { |
50 | let then_expr = unwrap_trivial_block(then_block); | 54 | let then_expr = unwrap_trivial_block(then_block); |
51 | make::match_arm(vec![pat.clone()], then_expr) | 55 | make::match_arm(vec![pat.clone()], then_expr) |
52 | }; | 56 | }; |
53 | let else_arm = { | 57 | let else_arm = { |
54 | let pattern = sema | 58 | let pattern = ctx |
59 | .sema | ||
55 | .type_of_pat(&pat) | 60 | .type_of_pat(&pat) |
56 | .and_then(|ty| TryEnum::from_ty(sema, &ty)) | 61 | .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty)) |
57 | .map(|it| it.sad_pattern()) | 62 | .map(|it| it.sad_pattern()) |
58 | .unwrap_or_else(|| make::placeholder_pat().into()); | 63 | .unwrap_or_else(|| make::placeholder_pat().into()); |
59 | let else_expr = unwrap_trivial_block(else_block); | 64 | let else_expr = unwrap_trivial_block(else_block); |
60 | make::match_arm(vec![pattern], else_expr) | 65 | make::match_arm(vec![pattern], else_expr) |
61 | }; | 66 | }; |
62 | make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])) | 67 | make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])) |
68 | .indent(IndentLevel::from_node(if_expr.syntax())) | ||
63 | }; | 69 | }; |
64 | 70 | ||
65 | let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr); | ||
66 | |||
67 | edit.target(if_expr.syntax().text_range()); | ||
68 | edit.set_cursor(if_expr.syntax().text_range().start()); | ||
69 | edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr); | 71 | edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr); |
70 | }) | 72 | }) |
71 | } | 73 | } |
@@ -74,13 +76,13 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> { | |||
74 | mod tests { | 76 | mod tests { |
75 | use super::*; | 77 | use super::*; |
76 | 78 | ||
77 | use crate::helpers::{check_assist, check_assist_target}; | 79 | use crate::tests::{check_assist, check_assist_target}; |
78 | 80 | ||
79 | #[test] | 81 | #[test] |
80 | fn test_replace_if_let_with_match_unwraps_simple_expressions() { | 82 | fn test_replace_if_let_with_match_unwraps_simple_expressions() { |
81 | check_assist( | 83 | check_assist( |
82 | replace_if_let_with_match, | 84 | replace_if_let_with_match, |
83 | " | 85 | r#" |
84 | impl VariantData { | 86 | impl VariantData { |
85 | pub fn is_struct(&self) -> bool { | 87 | pub fn is_struct(&self) -> bool { |
86 | if <|>let VariantData::Struct(..) = *self { | 88 | if <|>let VariantData::Struct(..) = *self { |
@@ -89,16 +91,16 @@ impl VariantData { | |||
89 | false | 91 | false |
90 | } | 92 | } |
91 | } | 93 | } |
92 | } ", | 94 | } "#, |
93 | " | 95 | r#" |
94 | impl VariantData { | 96 | impl VariantData { |
95 | pub fn is_struct(&self) -> bool { | 97 | pub fn is_struct(&self) -> bool { |
96 | <|>match *self { | 98 | match *self { |
97 | VariantData::Struct(..) => true, | 99 | VariantData::Struct(..) => true, |
98 | _ => false, | 100 | _ => false, |
99 | } | 101 | } |
100 | } | 102 | } |
101 | } ", | 103 | } "#, |
102 | ) | 104 | ) |
103 | } | 105 | } |
104 | 106 | ||
@@ -106,7 +108,7 @@ impl VariantData { | |||
106 | fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() { | 108 | fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() { |
107 | check_assist( | 109 | check_assist( |
108 | replace_if_let_with_match, | 110 | replace_if_let_with_match, |
109 | " | 111 | r#" |
110 | fn foo() { | 112 | fn foo() { |
111 | if <|>let VariantData::Struct(..) = a { | 113 | if <|>let VariantData::Struct(..) = a { |
112 | bar( | 114 | bar( |
@@ -115,10 +117,10 @@ fn foo() { | |||
115 | } else { | 117 | } else { |
116 | false | 118 | false |
117 | } | 119 | } |
118 | } ", | 120 | } "#, |
119 | " | 121 | r#" |
120 | fn foo() { | 122 | fn foo() { |
121 | <|>match a { | 123 | match a { |
122 | VariantData::Struct(..) => { | 124 | VariantData::Struct(..) => { |
123 | bar( | 125 | bar( |
124 | 123 | 126 | 123 |
@@ -126,7 +128,7 @@ fn foo() { | |||
126 | } | 128 | } |
127 | _ => false, | 129 | _ => false, |
128 | } | 130 | } |
129 | } ", | 131 | } "#, |
130 | ) | 132 | ) |
131 | } | 133 | } |
132 | 134 | ||
@@ -134,7 +136,7 @@ fn foo() { | |||
134 | fn replace_if_let_with_match_target() { | 136 | fn replace_if_let_with_match_target() { |
135 | check_assist_target( | 137 | check_assist_target( |
136 | replace_if_let_with_match, | 138 | replace_if_let_with_match, |
137 | " | 139 | r#" |
138 | impl VariantData { | 140 | impl VariantData { |
139 | pub fn is_struct(&self) -> bool { | 141 | pub fn is_struct(&self) -> bool { |
140 | if <|>let VariantData::Struct(..) = *self { | 142 | if <|>let VariantData::Struct(..) = *self { |
@@ -143,7 +145,7 @@ impl VariantData { | |||
143 | false | 145 | false |
144 | } | 146 | } |
145 | } | 147 | } |
146 | } ", | 148 | } "#, |
147 | "if let VariantData::Struct(..) = *self { | 149 | "if let VariantData::Struct(..) = *self { |
148 | true | 150 | true |
149 | } else { | 151 | } else { |
@@ -173,7 +175,7 @@ enum Option<T> { Some(T), None } | |||
173 | use Option::*; | 175 | use Option::*; |
174 | 176 | ||
175 | fn foo(x: Option<i32>) { | 177 | fn foo(x: Option<i32>) { |
176 | <|>match x { | 178 | match x { |
177 | Some(x) => println!("{}", x), | 179 | Some(x) => println!("{}", x), |
178 | None => println!("none"), | 180 | None => println!("none"), |
179 | } | 181 | } |
@@ -203,7 +205,7 @@ enum Result<T, E> { Ok(T), Err(E) } | |||
203 | use Result::*; | 205 | use Result::*; |
204 | 206 | ||
205 | fn foo(x: Result<i32, ()>) { | 207 | fn foo(x: Result<i32, ()>) { |
206 | <|>match x { | 208 | match x { |
207 | Ok(x) => println!("{}", x), | 209 | Ok(x) => println!("{}", x), |
208 | Err(_) => println!("none"), | 210 | Err(_) => println!("none"), |
209 | } | 211 | } |
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..761557ac0 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( |
@@ -56,25 +53,20 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> { | |||
56 | ) | 53 | ) |
57 | .into(), | 54 | .into(), |
58 | }; | 55 | }; |
59 | let block = | 56 | let block = make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax())); |
60 | IndentLevel::from_node(let_stmt.syntax()).increase_indent(make::block_expr(None, None)); | ||
61 | let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block); | 57 | let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block); |
62 | let stmt = make::expr_stmt(if_); | 58 | let stmt = make::expr_stmt(if_); |
63 | 59 | ||
64 | let placeholder = stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap(); | 60 | let placeholder = stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap(); |
65 | let target_offset = | ||
66 | let_stmt.syntax().text_range().start() + placeholder.syntax().text_range().start(); | ||
67 | let stmt = stmt.replace_descendant(placeholder.into(), original_pat); | 61 | let stmt = stmt.replace_descendant(placeholder.into(), original_pat); |
68 | 62 | ||
69 | edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt)); | 63 | 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); | ||
72 | }) | 64 | }) |
73 | } | 65 | } |
74 | 66 | ||
75 | #[cfg(test)] | 67 | #[cfg(test)] |
76 | mod tests { | 68 | mod tests { |
77 | use crate::helpers::check_assist; | 69 | use crate::tests::check_assist; |
78 | 70 | ||
79 | use super::*; | 71 | use super::*; |
80 | 72 | ||
@@ -93,7 +85,7 @@ fn main() { | |||
93 | enum E<T> { X(T), Y(T) } | 85 | enum E<T> { X(T), Y(T) } |
94 | 86 | ||
95 | fn main() { | 87 | fn main() { |
96 | if let <|>x = E::X(92) { | 88 | if let x = E::X(92) { |
97 | } | 89 | } |
98 | } | 90 | } |
99 | ", | 91 | ", |
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 918e8dd8d..0197a8cf0 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.text_edit_builder()); | 42 | insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_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 | ||
@@ -88,7 +89,7 @@ std::fmt::Debug<|> | |||
88 | " | 89 | " |
89 | use std::fmt::Debug; | 90 | use std::fmt::Debug; |
90 | 91 | ||
91 | Debug<|> | 92 | Debug |
92 | ", | 93 | ", |
93 | ); | 94 | ); |
94 | } | 95 | } |
@@ -105,7 +106,7 @@ fn main() { | |||
105 | " | 106 | " |
106 | use std::fmt::Debug; | 107 | use std::fmt::Debug; |
107 | 108 | ||
108 | Debug<|> | 109 | Debug |
109 | 110 | ||
110 | fn main() { | 111 | fn main() { |
111 | } | 112 | } |
@@ -129,7 +130,7 @@ use std::fmt::Debug; | |||
129 | fn main() { | 130 | fn main() { |
130 | } | 131 | } |
131 | 132 | ||
132 | Debug<|> | 133 | Debug |
133 | ", | 134 | ", |
134 | ); | 135 | ); |
135 | } | 136 | } |
@@ -144,7 +145,7 @@ std::fmt<|>::Debug | |||
144 | " | 145 | " |
145 | use std::fmt; | 146 | use std::fmt; |
146 | 147 | ||
147 | fmt<|>::Debug | 148 | fmt::Debug |
148 | ", | 149 | ", |
149 | ); | 150 | ); |
150 | } | 151 | } |
@@ -163,7 +164,7 @@ impl std::fmt::Debug<|> for Foo { | |||
163 | use stdx; | 164 | use stdx; |
164 | use std::fmt::Debug; | 165 | use std::fmt::Debug; |
165 | 166 | ||
166 | impl Debug<|> for Foo { | 167 | impl Debug for Foo { |
167 | } | 168 | } |
168 | ", | 169 | ", |
169 | ); | 170 | ); |
@@ -180,7 +181,7 @@ impl std::fmt::Debug<|> for Foo { | |||
180 | " | 181 | " |
181 | use std::fmt::Debug; | 182 | use std::fmt::Debug; |
182 | 183 | ||
183 | impl Debug<|> for Foo { | 184 | impl Debug for Foo { |
184 | } | 185 | } |
185 | ", | 186 | ", |
186 | ); | 187 | ); |
@@ -197,7 +198,7 @@ impl Debug<|> for Foo { | |||
197 | " | 198 | " |
198 | use std::fmt::Debug; | 199 | use std::fmt::Debug; |
199 | 200 | ||
200 | impl Debug<|> for Foo { | 201 | impl Debug for Foo { |
201 | } | 202 | } |
202 | ", | 203 | ", |
203 | ); | 204 | ); |
@@ -216,7 +217,7 @@ impl std::io<|> for Foo { | |||
216 | " | 217 | " |
217 | use std::{io, fmt}; | 218 | use std::{io, fmt}; |
218 | 219 | ||
219 | impl io<|> for Foo { | 220 | impl io for Foo { |
220 | } | 221 | } |
221 | ", | 222 | ", |
222 | ); | 223 | ); |
@@ -235,7 +236,7 @@ impl std::fmt::Debug<|> for Foo { | |||
235 | " | 236 | " |
236 | use std::fmt::{self, Debug, }; | 237 | use std::fmt::{self, Debug, }; |
237 | 238 | ||
238 | impl Debug<|> for Foo { | 239 | impl Debug for Foo { |
239 | } | 240 | } |
240 | ", | 241 | ", |
241 | ); | 242 | ); |
@@ -254,7 +255,7 @@ impl std::fmt<|> for Foo { | |||
254 | " | 255 | " |
255 | use std::fmt::{self, Debug}; | 256 | use std::fmt::{self, Debug}; |
256 | 257 | ||
257 | impl fmt<|> for Foo { | 258 | impl fmt for Foo { |
258 | } | 259 | } |
259 | ", | 260 | ", |
260 | ); | 261 | ); |
@@ -273,7 +274,7 @@ impl std::fmt::nested<|> for Foo { | |||
273 | " | 274 | " |
274 | use std::fmt::{Debug, nested::{Display, self}}; | 275 | use std::fmt::{Debug, nested::{Display, self}}; |
275 | 276 | ||
276 | impl nested<|> for Foo { | 277 | impl nested for Foo { |
277 | } | 278 | } |
278 | ", | 279 | ", |
279 | ); | 280 | ); |
@@ -292,7 +293,7 @@ impl std::fmt::nested<|> for Foo { | |||
292 | " | 293 | " |
293 | use std::fmt::{Debug, nested::{self, Display}}; | 294 | use std::fmt::{Debug, nested::{self, Display}}; |
294 | 295 | ||
295 | impl nested<|> for Foo { | 296 | impl nested for Foo { |
296 | } | 297 | } |
297 | ", | 298 | ", |
298 | ); | 299 | ); |
@@ -311,7 +312,7 @@ impl std::fmt::nested::Debug<|> for Foo { | |||
311 | " | 312 | " |
312 | use std::fmt::{Debug, nested::{Display, Debug}}; | 313 | use std::fmt::{Debug, nested::{Display, Debug}}; |
313 | 314 | ||
314 | impl Debug<|> for Foo { | 315 | impl Debug for Foo { |
315 | } | 316 | } |
316 | ", | 317 | ", |
317 | ); | 318 | ); |
@@ -330,7 +331,7 @@ impl std::fmt::nested::Display<|> for Foo { | |||
330 | " | 331 | " |
331 | use std::fmt::{nested::Display, Debug}; | 332 | use std::fmt::{nested::Display, Debug}; |
332 | 333 | ||
333 | impl Display<|> for Foo { | 334 | impl Display for Foo { |
334 | } | 335 | } |
335 | ", | 336 | ", |
336 | ); | 337 | ); |
@@ -349,7 +350,7 @@ impl std::fmt::Display<|> for Foo { | |||
349 | " | 350 | " |
350 | use std::fmt::{Display, nested::Debug}; | 351 | use std::fmt::{Display, nested::Debug}; |
351 | 352 | ||
352 | impl Display<|> for Foo { | 353 | impl Display for Foo { |
353 | } | 354 | } |
354 | ", | 355 | ", |
355 | ); | 356 | ); |
@@ -373,7 +374,7 @@ use crate::{ | |||
373 | AssocItem, | 374 | AssocItem, |
374 | }; | 375 | }; |
375 | 376 | ||
376 | fn foo() { lower<|>::trait_env() } | 377 | fn foo() { lower::trait_env() } |
377 | ", | 378 | ", |
378 | ); | 379 | ); |
379 | } | 380 | } |
@@ -391,7 +392,7 @@ impl foo::Debug<|> for Foo { | |||
391 | " | 392 | " |
392 | use std::fmt as foo; | 393 | use std::fmt as foo; |
393 | 394 | ||
394 | impl Debug<|> for Foo { | 395 | impl Debug for Foo { |
395 | } | 396 | } |
396 | ", | 397 | ", |
397 | ); | 398 | ); |
@@ -434,7 +435,7 @@ mod foo { | |||
434 | mod bar { | 435 | mod bar { |
435 | use std::fmt::Debug; | 436 | use std::fmt::Debug; |
436 | 437 | ||
437 | Debug<|> | 438 | Debug |
438 | } | 439 | } |
439 | } | 440 | } |
440 | ", | 441 | ", |
@@ -457,7 +458,7 @@ fn main() { | |||
457 | use std::fmt::Debug; | 458 | use std::fmt::Debug; |
458 | 459 | ||
459 | fn main() { | 460 | fn main() { |
460 | Debug<|> | 461 | Debug |
461 | } | 462 | } |
462 | ", | 463 | ", |
463 | ); | 464 | ); |
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..cff7dfb81 100644 --- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs +++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs | |||
@@ -1,11 +1,18 @@ | |||
1 | use std::iter; | 1 | use std::iter; |
2 | 2 | ||
3 | use ra_syntax::{ | 3 | use ra_syntax::{ |
4 | ast::{self, edit::IndentLevel, make}, | 4 | ast::{ |
5 | self, | ||
6 | edit::{AstNodeEdit, IndentLevel}, | ||
7 | make, | ||
8 | }, | ||
5 | AstNode, | 9 | AstNode, |
6 | }; | 10 | }; |
7 | 11 | ||
8 | use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; | 12 | use crate::{ |
13 | utils::{render_snippet, Cursor, TryEnum}, | ||
14 | AssistContext, AssistId, Assists, | ||
15 | }; | ||
9 | 16 | ||
10 | // Assist: replace_unwrap_with_match | 17 | // Assist: replace_unwrap_with_match |
11 | // | 18 | // |
@@ -25,11 +32,11 @@ use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; | |||
25 | // let x: Result<i32, i32> = Result::Ok(92); | 32 | // let x: Result<i32, i32> = Result::Ok(92); |
26 | // let y = match x { | 33 | // let y = match x { |
27 | // Ok(a) => a, | 34 | // Ok(a) => a, |
28 | // _ => unreachable!(), | 35 | // $0_ => unreachable!(), |
29 | // }; | 36 | // }; |
30 | // } | 37 | // } |
31 | // ``` | 38 | // ``` |
32 | pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> { | 39 | pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
33 | let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?; | 40 | let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?; |
34 | let name = method_call.name_ref()?; | 41 | let name = method_call.name_ref()?; |
35 | if name.text() != "unwrap" { | 42 | if name.text() != "unwrap" { |
@@ -37,9 +44,9 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> { | |||
37 | } | 44 | } |
38 | let caller = method_call.expr()?; | 45 | let caller = method_call.expr()?; |
39 | let ty = ctx.sema.type_of_expr(&caller)?; | 46 | let ty = ctx.sema.type_of_expr(&caller)?; |
40 | let happy_variant = TryEnum::from_ty(ctx.sema, &ty)?.happy_case(); | 47 | let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case(); |
41 | 48 | let target = method_call.syntax().text_range(); | |
42 | ctx.add_assist(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", |edit| { | 49 | acc.add(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", target, |builder| { |
43 | let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); | 50 | let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); |
44 | let it = make::bind_pat(make::name("a")).into(); | 51 | let it = make::bind_pat(make::name("a")).into(); |
45 | let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); | 52 | let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); |
@@ -47,23 +54,36 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> { | |||
47 | let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); | 54 | let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); |
48 | let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); | 55 | let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); |
49 | 56 | ||
50 | let unreachable_call = make::unreachable_macro_call().into(); | 57 | let unreachable_call = make::expr_unreachable(); |
51 | let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); | 58 | let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); |
52 | 59 | ||
53 | let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); | 60 | let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); |
54 | let match_expr = make::expr_match(caller.clone(), match_arm_list); | 61 | 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); | 62 | .indent(IndentLevel::from_node(method_call.syntax())); |
56 | 63 | ||
57 | edit.target(method_call.syntax().text_range()); | 64 | let range = method_call.syntax().text_range(); |
58 | edit.set_cursor(caller.syntax().text_range().start()); | 65 | match ctx.config.snippet_cap { |
59 | edit.replace_ast::<ast::Expr>(method_call.into(), match_expr); | 66 | Some(cap) => { |
67 | let err_arm = match_expr | ||
68 | .syntax() | ||
69 | .descendants() | ||
70 | .filter_map(ast::MatchArm::cast) | ||
71 | .last() | ||
72 | .unwrap(); | ||
73 | let snippet = | ||
74 | render_snippet(cap, match_expr.syntax(), Cursor::Before(err_arm.syntax())); | ||
75 | builder.replace_snippet(cap, range, snippet) | ||
76 | } | ||
77 | None => builder.replace(range, match_expr.to_string()), | ||
78 | } | ||
60 | }) | 79 | }) |
61 | } | 80 | } |
62 | 81 | ||
63 | #[cfg(test)] | 82 | #[cfg(test)] |
64 | mod tests { | 83 | mod tests { |
84 | use crate::tests::{check_assist, check_assist_target}; | ||
85 | |||
65 | use super::*; | 86 | use super::*; |
66 | use crate::helpers::{check_assist, check_assist_target}; | ||
67 | 87 | ||
68 | #[test] | 88 | #[test] |
69 | fn test_replace_result_unwrap_with_match() { | 89 | fn test_replace_result_unwrap_with_match() { |
@@ -82,9 +102,9 @@ enum Result<T, E> { Ok(T), Err(E) } | |||
82 | fn i<T>(a: T) -> T { a } | 102 | fn i<T>(a: T) -> T { a } |
83 | fn main() { | 103 | fn main() { |
84 | let x: Result<i32, i32> = Result::Ok(92); | 104 | let x: Result<i32, i32> = Result::Ok(92); |
85 | let y = <|>match i(x) { | 105 | let y = match i(x) { |
86 | Ok(a) => a, | 106 | Ok(a) => a, |
87 | _ => unreachable!(), | 107 | $0_ => unreachable!(), |
88 | }; | 108 | }; |
89 | } | 109 | } |
90 | ", | 110 | ", |
@@ -108,9 +128,9 @@ enum Option<T> { Some(T), None } | |||
108 | fn i<T>(a: T) -> T { a } | 128 | fn i<T>(a: T) -> T { a } |
109 | fn main() { | 129 | fn main() { |
110 | let x = Option::Some(92); | 130 | let x = Option::Some(92); |
111 | let y = <|>match i(x) { | 131 | let y = match i(x) { |
112 | Some(a) => a, | 132 | Some(a) => a, |
113 | _ => unreachable!(), | 133 | $0_ => unreachable!(), |
114 | }; | 134 | }; |
115 | } | 135 | } |
116 | ", | 136 | ", |
@@ -134,9 +154,9 @@ enum Result<T, E> { Ok(T), Err(E) } | |||
134 | fn i<T>(a: T) -> T { a } | 154 | fn i<T>(a: T) -> T { a } |
135 | fn main() { | 155 | fn main() { |
136 | let x: Result<i32, i32> = Result::Ok(92); | 156 | let x: Result<i32, i32> = Result::Ok(92); |
137 | let y = <|>match i(x) { | 157 | let y = match i(x) { |
138 | Ok(a) => a, | 158 | Ok(a) => a, |
139 | _ => unreachable!(), | 159 | $0_ => unreachable!(), |
140 | }.count_zeroes(); | 160 | }.count_zeroes(); |
141 | } | 161 | } |
142 | ", | 162 | ", |
diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs index f25826796..c7a874480 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,18 +26,16 @@ 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(); | ||
30 | 29 | ||
31 | ctx.add_assist(AssistId("split_import"), "Split import", |edit| { | 30 | let target = colon_colon.text_range(); |
32 | edit.target(colon_colon.text_range()); | 31 | acc.add(AssistId("split_import"), "Split import", target, |edit| { |
33 | edit.replace_ast(use_tree, new_tree); | 32 | edit.replace_ast(use_tree, new_tree); |
34 | edit.set_cursor(cursor); | ||
35 | }) | 33 | }) |
36 | } | 34 | } |
37 | 35 | ||
38 | #[cfg(test)] | 36 | #[cfg(test)] |
39 | mod tests { | 37 | mod tests { |
40 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | 38 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
41 | 39 | ||
42 | use super::*; | 40 | use super::*; |
43 | 41 | ||
@@ -46,7 +44,7 @@ mod tests { | |||
46 | check_assist( | 44 | check_assist( |
47 | split_import, | 45 | split_import, |
48 | "use crate::<|>db::RootDatabase;", | 46 | "use crate::<|>db::RootDatabase;", |
49 | "use crate::<|>{db::RootDatabase};", | 47 | "use crate::{db::RootDatabase};", |
50 | ) | 48 | ) |
51 | } | 49 | } |
52 | 50 | ||
@@ -55,7 +53,7 @@ mod tests { | |||
55 | check_assist( | 53 | check_assist( |
56 | split_import, | 54 | split_import, |
57 | "use crate:<|>:db::{RootDatabase, FileSymbol}", | 55 | "use crate:<|>:db::{RootDatabase, FileSymbol}", |
58 | "use crate:<|>:{db::{RootDatabase, FileSymbol}}", | 56 | "use crate::{db::{RootDatabase, FileSymbol}}", |
59 | ) | 57 | ) |
60 | } | 58 | } |
61 | 59 | ||
diff --git a/crates/ra_assists/src/handlers/unwrap_block.rs b/crates/ra_assists/src/handlers/unwrap_block.rs new file mode 100644 index 000000000..8440c7d0f --- /dev/null +++ b/crates/ra_assists/src/handlers/unwrap_block.rs | |||
@@ -0,0 +1,512 @@ | |||
1 | use ra_fmt::unwrap_trivial_block; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, ElseBranch, Expr, LoopBodyOwner}, | ||
4 | match_ast, AstNode, TextRange, T, | ||
5 | }; | ||
6 | |||
7 | use crate::{AssistContext, AssistId, Assists}; | ||
8 | |||
9 | // Assist: unwrap_block | ||
10 | // | ||
11 | // This assist removes if...else, for, while and loop control statements to just keep the body. | ||
12 | // | ||
13 | // ``` | ||
14 | // fn foo() { | ||
15 | // if true {<|> | ||
16 | // println!("foo"); | ||
17 | // } | ||
18 | // } | ||
19 | // ``` | ||
20 | // -> | ||
21 | // ``` | ||
22 | // fn foo() { | ||
23 | // println!("foo"); | ||
24 | // } | ||
25 | // ``` | ||
26 | pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
27 | let l_curly_token = ctx.find_token_at_offset(T!['{'])?; | ||
28 | let block = ast::BlockExpr::cast(l_curly_token.parent())?; | ||
29 | let parent = block.syntax().parent()?; | ||
30 | let assist_id = AssistId("unwrap_block"); | ||
31 | let assist_label = "Unwrap block"; | ||
32 | |||
33 | let (expr, expr_to_unwrap) = match_ast! { | ||
34 | match parent { | ||
35 | ast::ForExpr(for_expr) => { | ||
36 | let block_expr = for_expr.loop_body()?; | ||
37 | let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?; | ||
38 | (ast::Expr::ForExpr(for_expr), expr_to_unwrap) | ||
39 | }, | ||
40 | ast::WhileExpr(while_expr) => { | ||
41 | let block_expr = while_expr.loop_body()?; | ||
42 | let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?; | ||
43 | (ast::Expr::WhileExpr(while_expr), expr_to_unwrap) | ||
44 | }, | ||
45 | ast::LoopExpr(loop_expr) => { | ||
46 | let block_expr = loop_expr.loop_body()?; | ||
47 | let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?; | ||
48 | (ast::Expr::LoopExpr(loop_expr), expr_to_unwrap) | ||
49 | }, | ||
50 | ast::IfExpr(if_expr) => { | ||
51 | let mut resp = None; | ||
52 | |||
53 | let then_branch = if_expr.then_branch()?; | ||
54 | if then_branch.l_curly_token()?.text_range().contains_range(ctx.frange.range) { | ||
55 | if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) { | ||
56 | // For `else if` blocks | ||
57 | let ancestor_then_branch = ancestor.then_branch()?; | ||
58 | let l_curly_token = then_branch.l_curly_token()?; | ||
59 | |||
60 | let target = then_branch.syntax().text_range(); | ||
61 | return acc.add(assist_id, assist_label, target, |edit| { | ||
62 | let range_to_del_else_if = TextRange::new(ancestor_then_branch.syntax().text_range().end(), l_curly_token.text_range().start()); | ||
63 | let range_to_del_rest = TextRange::new(then_branch.syntax().text_range().end(), if_expr.syntax().text_range().end()); | ||
64 | |||
65 | edit.delete(range_to_del_rest); | ||
66 | edit.delete(range_to_del_else_if); | ||
67 | edit.replace(target, update_expr_string(then_branch.to_string(), &[' ', '{'])); | ||
68 | }); | ||
69 | } else { | ||
70 | resp = Some((ast::Expr::IfExpr(if_expr.clone()), Expr::BlockExpr(then_branch))); | ||
71 | } | ||
72 | } else if let Some(else_branch) = if_expr.else_branch() { | ||
73 | match else_branch { | ||
74 | ElseBranch::Block(else_block) => { | ||
75 | let l_curly_token = else_block.l_curly_token()?; | ||
76 | if l_curly_token.text_range().contains_range(ctx.frange.range) { | ||
77 | let target = else_block.syntax().text_range(); | ||
78 | return acc.add(assist_id, assist_label, target, |edit| { | ||
79 | let range_to_del = TextRange::new(then_branch.syntax().text_range().end(), l_curly_token.text_range().start()); | ||
80 | |||
81 | edit.delete(range_to_del); | ||
82 | edit.replace(target, update_expr_string(else_block.to_string(), &[' ', '{'])); | ||
83 | }); | ||
84 | } | ||
85 | }, | ||
86 | ElseBranch::IfExpr(_) => {}, | ||
87 | } | ||
88 | } | ||
89 | |||
90 | resp? | ||
91 | }, | ||
92 | _ => return None, | ||
93 | } | ||
94 | }; | ||
95 | |||
96 | let target = expr_to_unwrap.syntax().text_range(); | ||
97 | acc.add(assist_id, assist_label, target, |edit| { | ||
98 | edit.replace( | ||
99 | expr.syntax().text_range(), | ||
100 | update_expr_string(expr_to_unwrap.to_string(), &[' ', '{', '\n']), | ||
101 | ); | ||
102 | }) | ||
103 | } | ||
104 | |||
105 | fn extract_expr(cursor_range: TextRange, block: ast::BlockExpr) -> Option<ast::Expr> { | ||
106 | let cursor_in_range = block.l_curly_token()?.text_range().contains_range(cursor_range); | ||
107 | |||
108 | if cursor_in_range { | ||
109 | Some(unwrap_trivial_block(block)) | ||
110 | } else { | ||
111 | None | ||
112 | } | ||
113 | } | ||
114 | |||
115 | fn update_expr_string(expr_str: String, trim_start_pat: &[char]) -> String { | ||
116 | let expr_string = expr_str.trim_start_matches(trim_start_pat); | ||
117 | let mut expr_string_lines: Vec<&str> = expr_string.lines().collect(); | ||
118 | expr_string_lines.pop(); // Delete last line | ||
119 | |||
120 | expr_string_lines | ||
121 | .into_iter() | ||
122 | .map(|line| line.replacen(" ", "", 1)) // Delete indentation | ||
123 | .collect::<Vec<String>>() | ||
124 | .join("\n") | ||
125 | } | ||
126 | |||
127 | #[cfg(test)] | ||
128 | mod tests { | ||
129 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
130 | |||
131 | use super::*; | ||
132 | |||
133 | #[test] | ||
134 | fn simple_if() { | ||
135 | check_assist( | ||
136 | unwrap_block, | ||
137 | r#" | ||
138 | fn main() { | ||
139 | bar(); | ||
140 | if true {<|> | ||
141 | foo(); | ||
142 | |||
143 | //comment | ||
144 | bar(); | ||
145 | } else { | ||
146 | println!("bar"); | ||
147 | } | ||
148 | } | ||
149 | "#, | ||
150 | r#" | ||
151 | fn main() { | ||
152 | bar(); | ||
153 | foo(); | ||
154 | |||
155 | //comment | ||
156 | bar(); | ||
157 | } | ||
158 | "#, | ||
159 | ); | ||
160 | } | ||
161 | |||
162 | #[test] | ||
163 | fn simple_if_else() { | ||
164 | check_assist( | ||
165 | unwrap_block, | ||
166 | r#" | ||
167 | fn main() { | ||
168 | bar(); | ||
169 | if true { | ||
170 | foo(); | ||
171 | |||
172 | //comment | ||
173 | bar(); | ||
174 | } else {<|> | ||
175 | println!("bar"); | ||
176 | } | ||
177 | } | ||
178 | "#, | ||
179 | r#" | ||
180 | fn main() { | ||
181 | bar(); | ||
182 | if true { | ||
183 | foo(); | ||
184 | |||
185 | //comment | ||
186 | bar(); | ||
187 | } | ||
188 | println!("bar"); | ||
189 | } | ||
190 | "#, | ||
191 | ); | ||
192 | } | ||
193 | |||
194 | #[test] | ||
195 | fn simple_if_else_if() { | ||
196 | check_assist( | ||
197 | unwrap_block, | ||
198 | r#" | ||
199 | fn main() { | ||
200 | //bar(); | ||
201 | if true { | ||
202 | println!("true"); | ||
203 | |||
204 | //comment | ||
205 | //bar(); | ||
206 | } else if false {<|> | ||
207 | println!("bar"); | ||
208 | } else { | ||
209 | println!("foo"); | ||
210 | } | ||
211 | } | ||
212 | "#, | ||
213 | r#" | ||
214 | fn main() { | ||
215 | //bar(); | ||
216 | if true { | ||
217 | println!("true"); | ||
218 | |||
219 | //comment | ||
220 | //bar(); | ||
221 | } | ||
222 | println!("bar"); | ||
223 | } | ||
224 | "#, | ||
225 | ); | ||
226 | } | ||
227 | |||
228 | #[test] | ||
229 | fn simple_if_else_if_nested() { | ||
230 | check_assist( | ||
231 | unwrap_block, | ||
232 | r#" | ||
233 | fn main() { | ||
234 | //bar(); | ||
235 | if true { | ||
236 | println!("true"); | ||
237 | |||
238 | //comment | ||
239 | //bar(); | ||
240 | } else if false { | ||
241 | println!("bar"); | ||
242 | } else if true {<|> | ||
243 | println!("foo"); | ||
244 | } | ||
245 | } | ||
246 | "#, | ||
247 | r#" | ||
248 | fn main() { | ||
249 | //bar(); | ||
250 | if true { | ||
251 | println!("true"); | ||
252 | |||
253 | //comment | ||
254 | //bar(); | ||
255 | } else if false { | ||
256 | println!("bar"); | ||
257 | } | ||
258 | println!("foo"); | ||
259 | } | ||
260 | "#, | ||
261 | ); | ||
262 | } | ||
263 | |||
264 | #[test] | ||
265 | fn simple_if_else_if_nested_else() { | ||
266 | check_assist( | ||
267 | unwrap_block, | ||
268 | r#" | ||
269 | fn main() { | ||
270 | //bar(); | ||
271 | if true { | ||
272 | println!("true"); | ||
273 | |||
274 | //comment | ||
275 | //bar(); | ||
276 | } else if false { | ||
277 | println!("bar"); | ||
278 | } else if true { | ||
279 | println!("foo"); | ||
280 | } else {<|> | ||
281 | println!("else"); | ||
282 | } | ||
283 | } | ||
284 | "#, | ||
285 | r#" | ||
286 | fn main() { | ||
287 | //bar(); | ||
288 | if true { | ||
289 | println!("true"); | ||
290 | |||
291 | //comment | ||
292 | //bar(); | ||
293 | } else if false { | ||
294 | println!("bar"); | ||
295 | } else if true { | ||
296 | println!("foo"); | ||
297 | } | ||
298 | println!("else"); | ||
299 | } | ||
300 | "#, | ||
301 | ); | ||
302 | } | ||
303 | |||
304 | #[test] | ||
305 | fn simple_if_else_if_nested_middle() { | ||
306 | check_assist( | ||
307 | unwrap_block, | ||
308 | r#" | ||
309 | fn main() { | ||
310 | //bar(); | ||
311 | if true { | ||
312 | println!("true"); | ||
313 | |||
314 | //comment | ||
315 | //bar(); | ||
316 | } else if false { | ||
317 | println!("bar"); | ||
318 | } else if true {<|> | ||
319 | println!("foo"); | ||
320 | } else { | ||
321 | println!("else"); | ||
322 | } | ||
323 | } | ||
324 | "#, | ||
325 | r#" | ||
326 | fn main() { | ||
327 | //bar(); | ||
328 | if true { | ||
329 | println!("true"); | ||
330 | |||
331 | //comment | ||
332 | //bar(); | ||
333 | } else if false { | ||
334 | println!("bar"); | ||
335 | } | ||
336 | println!("foo"); | ||
337 | } | ||
338 | "#, | ||
339 | ); | ||
340 | } | ||
341 | |||
342 | #[test] | ||
343 | fn simple_if_bad_cursor_position() { | ||
344 | check_assist_not_applicable( | ||
345 | unwrap_block, | ||
346 | r#" | ||
347 | fn main() { | ||
348 | bar();<|> | ||
349 | if true { | ||
350 | foo(); | ||
351 | |||
352 | //comment | ||
353 | bar(); | ||
354 | } else { | ||
355 | println!("bar"); | ||
356 | } | ||
357 | } | ||
358 | "#, | ||
359 | ); | ||
360 | } | ||
361 | |||
362 | #[test] | ||
363 | fn simple_for() { | ||
364 | check_assist( | ||
365 | unwrap_block, | ||
366 | r#" | ||
367 | fn main() { | ||
368 | for i in 0..5 {<|> | ||
369 | if true { | ||
370 | foo(); | ||
371 | |||
372 | //comment | ||
373 | bar(); | ||
374 | } else { | ||
375 | println!("bar"); | ||
376 | } | ||
377 | } | ||
378 | } | ||
379 | "#, | ||
380 | r#" | ||
381 | fn main() { | ||
382 | if true { | ||
383 | foo(); | ||
384 | |||
385 | //comment | ||
386 | bar(); | ||
387 | } else { | ||
388 | println!("bar"); | ||
389 | } | ||
390 | } | ||
391 | "#, | ||
392 | ); | ||
393 | } | ||
394 | |||
395 | #[test] | ||
396 | fn simple_if_in_for() { | ||
397 | check_assist( | ||
398 | unwrap_block, | ||
399 | r#" | ||
400 | fn main() { | ||
401 | for i in 0..5 { | ||
402 | if true {<|> | ||
403 | foo(); | ||
404 | |||
405 | //comment | ||
406 | bar(); | ||
407 | } else { | ||
408 | println!("bar"); | ||
409 | } | ||
410 | } | ||
411 | } | ||
412 | "#, | ||
413 | r#" | ||
414 | fn main() { | ||
415 | for i in 0..5 { | ||
416 | foo(); | ||
417 | |||
418 | //comment | ||
419 | bar(); | ||
420 | } | ||
421 | } | ||
422 | "#, | ||
423 | ); | ||
424 | } | ||
425 | |||
426 | #[test] | ||
427 | fn simple_loop() { | ||
428 | check_assist( | ||
429 | unwrap_block, | ||
430 | r#" | ||
431 | fn main() { | ||
432 | loop {<|> | ||
433 | if true { | ||
434 | foo(); | ||
435 | |||
436 | //comment | ||
437 | bar(); | ||
438 | } else { | ||
439 | println!("bar"); | ||
440 | } | ||
441 | } | ||
442 | } | ||
443 | "#, | ||
444 | r#" | ||
445 | fn main() { | ||
446 | if true { | ||
447 | foo(); | ||
448 | |||
449 | //comment | ||
450 | bar(); | ||
451 | } else { | ||
452 | println!("bar"); | ||
453 | } | ||
454 | } | ||
455 | "#, | ||
456 | ); | ||
457 | } | ||
458 | |||
459 | #[test] | ||
460 | fn simple_while() { | ||
461 | check_assist( | ||
462 | unwrap_block, | ||
463 | r#" | ||
464 | fn main() { | ||
465 | while true {<|> | ||
466 | if true { | ||
467 | foo(); | ||
468 | |||
469 | //comment | ||
470 | bar(); | ||
471 | } else { | ||
472 | println!("bar"); | ||
473 | } | ||
474 | } | ||
475 | } | ||
476 | "#, | ||
477 | r#" | ||
478 | fn main() { | ||
479 | if true { | ||
480 | foo(); | ||
481 | |||
482 | //comment | ||
483 | bar(); | ||
484 | } else { | ||
485 | println!("bar"); | ||
486 | } | ||
487 | } | ||
488 | "#, | ||
489 | ); | ||
490 | } | ||
491 | |||
492 | #[test] | ||
493 | fn simple_if_in_while_bad_cursor_position() { | ||
494 | check_assist_not_applicable( | ||
495 | unwrap_block, | ||
496 | r#" | ||
497 | fn main() { | ||
498 | while true { | ||
499 | if true { | ||
500 | foo();<|> | ||
501 | |||
502 | //comment | ||
503 | bar(); | ||
504 | } else { | ||
505 | println!("bar"); | ||
506 | } | ||
507 | } | ||
508 | } | ||
509 | "#, | ||
510 | ); | ||
511 | } | ||
512 | } | ||
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 64bd87afb..464bc03dd 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -10,119 +10,113 @@ 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_config; |
14 | mod marks; | 14 | mod assist_context; |
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 ra_db::{FileId, FileRange}; | ||
21 | use ra_ide_db::RootDatabase; | ||
22 | use ra_syntax::{TextRange, TextSize}; | ||
23 | use ra_text_edit::TextEdit; | ||
24 | |||
25 | pub(crate) use crate::assist_ctx::{Assist, AssistCtx, AssistHandler}; | ||
26 | use hir::Semantics; | 20 | use hir::Semantics; |
21 | use ra_db::FileRange; | ||
22 | use ra_ide_db::{source_change::SourceChange, RootDatabase}; | ||
23 | use ra_syntax::TextRange; | ||
24 | |||
25 | pub(crate) use crate::assist_context::{AssistContext, Assists}; | ||
26 | |||
27 | pub use assist_config::AssistConfig; | ||
27 | 28 | ||
28 | /// Unique identifier of the assist, should not be shown to the user | 29 | /// Unique identifier of the assist, should not be shown to the user |
29 | /// directly. | 30 | /// directly. |
30 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 31 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
31 | pub struct AssistId(pub &'static str); | 32 | pub struct AssistId(pub &'static str); |
32 | 33 | ||
33 | #[derive(Debug, Clone)] | ||
34 | pub struct AssistLabel { | ||
35 | /// Short description of the assist, as shown in the UI. | ||
36 | pub label: String, | ||
37 | pub id: AssistId, | ||
38 | } | ||
39 | |||
40 | #[derive(Clone, Debug)] | 34 | #[derive(Clone, Debug)] |
41 | pub struct GroupLabel(pub String); | 35 | pub struct GroupLabel(pub String); |
42 | 36 | ||
43 | impl AssistLabel { | ||
44 | pub(crate) fn new(label: String, id: AssistId) -> AssistLabel { | ||
45 | // FIXME: make fields private, so that this invariant can't be broken | ||
46 | assert!(label.starts_with(|c: char| c.is_uppercase())); | ||
47 | AssistLabel { label, id } | ||
48 | } | ||
49 | } | ||
50 | |||
51 | #[derive(Debug, Clone)] | 37 | #[derive(Debug, Clone)] |
52 | pub struct AssistAction { | 38 | pub struct Assist { |
53 | pub edit: TextEdit, | 39 | pub id: AssistId, |
54 | pub cursor_position: Option<TextSize>, | 40 | /// Short description of the assist, as shown in the UI. |
55 | // FIXME: This belongs to `AssistLabel` | 41 | pub label: String, |
56 | pub target: Option<TextRange>, | 42 | pub group: Option<GroupLabel>, |
57 | pub file: AssistFile, | 43 | /// Target ranges are used to sort assists: the smaller the target range, |
44 | /// the more specific assist is, and so it should be sorted first. | ||
45 | pub target: TextRange, | ||
58 | } | 46 | } |
59 | 47 | ||
60 | #[derive(Debug, Clone)] | 48 | #[derive(Debug, Clone)] |
61 | pub struct ResolvedAssist { | 49 | pub struct ResolvedAssist { |
62 | pub label: AssistLabel, | 50 | pub assist: Assist, |
63 | pub group_label: Option<GroupLabel>, | 51 | pub source_change: SourceChange, |
64 | pub action: AssistAction, | ||
65 | } | 52 | } |
66 | 53 | ||
67 | #[derive(Debug, Clone, Copy)] | 54 | impl Assist { |
68 | pub enum AssistFile { | 55 | /// Return all the assists applicable at the given position. |
69 | CurrentFile, | 56 | /// |
70 | TargetFile(FileId), | 57 | /// Assists are returned in the "unresolved" state, that is only labels are |
71 | } | 58 | /// returned, without actual edits. |
72 | 59 | pub fn unresolved(db: &RootDatabase, config: &AssistConfig, range: FileRange) -> Vec<Assist> { | |
73 | impl Default for AssistFile { | 60 | let sema = Semantics::new(db); |
74 | fn default() -> Self { | 61 | let ctx = AssistContext::new(sema, config, range); |
75 | Self::CurrentFile | 62 | let mut acc = Assists::new_unresolved(&ctx); |
63 | handlers::all().iter().for_each(|handler| { | ||
64 | handler(&mut acc, &ctx); | ||
65 | }); | ||
66 | acc.finish_unresolved() | ||
76 | } | 67 | } |
77 | } | ||
78 | 68 | ||
79 | /// Return all the assists applicable at the given position. | 69 | /// Return all the assists applicable at the given position. |
80 | /// | 70 | /// |
81 | /// Assists are returned in the "unresolved" state, that is only labels are | 71 | /// Assists are returned in the "resolved" state, that is with edit fully |
82 | /// returned, without actual edits. | 72 | /// computed. |
83 | pub fn unresolved_assists(db: &RootDatabase, range: FileRange) -> Vec<AssistLabel> { | 73 | pub fn resolved( |
84 | let sema = Semantics::new(db); | 74 | db: &RootDatabase, |
85 | let ctx = AssistCtx::new(&sema, range, false); | 75 | config: &AssistConfig, |
86 | handlers::all() | 76 | range: FileRange, |
87 | .iter() | 77 | ) -> Vec<ResolvedAssist> { |
88 | .filter_map(|f| f(ctx.clone())) | 78 | let sema = Semantics::new(db); |
89 | .flat_map(|it| it.0) | 79 | let ctx = AssistContext::new(sema, config, range); |
90 | .map(|a| a.label) | 80 | let mut acc = Assists::new_resolved(&ctx); |
91 | .collect() | 81 | handlers::all().iter().for_each(|handler| { |
92 | } | 82 | handler(&mut acc, &ctx); |
83 | }); | ||
84 | acc.finish_resolved() | ||
85 | } | ||
93 | 86 | ||
94 | /// Return all the assists applicable at the given position. | 87 | pub(crate) fn new( |
95 | /// | 88 | id: AssistId, |
96 | /// Assists are returned in the "resolved" state, that is with edit fully | 89 | label: String, |
97 | /// computed. | 90 | group: Option<GroupLabel>, |
98 | pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssist> { | 91 | target: TextRange, |
99 | let sema = Semantics::new(db); | 92 | ) -> Assist { |
100 | let ctx = AssistCtx::new(&sema, range, true); | 93 | // FIXME: make fields private, so that this invariant can't be broken |
101 | let mut a = handlers::all() | 94 | assert!(label.starts_with(|c: char| c.is_uppercase())); |
102 | .iter() | 95 | Assist { id, label, group, target } |
103 | .filter_map(|f| f(ctx.clone())) | 96 | } |
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 | } | 97 | } |
110 | 98 | ||
111 | mod handlers { | 99 | mod handlers { |
112 | use crate::AssistHandler; | 100 | use crate::{AssistContext, Assists}; |
101 | |||
102 | pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>; | ||
113 | 103 | ||
114 | mod add_custom_impl; | 104 | mod add_custom_impl; |
115 | mod add_derive; | 105 | mod add_derive; |
116 | mod add_explicit_type; | 106 | mod add_explicit_type; |
107 | mod add_from_impl_for_enum; | ||
117 | mod add_function; | 108 | mod add_function; |
118 | mod add_impl; | 109 | mod add_impl; |
119 | mod add_missing_impl_members; | 110 | mod add_missing_impl_members; |
120 | mod add_new; | 111 | mod add_new; |
112 | mod add_turbo_fish; | ||
121 | mod apply_demorgan; | 113 | mod apply_demorgan; |
122 | mod auto_import; | 114 | mod auto_import; |
115 | mod change_return_type_to_result; | ||
123 | mod change_visibility; | 116 | mod change_visibility; |
124 | mod early_return; | 117 | mod early_return; |
125 | mod fill_match_arms; | 118 | mod fill_match_arms; |
119 | mod fix_visibility; | ||
126 | mod flip_binexpr; | 120 | mod flip_binexpr; |
127 | mod flip_comma; | 121 | mod flip_comma; |
128 | mod flip_trait_bound; | 122 | mod flip_trait_bound; |
@@ -136,28 +130,32 @@ mod handlers { | |||
136 | mod raw_string; | 130 | mod raw_string; |
137 | mod remove_dbg; | 131 | mod remove_dbg; |
138 | mod remove_mut; | 132 | mod remove_mut; |
133 | mod reorder_fields; | ||
139 | mod replace_if_let_with_match; | 134 | mod replace_if_let_with_match; |
140 | mod replace_let_with_if_let; | 135 | mod replace_let_with_if_let; |
141 | mod replace_qualified_name_with_use; | 136 | mod replace_qualified_name_with_use; |
142 | mod replace_unwrap_with_match; | 137 | mod replace_unwrap_with_match; |
143 | mod split_import; | 138 | mod split_import; |
144 | mod add_from_impl_for_enum; | 139 | mod unwrap_block; |
145 | mod reorder_fields; | ||
146 | 140 | ||
147 | pub(crate) fn all() -> &'static [AssistHandler] { | 141 | pub(crate) fn all() -> &'static [Handler] { |
148 | &[ | 142 | &[ |
149 | // These are alphabetic for the foolish consistency | 143 | // These are alphabetic for the foolish consistency |
150 | add_custom_impl::add_custom_impl, | 144 | add_custom_impl::add_custom_impl, |
151 | add_derive::add_derive, | 145 | add_derive::add_derive, |
152 | add_explicit_type::add_explicit_type, | 146 | add_explicit_type::add_explicit_type, |
147 | add_from_impl_for_enum::add_from_impl_for_enum, | ||
153 | add_function::add_function, | 148 | add_function::add_function, |
154 | add_impl::add_impl, | 149 | add_impl::add_impl, |
155 | add_new::add_new, | 150 | add_new::add_new, |
151 | add_turbo_fish::add_turbo_fish, | ||
156 | apply_demorgan::apply_demorgan, | 152 | apply_demorgan::apply_demorgan, |
157 | auto_import::auto_import, | 153 | auto_import::auto_import, |
154 | change_return_type_to_result::change_return_type_to_result, | ||
158 | change_visibility::change_visibility, | 155 | change_visibility::change_visibility, |
159 | early_return::convert_to_guarded_return, | 156 | early_return::convert_to_guarded_return, |
160 | fill_match_arms::fill_match_arms, | 157 | fill_match_arms::fill_match_arms, |
158 | fix_visibility::fix_visibility, | ||
161 | flip_binexpr::flip_binexpr, | 159 | flip_binexpr::flip_binexpr, |
162 | flip_comma::flip_comma, | 160 | flip_comma::flip_comma, |
163 | flip_trait_bound::flip_trait_bound, | 161 | flip_trait_bound::flip_trait_bound, |
@@ -175,167 +173,18 @@ mod handlers { | |||
175 | raw_string::remove_hash, | 173 | raw_string::remove_hash, |
176 | remove_dbg::remove_dbg, | 174 | remove_dbg::remove_dbg, |
177 | remove_mut::remove_mut, | 175 | remove_mut::remove_mut, |
176 | reorder_fields::reorder_fields, | ||
178 | replace_if_let_with_match::replace_if_let_with_match, | 177 | replace_if_let_with_match::replace_if_let_with_match, |
179 | replace_let_with_if_let::replace_let_with_if_let, | 178 | replace_let_with_if_let::replace_let_with_if_let, |
180 | replace_qualified_name_with_use::replace_qualified_name_with_use, | 179 | replace_qualified_name_with_use::replace_qualified_name_with_use, |
181 | replace_unwrap_with_match::replace_unwrap_with_match, | 180 | replace_unwrap_with_match::replace_unwrap_with_match, |
182 | split_import::split_import, | 181 | split_import::split_import, |
183 | add_from_impl_for_enum::add_from_impl_for_enum, | 182 | unwrap_block::unwrap_block, |
184 | // These are manually sorted for better priorities | 183 | // These are manually sorted for better priorities |
185 | add_missing_impl_members::add_missing_impl_members, | 184 | add_missing_impl_members::add_missing_impl_members, |
186 | add_missing_impl_members::add_missing_default_members, | 185 | add_missing_impl_members::add_missing_default_members, |
187 | reorder_fields::reorder_fields, | 186 | // Are you sure you want to add new assist here, and not to the |
187 | // sorted list above? | ||
188 | ] | 188 | ] |
189 | } | 189 | } |
190 | } | 190 | } |
191 | |||
192 | #[cfg(test)] | ||
193 | mod helpers { | ||
194 | use std::sync::Arc; | ||
195 | |||
196 | use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; | ||
197 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; | ||
198 | use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset}; | ||
199 | |||
200 | use crate::{AssistCtx, AssistFile, AssistHandler}; | ||
201 | use hir::Semantics; | ||
202 | |||
203 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { | ||
204 | let (mut db, file_id) = RootDatabase::with_single_file(text); | ||
205 | // FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`, | ||
206 | // but it looks like this might need specialization? :( | ||
207 | db.set_local_roots(Arc::new(vec![db.file_source_root(file_id)])); | ||
208 | (db, file_id) | ||
209 | } | ||
210 | |||
211 | pub(crate) fn check_assist( | ||
212 | assist: AssistHandler, | ||
213 | ra_fixture_before: &str, | ||
214 | ra_fixture_after: &str, | ||
215 | ) { | ||
216 | check(assist, ra_fixture_before, ExpectedResult::After(ra_fixture_after)); | ||
217 | } | ||
218 | |||
219 | // FIXME: instead of having a separate function here, maybe use | ||
220 | // `extract_ranges` and mark the target as `<target> </target>` in the | ||
221 | // fixuture? | ||
222 | pub(crate) fn check_assist_target(assist: AssistHandler, ra_fixture: &str, target: &str) { | ||
223 | check(assist, ra_fixture, ExpectedResult::Target(target)); | ||
224 | } | ||
225 | |||
226 | pub(crate) fn check_assist_not_applicable(assist: AssistHandler, ra_fixture: &str) { | ||
227 | check(assist, ra_fixture, ExpectedResult::NotApplicable); | ||
228 | } | ||
229 | |||
230 | enum ExpectedResult<'a> { | ||
231 | NotApplicable, | ||
232 | After(&'a str), | ||
233 | Target(&'a str), | ||
234 | } | ||
235 | |||
236 | fn check(assist: AssistHandler, before: &str, expected: ExpectedResult) { | ||
237 | let (text_without_caret, file_with_caret_id, range_or_offset, db) = | ||
238 | if before.contains("//-") { | ||
239 | let (mut db, position) = RootDatabase::with_position(before); | ||
240 | db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)])); | ||
241 | ( | ||
242 | db.file_text(position.file_id).as_ref().to_owned(), | ||
243 | position.file_id, | ||
244 | RangeOrOffset::Offset(position.offset), | ||
245 | db, | ||
246 | ) | ||
247 | } else { | ||
248 | let (range_or_offset, text_without_caret) = extract_range_or_offset(before); | ||
249 | let (db, file_id) = with_single_file(&text_without_caret); | ||
250 | (text_without_caret, file_id, range_or_offset, db) | ||
251 | }; | ||
252 | |||
253 | let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; | ||
254 | |||
255 | let sema = Semantics::new(&db); | ||
256 | let assist_ctx = AssistCtx::new(&sema, frange, true); | ||
257 | |||
258 | match (assist(assist_ctx), expected) { | ||
259 | (Some(assist), ExpectedResult::After(after)) => { | ||
260 | let action = assist.0[0].action.clone().unwrap(); | ||
261 | |||
262 | let assisted_file_text = if let AssistFile::TargetFile(file_id) = action.file { | ||
263 | db.file_text(file_id).as_ref().to_owned() | ||
264 | } else { | ||
265 | text_without_caret | ||
266 | }; | ||
267 | |||
268 | let mut actual = action.edit.apply(&assisted_file_text); | ||
269 | match action.cursor_position { | ||
270 | None => { | ||
271 | if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { | ||
272 | let off = action | ||
273 | .edit | ||
274 | .apply_to_offset(before_cursor_pos) | ||
275 | .expect("cursor position is affected by the edit"); | ||
276 | actual = add_cursor(&actual, off) | ||
277 | } | ||
278 | } | ||
279 | Some(off) => actual = add_cursor(&actual, off), | ||
280 | }; | ||
281 | |||
282 | assert_eq_text!(after, &actual); | ||
283 | } | ||
284 | (Some(assist), ExpectedResult::Target(target)) => { | ||
285 | let action = assist.0[0].action.clone().unwrap(); | ||
286 | let range = action.target.expect("expected target on action"); | ||
287 | assert_eq_text!(&text_without_caret[range], target); | ||
288 | } | ||
289 | (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"), | ||
290 | (None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => { | ||
291 | panic!("code action is not applicable") | ||
292 | } | ||
293 | (None, ExpectedResult::NotApplicable) => (), | ||
294 | }; | ||
295 | } | ||
296 | } | ||
297 | |||
298 | #[cfg(test)] | ||
299 | mod tests { | ||
300 | use ra_db::FileRange; | ||
301 | use ra_syntax::TextRange; | ||
302 | use test_utils::{extract_offset, extract_range}; | ||
303 | |||
304 | use crate::{helpers, resolved_assists}; | ||
305 | |||
306 | #[test] | ||
307 | fn assist_order_field_struct() { | ||
308 | let before = "struct Foo { <|>bar: u32 }"; | ||
309 | let (before_cursor_pos, before) = extract_offset(before); | ||
310 | let (db, file_id) = helpers::with_single_file(&before); | ||
311 | let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; | ||
312 | let assists = resolved_assists(&db, frange); | ||
313 | let mut assists = assists.iter(); | ||
314 | |||
315 | assert_eq!( | ||
316 | assists.next().expect("expected assist").label.label, | ||
317 | "Change visibility to pub(crate)" | ||
318 | ); | ||
319 | assert_eq!(assists.next().expect("expected assist").label.label, "Add `#[derive]`"); | ||
320 | } | ||
321 | |||
322 | #[test] | ||
323 | fn assist_order_if_expr() { | ||
324 | let before = " | ||
325 | pub fn test_some_range(a: int) -> bool { | ||
326 | if let 2..6 = <|>5<|> { | ||
327 | true | ||
328 | } else { | ||
329 | false | ||
330 | } | ||
331 | }"; | ||
332 | let (range, before) = extract_range(before); | ||
333 | let (db, file_id) = helpers::with_single_file(&before); | ||
334 | let frange = FileRange { file_id, range }; | ||
335 | let assists = resolved_assists(&db, frange); | ||
336 | let mut assists = assists.iter(); | ||
337 | |||
338 | assert_eq!(assists.next().expect("expected assist").label.label, "Extract into variable"); | ||
339 | assert_eq!(assists.next().expect("expected assist").label.label, "Replace with match"); | ||
340 | } | ||
341 | } | ||
diff --git a/crates/ra_assists/src/marks.rs b/crates/ra_assists/src/marks.rs deleted file mode 100644 index 8d910205f..000000000 --- a/crates/ra_assists/src/marks.rs +++ /dev/null | |||
@@ -1,12 +0,0 @@ | |||
1 | //! See test_utils/src/marks.rs | ||
2 | |||
3 | test_utils::marks![ | ||
4 | introduce_var_in_comment_is_not_applicable | ||
5 | test_introduce_var_expr_stmt | ||
6 | test_introduce_var_last_expr | ||
7 | not_applicable_outside_of_bind_pat | ||
8 | test_not_inline_mut_variable | ||
9 | test_not_applicable_if_variable_unused | ||
10 | change_visibility_field_false_positive | ||
11 | test_add_from_impl_already_exists | ||
12 | ]; | ||
diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs new file mode 100644 index 000000000..62dd3547f --- /dev/null +++ b/crates/ra_assists/src/tests.rs | |||
@@ -0,0 +1,153 @@ | |||
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 | assert_eq_text, extract_offset, extract_range, extract_range_or_offset, RangeOrOffset, | ||
11 | }; | ||
12 | |||
13 | use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; | ||
14 | |||
15 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { | ||
16 | let (mut db, file_id) = RootDatabase::with_single_file(text); | ||
17 | // FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`, | ||
18 | // but it looks like this might need specialization? :( | ||
19 | db.set_local_roots(Arc::new(vec![db.file_source_root(file_id)])); | ||
20 | (db, file_id) | ||
21 | } | ||
22 | |||
23 | pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) { | ||
24 | check(assist, ra_fixture_before, ExpectedResult::After(ra_fixture_after)); | ||
25 | } | ||
26 | |||
27 | // FIXME: instead of having a separate function here, maybe use | ||
28 | // `extract_ranges` and mark the target as `<target> </target>` in the | ||
29 | // fixuture? | ||
30 | pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) { | ||
31 | check(assist, ra_fixture, ExpectedResult::Target(target)); | ||
32 | } | ||
33 | |||
34 | pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) { | ||
35 | check(assist, ra_fixture, ExpectedResult::NotApplicable); | ||
36 | } | ||
37 | |||
38 | fn check_doc_test(assist_id: &str, before: &str, after: &str) { | ||
39 | let (selection, before) = extract_range_or_offset(before); | ||
40 | let (db, file_id) = crate::tests::with_single_file(&before); | ||
41 | let frange = FileRange { file_id, range: selection.into() }; | ||
42 | |||
43 | let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange) | ||
44 | .into_iter() | ||
45 | .find(|assist| assist.assist.id.0 == assist_id) | ||
46 | .unwrap_or_else(|| { | ||
47 | panic!( | ||
48 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", | ||
49 | assist_id, | ||
50 | Assist::resolved(&db, &AssistConfig::default(), frange) | ||
51 | .into_iter() | ||
52 | .map(|assist| assist.assist.id.0) | ||
53 | .collect::<Vec<_>>() | ||
54 | .join(", ") | ||
55 | ) | ||
56 | }); | ||
57 | |||
58 | let actual = { | ||
59 | let change = assist.source_change.source_file_edits.pop().unwrap(); | ||
60 | let mut actual = before.clone(); | ||
61 | change.edit.apply(&mut actual); | ||
62 | actual | ||
63 | }; | ||
64 | assert_eq_text!(after, &actual); | ||
65 | } | ||
66 | |||
67 | enum ExpectedResult<'a> { | ||
68 | NotApplicable, | ||
69 | After(&'a str), | ||
70 | Target(&'a str), | ||
71 | } | ||
72 | |||
73 | fn check(handler: Handler, before: &str, expected: ExpectedResult) { | ||
74 | let (text_without_caret, file_with_caret_id, range_or_offset, db) = if before.contains("//-") { | ||
75 | let (mut db, position) = RootDatabase::with_position(before); | ||
76 | db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)])); | ||
77 | ( | ||
78 | db.file_text(position.file_id).as_ref().to_owned(), | ||
79 | position.file_id, | ||
80 | RangeOrOffset::Offset(position.offset), | ||
81 | db, | ||
82 | ) | ||
83 | } else { | ||
84 | let (range_or_offset, text_without_caret) = extract_range_or_offset(before); | ||
85 | let (db, file_id) = with_single_file(&text_without_caret); | ||
86 | (text_without_caret, file_id, range_or_offset, db) | ||
87 | }; | ||
88 | |||
89 | let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; | ||
90 | |||
91 | let sema = Semantics::new(&db); | ||
92 | let config = AssistConfig::default(); | ||
93 | let ctx = AssistContext::new(sema, &config, 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 | assert_eq_text!(after, &actual); | ||
106 | } | ||
107 | (Some(assist), ExpectedResult::Target(target)) => { | ||
108 | let range = assist.assist.target; | ||
109 | assert_eq_text!(&text_without_caret[range], target); | ||
110 | } | ||
111 | (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"), | ||
112 | (None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => { | ||
113 | panic!("code action is not applicable") | ||
114 | } | ||
115 | (None, ExpectedResult::NotApplicable) => (), | ||
116 | }; | ||
117 | } | ||
118 | |||
119 | #[test] | ||
120 | fn assist_order_field_struct() { | ||
121 | let before = "struct Foo { <|>bar: u32 }"; | ||
122 | let (before_cursor_pos, before) = extract_offset(before); | ||
123 | let (db, file_id) = with_single_file(&before); | ||
124 | let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; | ||
125 | let assists = Assist::resolved(&db, &AssistConfig::default(), frange); | ||
126 | let mut assists = assists.iter(); | ||
127 | |||
128 | assert_eq!( | ||
129 | assists.next().expect("expected assist").assist.label, | ||
130 | "Change visibility to pub(crate)" | ||
131 | ); | ||
132 | assert_eq!(assists.next().expect("expected assist").assist.label, "Add `#[derive]`"); | ||
133 | } | ||
134 | |||
135 | #[test] | ||
136 | fn assist_order_if_expr() { | ||
137 | let before = " | ||
138 | pub fn test_some_range(a: int) -> bool { | ||
139 | if let 2..6 = <|>5<|> { | ||
140 | true | ||
141 | } else { | ||
142 | false | ||
143 | } | ||
144 | }"; | ||
145 | let (range, before) = extract_range(before); | ||
146 | let (db, file_id) = with_single_file(&before); | ||
147 | let frange = FileRange { file_id, range }; | ||
148 | let assists = Assist::resolved(&db, &AssistConfig::default(), frange); | ||
149 | let mut assists = assists.iter(); | ||
150 | |||
151 | assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); | ||
152 | assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match"); | ||
153 | } | ||
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/tests/generated.rs index e4fa9ee36..250e56a69 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)] |
@@ -15,7 +15,7 @@ struct S; | |||
15 | struct S; | 15 | struct S; |
16 | 16 | ||
17 | impl Debug for S { | 17 | impl Debug for S { |
18 | 18 | $0 | |
19 | } | 19 | } |
20 | "#####, | 20 | "#####, |
21 | ) | 21 | ) |
@@ -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 { |
@@ -32,7 +32,7 @@ struct Point { | |||
32 | } | 32 | } |
33 | "#####, | 33 | "#####, |
34 | r#####" | 34 | r#####" |
35 | #[derive()] | 35 | #[derive($0)] |
36 | struct Point { | 36 | struct Point { |
37 | x: u32, | 37 | x: u32, |
38 | y: u32, | 38 | y: u32, |
@@ -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; |
@@ -78,7 +78,7 @@ fn foo() { | |||
78 | } | 78 | } |
79 | 79 | ||
80 | fn bar(arg: &str, baz: Baz) { | 80 | fn bar(arg: &str, baz: Baz) { |
81 | todo!() | 81 | ${0:todo!()} |
82 | } | 82 | } |
83 | 83 | ||
84 | "#####, | 84 | "#####, |
@@ -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,20 +104,20 @@ 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> { |
111 | data: T,<|> | 111 | data: T,<|> |
112 | } | 112 | } |
113 | "#####, | 113 | "#####, |
114 | r#####" | 114 | r#####" |
115 | struct Ctx<T: Clone> { | 115 | struct Ctx<T: Clone> { |
116 | data: T, | 116 | data: T, |
117 | } | 117 | } |
118 | 118 | ||
119 | impl<T: Clone> Ctx<T> { | 119 | impl<T: Clone> Ctx<T> { |
120 | 120 | $0 | |
121 | } | 121 | } |
122 | "#####, | 122 | "#####, |
123 | ) | 123 | ) |
@@ -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 { |
@@ -150,7 +150,7 @@ trait Trait { | |||
150 | impl Trait for () { | 150 | impl Trait for () { |
151 | Type X = (); | 151 | Type X = (); |
152 | fn foo(&self) {} | 152 | fn foo(&self) {} |
153 | fn bar(&self) {} | 153 | $0fn bar(&self) {} |
154 | 154 | ||
155 | } | 155 | } |
156 | "#####, | 156 | "#####, |
@@ -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> { |
@@ -180,7 +180,9 @@ trait Trait<T> { | |||
180 | } | 180 | } |
181 | 181 | ||
182 | impl Trait<u32> for () { | 182 | impl Trait<u32> for () { |
183 | fn foo(&self) -> u32 { todo!() } | 183 | fn foo(&self) -> u32 { |
184 | ${0:todo!()} | ||
185 | } | ||
184 | 186 | ||
185 | } | 187 | } |
186 | "#####, | 188 | "#####, |
@@ -189,7 +191,7 @@ impl Trait<u32> for () { | |||
189 | 191 | ||
190 | #[test] | 192 | #[test] |
191 | fn doctest_add_new() { | 193 | fn doctest_add_new() { |
192 | check( | 194 | check_doc_test( |
193 | "add_new", | 195 | "add_new", |
194 | r#####" | 196 | r#####" |
195 | struct Ctx<T: Clone> { | 197 | struct Ctx<T: Clone> { |
@@ -202,7 +204,7 @@ struct Ctx<T: Clone> { | |||
202 | } | 204 | } |
203 | 205 | ||
204 | impl<T: Clone> Ctx<T> { | 206 | impl<T: Clone> Ctx<T> { |
205 | fn new(data: T) -> Self { Self { data } } | 207 | fn $0new(data: T) -> Self { Self { data } } |
206 | } | 208 | } |
207 | 209 | ||
208 | "#####, | 210 | "#####, |
@@ -210,8 +212,27 @@ impl<T: Clone> Ctx<T> { | |||
210 | } | 212 | } |
211 | 213 | ||
212 | #[test] | 214 | #[test] |
215 | fn doctest_add_turbo_fish() { | ||
216 | check_doc_test( | ||
217 | "add_turbo_fish", | ||
218 | r#####" | ||
219 | fn make<T>() -> T { todo!() } | ||
220 | fn main() { | ||
221 | let x = make<|>(); | ||
222 | } | ||
223 | "#####, | ||
224 | r#####" | ||
225 | fn make<T>() -> T { todo!() } | ||
226 | fn main() { | ||
227 | let x = make::<${0:_}>(); | ||
228 | } | ||
229 | "#####, | ||
230 | ) | ||
231 | } | ||
232 | |||
233 | #[test] | ||
213 | fn doctest_apply_demorgan() { | 234 | fn doctest_apply_demorgan() { |
214 | check( | 235 | check_doc_test( |
215 | "apply_demorgan", | 236 | "apply_demorgan", |
216 | r#####" | 237 | r#####" |
217 | fn main() { | 238 | fn main() { |
@@ -228,7 +249,7 @@ fn main() { | |||
228 | 249 | ||
229 | #[test] | 250 | #[test] |
230 | fn doctest_auto_import() { | 251 | fn doctest_auto_import() { |
231 | check( | 252 | check_doc_test( |
232 | "auto_import", | 253 | "auto_import", |
233 | r#####" | 254 | r#####" |
234 | fn main() { | 255 | fn main() { |
@@ -248,8 +269,21 @@ pub mod std { pub mod collections { pub struct HashMap { } } } | |||
248 | } | 269 | } |
249 | 270 | ||
250 | #[test] | 271 | #[test] |
272 | fn doctest_change_return_type_to_result() { | ||
273 | check_doc_test( | ||
274 | "change_return_type_to_result", | ||
275 | r#####" | ||
276 | fn foo() -> i32<|> { 42i32 } | ||
277 | "#####, | ||
278 | r#####" | ||
279 | fn foo() -> Result<i32, ${0:_}> { Ok(42i32) } | ||
280 | "#####, | ||
281 | ) | ||
282 | } | ||
283 | |||
284 | #[test] | ||
251 | fn doctest_change_visibility() { | 285 | fn doctest_change_visibility() { |
252 | check( | 286 | check_doc_test( |
253 | "change_visibility", | 287 | "change_visibility", |
254 | r#####" | 288 | r#####" |
255 | <|>fn frobnicate() {} | 289 | <|>fn frobnicate() {} |
@@ -262,7 +296,7 @@ pub(crate) fn frobnicate() {} | |||
262 | 296 | ||
263 | #[test] | 297 | #[test] |
264 | fn doctest_convert_to_guarded_return() { | 298 | fn doctest_convert_to_guarded_return() { |
265 | check( | 299 | check_doc_test( |
266 | "convert_to_guarded_return", | 300 | "convert_to_guarded_return", |
267 | r#####" | 301 | r#####" |
268 | fn main() { | 302 | fn main() { |
@@ -286,7 +320,7 @@ fn main() { | |||
286 | 320 | ||
287 | #[test] | 321 | #[test] |
288 | fn doctest_fill_match_arms() { | 322 | fn doctest_fill_match_arms() { |
289 | check( | 323 | check_doc_test( |
290 | "fill_match_arms", | 324 | "fill_match_arms", |
291 | r#####" | 325 | r#####" |
292 | enum Action { Move { distance: u32 }, Stop } | 326 | enum Action { Move { distance: u32 }, Stop } |
@@ -302,7 +336,7 @@ enum Action { Move { distance: u32 }, Stop } | |||
302 | 336 | ||
303 | fn handle(action: Action) { | 337 | fn handle(action: Action) { |
304 | match action { | 338 | match action { |
305 | Action::Move { distance } => {} | 339 | $0Action::Move { distance } => {} |
306 | Action::Stop => {} | 340 | Action::Stop => {} |
307 | } | 341 | } |
308 | } | 342 | } |
@@ -311,8 +345,31 @@ fn handle(action: Action) { | |||
311 | } | 345 | } |
312 | 346 | ||
313 | #[test] | 347 | #[test] |
348 | fn doctest_fix_visibility() { | ||
349 | check_doc_test( | ||
350 | "fix_visibility", | ||
351 | r#####" | ||
352 | mod m { | ||
353 | fn frobnicate() {} | ||
354 | } | ||
355 | fn main() { | ||
356 | m::frobnicate<|>() {} | ||
357 | } | ||
358 | "#####, | ||
359 | r#####" | ||
360 | mod m { | ||
361 | $0pub(crate) fn frobnicate() {} | ||
362 | } | ||
363 | fn main() { | ||
364 | m::frobnicate() {} | ||
365 | } | ||
366 | "#####, | ||
367 | ) | ||
368 | } | ||
369 | |||
370 | #[test] | ||
314 | fn doctest_flip_binexpr() { | 371 | fn doctest_flip_binexpr() { |
315 | check( | 372 | check_doc_test( |
316 | "flip_binexpr", | 373 | "flip_binexpr", |
317 | r#####" | 374 | r#####" |
318 | fn main() { | 375 | fn main() { |
@@ -329,7 +386,7 @@ fn main() { | |||
329 | 386 | ||
330 | #[test] | 387 | #[test] |
331 | fn doctest_flip_comma() { | 388 | fn doctest_flip_comma() { |
332 | check( | 389 | check_doc_test( |
333 | "flip_comma", | 390 | "flip_comma", |
334 | r#####" | 391 | r#####" |
335 | fn main() { | 392 | fn main() { |
@@ -346,7 +403,7 @@ fn main() { | |||
346 | 403 | ||
347 | #[test] | 404 | #[test] |
348 | fn doctest_flip_trait_bound() { | 405 | fn doctest_flip_trait_bound() { |
349 | check( | 406 | check_doc_test( |
350 | "flip_trait_bound", | 407 | "flip_trait_bound", |
351 | r#####" | 408 | r#####" |
352 | fn foo<T: Clone +<|> Copy>() { } | 409 | fn foo<T: Clone +<|> Copy>() { } |
@@ -359,7 +416,7 @@ fn foo<T: Copy + Clone>() { } | |||
359 | 416 | ||
360 | #[test] | 417 | #[test] |
361 | fn doctest_inline_local_variable() { | 418 | fn doctest_inline_local_variable() { |
362 | check( | 419 | check_doc_test( |
363 | "inline_local_variable", | 420 | "inline_local_variable", |
364 | r#####" | 421 | r#####" |
365 | fn main() { | 422 | fn main() { |
@@ -377,7 +434,7 @@ fn main() { | |||
377 | 434 | ||
378 | #[test] | 435 | #[test] |
379 | fn doctest_introduce_variable() { | 436 | fn doctest_introduce_variable() { |
380 | check( | 437 | check_doc_test( |
381 | "introduce_variable", | 438 | "introduce_variable", |
382 | r#####" | 439 | r#####" |
383 | fn main() { | 440 | fn main() { |
@@ -386,7 +443,7 @@ fn main() { | |||
386 | "#####, | 443 | "#####, |
387 | r#####" | 444 | r#####" |
388 | fn main() { | 445 | fn main() { |
389 | let var_name = (1 + 2); | 446 | let $0var_name = (1 + 2); |
390 | var_name * 4; | 447 | var_name * 4; |
391 | } | 448 | } |
392 | "#####, | 449 | "#####, |
@@ -395,7 +452,7 @@ fn main() { | |||
395 | 452 | ||
396 | #[test] | 453 | #[test] |
397 | fn doctest_invert_if() { | 454 | fn doctest_invert_if() { |
398 | check( | 455 | check_doc_test( |
399 | "invert_if", | 456 | "invert_if", |
400 | r#####" | 457 | r#####" |
401 | fn main() { | 458 | fn main() { |
@@ -412,7 +469,7 @@ fn main() { | |||
412 | 469 | ||
413 | #[test] | 470 | #[test] |
414 | fn doctest_make_raw_string() { | 471 | fn doctest_make_raw_string() { |
415 | check( | 472 | check_doc_test( |
416 | "make_raw_string", | 473 | "make_raw_string", |
417 | r#####" | 474 | r#####" |
418 | fn main() { | 475 | fn main() { |
@@ -429,7 +486,7 @@ fn main() { | |||
429 | 486 | ||
430 | #[test] | 487 | #[test] |
431 | fn doctest_make_usual_string() { | 488 | fn doctest_make_usual_string() { |
432 | check( | 489 | check_doc_test( |
433 | "make_usual_string", | 490 | "make_usual_string", |
434 | r#####" | 491 | r#####" |
435 | fn main() { | 492 | fn main() { |
@@ -446,7 +503,7 @@ fn main() { | |||
446 | 503 | ||
447 | #[test] | 504 | #[test] |
448 | fn doctest_merge_imports() { | 505 | fn doctest_merge_imports() { |
449 | check( | 506 | check_doc_test( |
450 | "merge_imports", | 507 | "merge_imports", |
451 | r#####" | 508 | r#####" |
452 | use std::<|>fmt::Formatter; | 509 | use std::<|>fmt::Formatter; |
@@ -460,7 +517,7 @@ use std::{fmt::Formatter, io}; | |||
460 | 517 | ||
461 | #[test] | 518 | #[test] |
462 | fn doctest_merge_match_arms() { | 519 | fn doctest_merge_match_arms() { |
463 | check( | 520 | check_doc_test( |
464 | "merge_match_arms", | 521 | "merge_match_arms", |
465 | r#####" | 522 | r#####" |
466 | enum Action { Move { distance: u32 }, Stop } | 523 | enum Action { Move { distance: u32 }, Stop } |
@@ -486,7 +543,7 @@ fn handle(action: Action) { | |||
486 | 543 | ||
487 | #[test] | 544 | #[test] |
488 | fn doctest_move_arm_cond_to_match_guard() { | 545 | fn doctest_move_arm_cond_to_match_guard() { |
489 | check( | 546 | check_doc_test( |
490 | "move_arm_cond_to_match_guard", | 547 | "move_arm_cond_to_match_guard", |
491 | r#####" | 548 | r#####" |
492 | enum Action { Move { distance: u32 }, Stop } | 549 | enum Action { Move { distance: u32 }, Stop } |
@@ -513,7 +570,7 @@ fn handle(action: Action) { | |||
513 | 570 | ||
514 | #[test] | 571 | #[test] |
515 | fn doctest_move_bounds_to_where_clause() { | 572 | fn doctest_move_bounds_to_where_clause() { |
516 | check( | 573 | check_doc_test( |
517 | "move_bounds_to_where_clause", | 574 | "move_bounds_to_where_clause", |
518 | r#####" | 575 | r#####" |
519 | fn apply<T, U, <|>F: FnOnce(T) -> U>(f: F, x: T) -> U { | 576 | fn apply<T, U, <|>F: FnOnce(T) -> U>(f: F, x: T) -> U { |
@@ -530,7 +587,7 @@ fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U { | |||
530 | 587 | ||
531 | #[test] | 588 | #[test] |
532 | fn doctest_move_guard_to_arm_body() { | 589 | fn doctest_move_guard_to_arm_body() { |
533 | check( | 590 | check_doc_test( |
534 | "move_guard_to_arm_body", | 591 | "move_guard_to_arm_body", |
535 | r#####" | 592 | r#####" |
536 | enum Action { Move { distance: u32 }, Stop } | 593 | enum Action { Move { distance: u32 }, Stop } |
@@ -557,7 +614,7 @@ fn handle(action: Action) { | |||
557 | 614 | ||
558 | #[test] | 615 | #[test] |
559 | fn doctest_remove_dbg() { | 616 | fn doctest_remove_dbg() { |
560 | check( | 617 | check_doc_test( |
561 | "remove_dbg", | 618 | "remove_dbg", |
562 | r#####" | 619 | r#####" |
563 | fn main() { | 620 | fn main() { |
@@ -574,7 +631,7 @@ fn main() { | |||
574 | 631 | ||
575 | #[test] | 632 | #[test] |
576 | fn doctest_remove_hash() { | 633 | fn doctest_remove_hash() { |
577 | check( | 634 | check_doc_test( |
578 | "remove_hash", | 635 | "remove_hash", |
579 | r#####" | 636 | r#####" |
580 | fn main() { | 637 | fn main() { |
@@ -591,7 +648,7 @@ fn main() { | |||
591 | 648 | ||
592 | #[test] | 649 | #[test] |
593 | fn doctest_remove_mut() { | 650 | fn doctest_remove_mut() { |
594 | check( | 651 | check_doc_test( |
595 | "remove_mut", | 652 | "remove_mut", |
596 | r#####" | 653 | r#####" |
597 | impl Walrus { | 654 | impl Walrus { |
@@ -608,7 +665,7 @@ impl Walrus { | |||
608 | 665 | ||
609 | #[test] | 666 | #[test] |
610 | fn doctest_reorder_fields() { | 667 | fn doctest_reorder_fields() { |
611 | check( | 668 | check_doc_test( |
612 | "reorder_fields", | 669 | "reorder_fields", |
613 | r#####" | 670 | r#####" |
614 | struct Foo {foo: i32, bar: i32}; | 671 | struct Foo {foo: i32, bar: i32}; |
@@ -623,7 +680,7 @@ const test: Foo = Foo {foo: 1, bar: 0} | |||
623 | 680 | ||
624 | #[test] | 681 | #[test] |
625 | fn doctest_replace_if_let_with_match() { | 682 | fn doctest_replace_if_let_with_match() { |
626 | check( | 683 | check_doc_test( |
627 | "replace_if_let_with_match", | 684 | "replace_if_let_with_match", |
628 | r#####" | 685 | r#####" |
629 | enum Action { Move { distance: u32 }, Stop } | 686 | enum Action { Move { distance: u32 }, Stop } |
@@ -651,7 +708,7 @@ fn handle(action: Action) { | |||
651 | 708 | ||
652 | #[test] | 709 | #[test] |
653 | fn doctest_replace_let_with_if_let() { | 710 | fn doctest_replace_let_with_if_let() { |
654 | check( | 711 | check_doc_test( |
655 | "replace_let_with_if_let", | 712 | "replace_let_with_if_let", |
656 | r#####" | 713 | r#####" |
657 | enum Option<T> { Some(T), None } | 714 | enum Option<T> { Some(T), None } |
@@ -677,7 +734,7 @@ fn compute() -> Option<i32> { None } | |||
677 | 734 | ||
678 | #[test] | 735 | #[test] |
679 | fn doctest_replace_qualified_name_with_use() { | 736 | fn doctest_replace_qualified_name_with_use() { |
680 | check( | 737 | check_doc_test( |
681 | "replace_qualified_name_with_use", | 738 | "replace_qualified_name_with_use", |
682 | r#####" | 739 | r#####" |
683 | fn process(map: std::collections::<|>HashMap<String, String>) {} | 740 | fn process(map: std::collections::<|>HashMap<String, String>) {} |
@@ -692,7 +749,7 @@ fn process(map: HashMap<String, String>) {} | |||
692 | 749 | ||
693 | #[test] | 750 | #[test] |
694 | fn doctest_replace_unwrap_with_match() { | 751 | fn doctest_replace_unwrap_with_match() { |
695 | check( | 752 | check_doc_test( |
696 | "replace_unwrap_with_match", | 753 | "replace_unwrap_with_match", |
697 | r#####" | 754 | r#####" |
698 | enum Result<T, E> { Ok(T), Err(E) } | 755 | enum Result<T, E> { Ok(T), Err(E) } |
@@ -707,7 +764,7 @@ fn main() { | |||
707 | let x: Result<i32, i32> = Result::Ok(92); | 764 | let x: Result<i32, i32> = Result::Ok(92); |
708 | let y = match x { | 765 | let y = match x { |
709 | Ok(a) => a, | 766 | Ok(a) => a, |
710 | _ => unreachable!(), | 767 | $0_ => unreachable!(), |
711 | }; | 768 | }; |
712 | } | 769 | } |
713 | "#####, | 770 | "#####, |
@@ -716,7 +773,7 @@ fn main() { | |||
716 | 773 | ||
717 | #[test] | 774 | #[test] |
718 | fn doctest_split_import() { | 775 | fn doctest_split_import() { |
719 | check( | 776 | check_doc_test( |
720 | "split_import", | 777 | "split_import", |
721 | r#####" | 778 | r#####" |
722 | use std::<|>collections::HashMap; | 779 | use std::<|>collections::HashMap; |
@@ -726,3 +783,22 @@ use std::{collections::HashMap}; | |||
726 | "#####, | 783 | "#####, |
727 | ) | 784 | ) |
728 | } | 785 | } |
786 | |||
787 | #[test] | ||
788 | fn doctest_unwrap_block() { | ||
789 | check_doc_test( | ||
790 | "unwrap_block", | ||
791 | r#####" | ||
792 | fn foo() { | ||
793 | if true {<|> | ||
794 | println!("foo"); | ||
795 | } | ||
796 | } | ||
797 | "#####, | ||
798 | r#####" | ||
799 | fn foo() { | ||
800 | println!("foo"); | ||
801 | } | ||
802 | "#####, | ||
803 | ) | ||
804 | } | ||
diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs index efd988697..0038a9764 100644 --- a/crates/ra_assists/src/utils.rs +++ b/crates/ra_assists/src/utils.rs | |||
@@ -1,19 +1,58 @@ | |||
1 | //! Assorted functions shared by several assists. | 1 | //! Assorted functions shared by several assists. |
2 | pub(crate) mod insert_use; | 2 | pub(crate) mod insert_use; |
3 | 3 | ||
4 | use std::iter; | 4 | use std::{iter, ops}; |
5 | 5 | ||
6 | use hir::{Adt, Crate, Semantics, Trait, Type}; | 6 | use hir::{Adt, Crate, Enum, ScopeDef, Semantics, Trait, Type}; |
7 | use ra_ide_db::RootDatabase; | 7 | use ra_ide_db::RootDatabase; |
8 | use ra_syntax::{ | 8 | use ra_syntax::{ |
9 | ast::{self, make, NameOwner}, | 9 | ast::{self, make, NameOwner}, |
10 | AstNode, T, | 10 | AstNode, SyntaxNode, T, |
11 | }; | 11 | }; |
12 | use rustc_hash::FxHashSet; | 12 | use rustc_hash::FxHashSet; |
13 | 13 | ||
14 | pub use insert_use::insert_use_statement; | 14 | use crate::assist_config::SnippetCap; |
15 | 15 | ||
16 | pub fn get_missing_impl_items( | 16 | pub(crate) use insert_use::insert_use_statement; |
17 | |||
18 | #[derive(Clone, Copy, Debug)] | ||
19 | pub(crate) enum Cursor<'a> { | ||
20 | Replace(&'a SyntaxNode), | ||
21 | Before(&'a SyntaxNode), | ||
22 | } | ||
23 | |||
24 | impl<'a> Cursor<'a> { | ||
25 | fn node(self) -> &'a SyntaxNode { | ||
26 | match self { | ||
27 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
28 | } | ||
29 | } | ||
30 | } | ||
31 | |||
32 | pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor) -> String { | ||
33 | assert!(cursor.node().ancestors().any(|it| it == *node)); | ||
34 | let range = cursor.node().text_range() - node.text_range().start(); | ||
35 | let range: ops::Range<usize> = range.into(); | ||
36 | |||
37 | let mut placeholder = cursor.node().to_string(); | ||
38 | escape(&mut placeholder); | ||
39 | let tab_stop = match cursor { | ||
40 | Cursor::Replace(placeholder) => format!("${{0:{}}}", placeholder), | ||
41 | Cursor::Before(placeholder) => format!("$0{}", placeholder), | ||
42 | }; | ||
43 | |||
44 | let mut buf = node.to_string(); | ||
45 | buf.replace_range(range, &tab_stop); | ||
46 | return buf; | ||
47 | |||
48 | fn escape(buf: &mut String) { | ||
49 | stdx::replace(buf, '{', r"\{"); | ||
50 | stdx::replace(buf, '}', r"\}"); | ||
51 | stdx::replace(buf, '$', r"\$"); | ||
52 | } | ||
53 | } | ||
54 | |||
55 | pub fn get_missing_assoc_items( | ||
17 | sema: &Semantics<RootDatabase>, | 56 | sema: &Semantics<RootDatabase>, |
18 | impl_def: &ast::ImplDef, | 57 | impl_def: &ast::ImplDef, |
19 | ) -> Vec<hir::AssocItem> { | 58 | ) -> Vec<hir::AssocItem> { |
@@ -23,21 +62,21 @@ pub fn get_missing_impl_items( | |||
23 | let mut impl_type = FxHashSet::default(); | 62 | let mut impl_type = FxHashSet::default(); |
24 | 63 | ||
25 | if let Some(item_list) = impl_def.item_list() { | 64 | if let Some(item_list) = impl_def.item_list() { |
26 | for item in item_list.impl_items() { | 65 | for item in item_list.assoc_items() { |
27 | match item { | 66 | match item { |
28 | ast::ImplItem::FnDef(f) => { | 67 | ast::AssocItem::FnDef(f) => { |
29 | if let Some(n) = f.name() { | 68 | if let Some(n) = f.name() { |
30 | impl_fns_consts.insert(n.syntax().to_string()); | 69 | impl_fns_consts.insert(n.syntax().to_string()); |
31 | } | 70 | } |
32 | } | 71 | } |
33 | 72 | ||
34 | ast::ImplItem::TypeAliasDef(t) => { | 73 | ast::AssocItem::TypeAliasDef(t) => { |
35 | if let Some(n) = t.name() { | 74 | if let Some(n) = t.name() { |
36 | impl_type.insert(n.syntax().to_string()); | 75 | impl_type.insert(n.syntax().to_string()); |
37 | } | 76 | } |
38 | } | 77 | } |
39 | 78 | ||
40 | ast::ImplItem::ConstDef(c) => { | 79 | ast::AssocItem::ConstDef(c) => { |
41 | if let Some(n) = c.name() { | 80 | if let Some(n) = c.name() { |
42 | impl_fns_consts.insert(n.syntax().to_string()); | 81 | impl_fns_consts.insert(n.syntax().to_string()); |
43 | } | 82 | } |
@@ -103,7 +142,7 @@ fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> { | |||
103 | } | 142 | } |
104 | 143 | ||
105 | #[derive(Clone, Copy)] | 144 | #[derive(Clone, Copy)] |
106 | pub(crate) enum TryEnum { | 145 | pub enum TryEnum { |
107 | Result, | 146 | Result, |
108 | Option, | 147 | Option, |
109 | } | 148 | } |
@@ -111,7 +150,7 @@ pub(crate) enum TryEnum { | |||
111 | impl TryEnum { | 150 | impl TryEnum { |
112 | const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result]; | 151 | const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result]; |
113 | 152 | ||
114 | pub(crate) fn from_ty(sema: &Semantics<RootDatabase>, ty: &Type) -> Option<TryEnum> { | 153 | pub fn from_ty(sema: &Semantics<RootDatabase>, ty: &Type) -> Option<TryEnum> { |
115 | let enum_ = match ty.as_adt() { | 154 | let enum_ = match ty.as_adt() { |
116 | Some(Adt::Enum(it)) => it, | 155 | Some(Adt::Enum(it)) => it, |
117 | _ => return None, | 156 | _ => return None, |
@@ -161,13 +200,19 @@ impl FamousDefs<'_, '_> { | |||
161 | #[cfg(test)] | 200 | #[cfg(test)] |
162 | pub(crate) const FIXTURE: &'static str = r#" | 201 | pub(crate) const FIXTURE: &'static str = r#" |
163 | //- /libcore.rs crate:core | 202 | //- /libcore.rs crate:core |
164 | pub mod convert{ | 203 | pub mod convert { |
165 | pub trait From<T> { | 204 | pub trait From<T> { |
166 | fn from(T) -> Self; | 205 | fn from(T) -> Self; |
167 | } | 206 | } |
168 | } | 207 | } |
169 | 208 | ||
170 | pub mod prelude { pub use crate::convert::From } | 209 | pub mod option { |
210 | pub enum Option<T> { None, Some(T)} | ||
211 | } | ||
212 | |||
213 | pub mod prelude { | ||
214 | pub use crate::{convert::From, option::Option::{self, *}}; | ||
215 | } | ||
171 | #[prelude_import] | 216 | #[prelude_import] |
172 | pub use prelude::*; | 217 | pub use prelude::*; |
173 | "#; | 218 | "#; |
@@ -176,7 +221,25 @@ pub use prelude::*; | |||
176 | self.find_trait("core:convert:From") | 221 | self.find_trait("core:convert:From") |
177 | } | 222 | } |
178 | 223 | ||
224 | pub(crate) fn core_option_Option(&self) -> Option<Enum> { | ||
225 | self.find_enum("core:option:Option") | ||
226 | } | ||
227 | |||
179 | fn find_trait(&self, path: &str) -> Option<Trait> { | 228 | fn find_trait(&self, path: &str) -> Option<Trait> { |
229 | match self.find_def(path)? { | ||
230 | hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it), | ||
231 | _ => None, | ||
232 | } | ||
233 | } | ||
234 | |||
235 | fn find_enum(&self, path: &str) -> Option<Enum> { | ||
236 | match self.find_def(path)? { | ||
237 | hir::ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(it))) => Some(it), | ||
238 | _ => None, | ||
239 | } | ||
240 | } | ||
241 | |||
242 | fn find_def(&self, path: &str) -> Option<ScopeDef> { | ||
180 | let db = self.0.db; | 243 | let db = self.0.db; |
181 | let mut path = path.split(':'); | 244 | let mut path = path.split(':'); |
182 | let trait_ = path.next_back()?; | 245 | let trait_ = path.next_back()?; |
@@ -201,9 +264,6 @@ pub use prelude::*; | |||
201 | } | 264 | } |
202 | let def = | 265 | let def = |
203 | module.scope(db, None).into_iter().find(|(name, _def)| &name.to_string() == trait_)?.1; | 266 | module.scope(db, None).into_iter().find(|(name, _def)| &name.to_string() == trait_)?.1; |
204 | match def { | 267 | Some(def) |
205 | hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it), | ||
206 | _ => None, | ||
207 | } | ||
208 | } | 268 | } |
209 | } | 269 | } |
diff --git a/crates/ra_assists/src/utils/insert_use.rs b/crates/ra_assists/src/utils/insert_use.rs index c507e71e0..0ee43482f 100644 --- a/crates/ra_assists/src/utils/insert_use.rs +++ b/crates/ra_assists/src/utils/insert_use.rs | |||
@@ -11,17 +11,20 @@ use ra_syntax::{ | |||
11 | }; | 11 | }; |
12 | use ra_text_edit::TextEditBuilder; | 12 | use ra_text_edit::TextEditBuilder; |
13 | 13 | ||
14 | use crate::assist_context::AssistContext; | ||
15 | |||
14 | /// 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. |
15 | /// 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 |
16 | /// the cursor position given, additionally merged with the existing use imports. | 18 | /// the cursor position given, additionally merged with the existing use imports. |
17 | pub fn insert_use_statement( | 19 | pub(crate) fn insert_use_statement( |
18 | // Ideally the position of the cursor, used to | 20 | // Ideally the position of the cursor, used to |
19 | position: &SyntaxNode, | 21 | position: &SyntaxNode, |
20 | path_to_import: &ModPath, | 22 | path_to_import: &ModPath, |
21 | edit: &mut TextEditBuilder, | 23 | ctx: &AssistContext, |
24 | builder: &mut TextEditBuilder, | ||
22 | ) { | 25 | ) { |
23 | 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<_>>(); |
24 | let container = position.ancestors().find_map(|n| { | 27 | let container = ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| { |
25 | if let Some(module) = ast::Module::cast(n.clone()) { | 28 | if let Some(module) = ast::Module::cast(n.clone()) { |
26 | return module.item_list().map(|it| it.syntax().clone()); | 29 | return module.item_list().map(|it| it.syntax().clone()); |
27 | } | 30 | } |
@@ -30,7 +33,7 @@ pub fn insert_use_statement( | |||
30 | 33 | ||
31 | if let Some(container) = container { | 34 | if let Some(container) = container { |
32 | let action = best_action_for_target(container, position.clone(), &target); | 35 | let action = best_action_for_target(container, position.clone(), &target); |
33 | make_assist(&action, &target, edit); | 36 | make_assist(&action, &target, builder); |
34 | } | 37 | } |
35 | } | 38 | } |
36 | 39 | ||