diff options
Diffstat (limited to 'crates')
409 files changed, 23590 insertions, 17345 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 | ||