diff options
author | Galilée 'Bill' Enguehard <[email protected]> | 2020-05-21 22:27:38 +0100 |
---|---|---|
committer | Galilée 'Bill' Enguehard <[email protected]> | 2020-05-21 22:27:38 +0100 |
commit | 7fece3bdd2450c0807f7dd742239cae95f0cc65e (patch) | |
tree | 866c4db826c959e79c63a6727bdb9f2c61e6fc4f /crates/ra_assists | |
parent | db926218b2082077750291f8426ddd28b284cd08 (diff) | |
parent | 59732df8d40dfadc6dcf5951265416576399712a (diff) |
Merge branch 'master' of github.com:rust-analyzer/rust-analyzer into modname_spacing
Diffstat (limited to 'crates/ra_assists')
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) { |