aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r--crates/ra_assists/src/assist_config.rs27
-rw-r--r--crates/ra_assists/src/assist_context.rs257
-rw-r--r--crates/ra_assists/src/assist_ctx.rs257
-rw-r--r--crates/ra_assists/src/ast_transform.rs24
-rw-r--r--crates/ra_assists/src/doc_tests.rs35
-rw-r--r--crates/ra_assists/src/handlers/add_custom_impl.rs65
-rw-r--r--crates/ra_assists/src/handlers/add_derive.rs37
-rw-r--r--crates/ra_assists/src/handlers/add_explicit_type.rs50
-rw-r--r--crates/ra_assists/src/handlers/add_from_impl_for_enum.rs44
-rw-r--r--crates/ra_assists/src/handlers/add_function.rs355
-rw-r--r--crates/ra_assists/src/handlers/add_impl.rs42
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs341
-rw-r--r--crates/ra_assists/src/handlers/add_new.rs71
-rw-r--r--crates/ra_assists/src/handlers/add_turbo_fish.rs134
-rw-r--r--crates/ra_assists/src/handlers/apply_demorgan.rs17
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs109
-rw-r--r--crates/ra_assists/src/handlers/change_return_type_to_result.rs961
-rw-r--r--crates/ra_assists/src/handlers/change_visibility.rs96
-rw-r--r--crates/ra_assists/src/handlers/early_return.rs55
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs237
-rw-r--r--crates/ra_assists/src/handlers/fix_visibility.rs559
-rw-r--r--crates/ra_assists/src/handlers/flip_binexpr.rs23
-rw-r--r--crates/ra_assists/src/handlers/flip_comma.rs11
-rw-r--r--crates/ra_assists/src/handlers/flip_trait_bound.rs24
-rw-r--r--crates/ra_assists/src/handlers/inline_local_variable.rs95
-rw-r--r--crates/ra_assists/src/handlers/introduce_variable.rs140
-rw-r--r--crates/ra_assists/src/handlers/invert_if.rs45
-rw-r--r--crates/ra_assists/src/handlers/merge_imports.rs38
-rw-r--r--crates/ra_assists/src/handlers/merge_match_arms.rs34
-rw-r--r--crates/ra_assists/src/handlers/move_bounds.rs18
-rw-r--r--crates/ra_assists/src/handlers/move_guard.rs54
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs54
-rw-r--r--crates/ra_assists/src/handlers/remove_dbg.rs47
-rw-r--r--crates/ra_assists/src/handlers/remove_mut.rs10
-rw-r--r--crates/ra_assists/src/handlers/reorder_fields.rs39
-rw-r--r--crates/ra_assists/src/handlers/replace_if_let_with_match.rs54
-rw-r--r--crates/ra_assists/src/handlers/replace_let_with_if_let.rs24
-rw-r--r--crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs61
-rw-r--r--crates/ra_assists/src/handlers/replace_unwrap_with_match.rs60
-rw-r--r--crates/ra_assists/src/handlers/split_import.rs16
-rw-r--r--crates/ra_assists/src/handlers/unwrap_block.rs512
-rw-r--r--crates/ra_assists/src/lib.rs307
-rw-r--r--crates/ra_assists/src/marks.rs12
-rw-r--r--crates/ra_assists/src/tests.rs153
-rw-r--r--crates/ra_assists/src/tests/generated.rs (renamed from crates/ra_assists/src/doc_tests/generated.rs)174
-rw-r--r--crates/ra_assists/src/utils.rs94
-rw-r--r--crates/ra_assists/src/utils/insert_use.rs11
47 files changed, 4197 insertions, 1686 deletions
diff --git a/crates/ra_assists/src/assist_config.rs b/crates/ra_assists/src/assist_config.rs
new file mode 100644
index 000000000..c0a0226fb
--- /dev/null
+++ b/crates/ra_assists/src/assist_config.rs
@@ -0,0 +1,27 @@
1//! Settings for tweaking assists.
2//!
3//! The fun thing here is `SnippetCap` -- this type can only be created in this
4//! module, and we use to statically check that we only produce snippet
5//! assists if we are allowed to.
6
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct AssistConfig {
9 pub snippet_cap: Option<SnippetCap>,
10}
11
12impl 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)]
19pub struct SnippetCap {
20 _private: (),
21}
22
23impl 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
3use algo::find_covering_element;
4use hir::Semantics;
5use ra_db::{FileId, FileRange};
6use ra_fmt::{leading_indent, reindent};
7use ra_ide_db::{
8 source_change::{SingleFileChange, SourceChange},
9 RootDatabase,
10};
11use ra_syntax::{
12 algo::{self, find_node_at_offset, SyntaxRewriter},
13 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
14 TokenAtOffset,
15};
16use ra_text_edit::TextEditBuilder;
17
18use crate::{
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 :-)
53pub(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
61impl<'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
98pub(crate) struct Assists {
99 resolve: bool,
100 file: FileId,
101 buf: Vec<(Assist, Option<SourceChange>)>,
102}
103
104impl 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
172pub(crate) struct AssistBuilder {
173 edit: TextEditBuilder,
174 file: FileId,
175 is_snippet: bool,
176}
177
178impl 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.
2use hir::Semantics;
3use ra_db::FileRange;
4use ra_fmt::{leading_indent, reindent};
5use ra_ide_db::RootDatabase;
6use ra_syntax::{
7 algo::{self, find_covering_element, find_node_at_offset},
8 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
9 TokenAtOffset,
10};
11use ra_text_edit::TextEditBuilder;
12
13use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
14use algo::SyntaxRewriter;
15
16#[derive(Clone, Debug)]
17pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);
18
19#[derive(Clone, Debug)]
20pub(crate) struct AssistInfo {
21 pub(crate) label: AssistLabel,
22 pub(crate) group_label: Option<GroupLabel>,
23 pub(crate) action: Option<AssistAction>,
24}
25
26impl 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
46pub(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)]
79pub(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
87impl<'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
141pub(crate) struct AssistGroup<'a> {
142 ctx: AssistCtx<'a>,
143 group_name: String,
144 assists: Vec<AssistInfo>,
145}
146
147impl<'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)]
179pub(crate) struct ActionBuilder {
180 edit: TextEditBuilder,
181 cursor_position: Option<TextSize>,
182 target: Option<TextRange>,
183 file: AssistFile,
184}
185
186impl 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.
2use rustc_hash::FxHashMap; 2use rustc_hash::FxHashMap;
3 3
4use hir::{PathResolution, SemanticsScope}; 4use hir::{HirDisplay, PathResolution, SemanticsScope};
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use 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
6mod generated;
7
8use ra_db::FileRange;
9use test_utils::{assert_eq_text, extract_range_or_offset};
10
11use crate::resolved_assists;
12
13fn check(assist_id: &str, before: &str, after: &str) {
14 let (selection, before) = extract_range_or_offset(before);
15 let (db, file_id) = crate::helpers::with_single_file(&before);
16 let frange = FileRange { file_id, range: selection.into() };
17
18 let assist = resolved_assists(&db, frange)
19 .into_iter()
20 .find(|assist| assist.label.id.0 == assist_id)
21 .unwrap_or_else(|| {
22 panic!(
23 "\n\nAssist is not applicable: {}\nAvailable assists: {}",
24 assist_id,
25 resolved_assists(&db, frange)
26 .into_iter()
27 .map(|assist| assist.label.id.0)
28 .collect::<Vec<_>>()
29 .join(", ")
30 )
31 });
32
33 let actual = 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};
7use stdx::SepBy; 7use stdx::SepBy;
8 8
9use crate::{Assist, AssistCtx, AssistId}; 9use crate::{
10 assist_context::{AssistContext, Assists},
11 AssistId,
12};
10 13
11// Assist: add_custom_impl 14// Assist: add_custom_impl
12// 15//
@@ -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// ```
28pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> { 31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
29 let input = ctx.find_node_at_offset::<ast::AttrInput>()?; 32 let input = ctx.find_node_at_offset::<ast::AttrInput>()?;
30 let attr = input.syntax().parent().and_then(ast::Attr::cast)?; 33 let attr = input.syntax().parent().and_then(ast::Attr::cast)?;
31 34
@@ -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)]
97mod tests { 100mod 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
117impl Debug for Foo { 120impl Debug for Foo {
118<|> 121 $0
119} 122}
120 ", 123 ",
121 ) 124 )
@@ -137,7 +140,7 @@ pub struct Foo {
137} 140}
138 141
139impl Debug for Foo { 142impl Debug for Foo {
140<|> 143 $0
141} 144}
142 ", 145 ",
143 ) 146 )
@@ -156,7 +159,7 @@ struct Foo {}
156struct Foo {} 159struct Foo {}
157 160
158impl Debug for Foo { 161impl 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
7use crate::{Assist, AssistCtx, AssistId}; 7use 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// ```
27pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> { 27pub(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)]
59mod tests { 65mod 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)]
99struct Foo { a: i32, } 106struct 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
7use crate::{Assist, AssistCtx, AssistId}; 7use crate::{AssistContext, AssistId, Assists};
8 8
9// Assist: add_explicit_type 9// Assist: add_explicit_type
10// 10//
@@ -21,12 +21,12 @@ use crate::{Assist, AssistCtx, AssistId};
21// let x: i32 = 92; 21// let x: i32 = 92;
22// } 22// }
23// ``` 23// ```
24pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> { 24pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let stmt = ctx.find_node_at_offset::<LetStmt>()?; 25 let stmt = ctx.find_node_at_offset::<LetStmt>()?;
26 let 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> {
77mod tests { 77mod tests {
78 use super::*; 78 use super::*;
79 79
80 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 80 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
81 81
82 #[test] 82 #[test]
83 fn add_explicit_type_target() { 83 fn add_explicit_type_target() {
@@ -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
210fn main() { 206fn 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 @@
1use ra_ide_db::RootDatabase; 1use ra_ide_db::RootDatabase;
2use ra_syntax::{ 2use ra_syntax::ast::{self, AstNode, NameOwner};
3 ast::{self, AstNode, NameOwner}, 3use test_utils::mark;
4 TextSize,
5};
6use stdx::format_to;
7 4
8use crate::{utils::FamousDefs, Assist, AssistCtx, AssistId}; 5use crate::{utils::FamousDefs, AssistContext, AssistId, Assists};
9use 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// ```
28pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> { 24pub(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
60impl From<{0}> for {1} {{ 56impl 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)]
97mod tests { 92mod 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 { 106impl 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 { 121impl 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#"
159enum A { <|>One(u32), } 155enum 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 { 183impl 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 @@
1use hir::HirDisplay;
2use ra_db::FileId;
1use ra_syntax::{ 3use 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
6use crate::{Assist, AssistCtx, AssistFile, AssistId};
7use ast::{edit::IndentLevel, ArgListOwner, ModuleItemOwner};
8use hir::HirDisplay;
9use rustc_hash::{FxHashMap, FxHashSet}; 11use rustc_hash::{FxHashMap, FxHashSet};
10 12
13use 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// ```
36pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> { 44pub(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
71struct FunctionTemplate { 76struct 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
85impl 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
78struct FunctionBuilder { 99struct 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
87impl FunctionBuilder { 108impl 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
179impl GeneratedFunctionTarget {
180 fn syntax(&self) -> &SyntaxNode {
181 match self {
182 GeneratedFunctionTarget::BehindItem(it) => it,
183 GeneratedFunctionTarget::InEmptyItemList(it) => it.syntax(),
184 }
185 }
186}
187
153fn fn_name(call: &ast::Path) -> Option<ast::Name> { 188fn 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
159fn fn_args( 194fn 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
227fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option<String> { 262fn 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
259fn next_space_for_fn_in_module( 303fn 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)]
285mod tests { 328mod 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
304fn bar() { 347fn bar() {
305 <|>todo!() 348 ${0:todo!()}
306} 349}
307", 350",
308 ) 351 )
@@ -329,7 +372,7 @@ impl Foo {
329} 372}
330 373
331fn bar() { 374fn bar() {
332 <|>todo!() 375 ${0:todo!()}
333} 376}
334", 377",
335 ) 378 )
@@ -353,7 +396,7 @@ fn foo1() {
353} 396}
354 397
355fn bar() { 398fn bar() {
356 <|>todo!() 399 ${0:todo!()}
357} 400}
358 401
359fn foo2() {} 402fn 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
407fn bar(baz: Baz) { 450fn bar(baz: Baz) {
408 <|>todo!() 451 ${0:todo!()}
409} 452}
410", 453",
411 ); 454 );
@@ -438,7 +481,7 @@ impl Baz {
438} 481}
439 482
440fn bar(baz: Baz) { 483fn bar(baz: Baz) {
441 <|>todo!() 484 ${0:todo!()}
442} 485}
443", 486",
444 ) 487 )
@@ -459,7 +502,7 @@ fn foo() {
459} 502}
460 503
461fn bar(arg: &str) { 504fn bar(arg: &str) {
462 <|>todo!() 505 ${0:todo!()}
463} 506}
464"#, 507"#,
465 ) 508 )
@@ -480,7 +523,7 @@ fn foo() {
480} 523}
481 524
482fn bar(arg: char) { 525fn bar(arg: char) {
483 <|>todo!() 526 ${0:todo!()}
484} 527}
485"#, 528"#,
486 ) 529 )
@@ -501,7 +544,7 @@ fn foo() {
501} 544}
502 545
503fn bar(arg: i32) { 546fn bar(arg: i32) {
504 <|>todo!() 547 ${0:todo!()}
505} 548}
506", 549",
507 ) 550 )
@@ -522,7 +565,7 @@ fn foo() {
522} 565}
523 566
524fn bar(arg: u8) { 567fn bar(arg: u8) {
525 <|>todo!() 568 ${0:todo!()}
526} 569}
527", 570",
528 ) 571 )
@@ -547,7 +590,7 @@ fn foo() {
547} 590}
548 591
549fn bar(x: u8) { 592fn bar(x: u8) {
550 <|>todo!() 593 ${0:todo!()}
551} 594}
552", 595",
553 ) 596 )
@@ -570,7 +613,7 @@ fn foo() {
570} 613}
571 614
572fn bar(worble: ()) { 615fn bar(worble: ()) {
573 <|>todo!() 616 ${0:todo!()}
574} 617}
575", 618",
576 ) 619 )
@@ -599,15 +642,40 @@ fn baz() {
599} 642}
600 643
601fn bar(foo: impl Foo) { 644fn 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"
656struct Baz;
657fn baz() -> Baz { todo!() }
658
659fn foo() {
660 bar<|>(&baz())
661}
662",
663 r"
664struct Baz;
665fn baz() -> Baz { todo!() }
666
667fn foo() {
668 bar(&baz())
669}
670
671fn 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}
619mod Foo { 687fn 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}
630mod Foo { 696fn foo() {
631 fn foo() { 697 bar(Baz::baz())
632 bar(super::Baz::baz()) 698}
633 }
634 699
635 fn bar(baz: super::Baz::Bof) { 700fn 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
659fn bar<T>(t: T) { 723fn 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
690fn bar(arg: fn() -> Baz) { 754fn 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
715fn bar(closure: impl Fn(i64) -> i64) { 779fn 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
736fn bar(baz: ()) { 800fn bar(baz: ()) {
737 <|>todo!() 801 ${0:todo!()}
738} 802}
739", 803",
740 ) 804 )
@@ -759,7 +823,7 @@ fn foo() {
759} 823}
760 824
761fn bar(baz_1: Baz, baz_2: Baz) { 825fn 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
786fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) { 850fn 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"
805mod bar { 869mod 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 "
890mod foo {
891 pub struct Foo;
892}
893fn bar() {
894 use foo::Foo;
895 let foo = Foo;
896 baz<|>(foo)
897}
898",
899 "
900mod foo {
901 pub struct Foo;
902}
903fn bar() {
904 use foo::Foo;
905 let foo = Foo;
906 baz(foo)
907}
908
909fn 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() {
861mod bar { 959mod 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
892pub(crate) fn bar() { 990pub(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"
937fn 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 @@
1use ra_syntax::{ 1use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner};
2 ast::{self, AstNode, NameOwner, TypeParamsOwner},
3 TextSize,
4};
5use stdx::{format_to, SepBy}; 2use stdx::{format_to, SepBy};
6 3
7use crate::{Assist, AssistCtx, AssistId}; 4use 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// ```
28pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> { 25pub(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)]
61mod tests { 64mod 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 @@
1use hir::HasSource; 1use hir::HasSource;
2use ra_syntax::{ 2use 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
7use crate::{ 11use 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// ```
47pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option<Assist> { 54pub(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// ```
88pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option<Assist> { 96pub(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
97fn add_missing_impl_members_inner( 106fn 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
166fn add_body(fn_def: ast::FnDef) -> ast::FnDef { 191fn 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)]
175mod tests { 200mod 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#"
185trait Foo { 210trait Foo {
186 type Output; 211 type Output;
187 212
@@ -197,8 +222,8 @@ struct S;
197impl Foo for S { 222impl Foo for S {
198 fn bar(&self) {} 223 fn bar(&self) {}
199<|> 224<|>
200}", 225}"#,
201 " 226 r#"
202trait Foo { 227trait Foo {
203 type Output; 228 type Output;
204 229
@@ -213,12 +238,16 @@ struct S;
213 238
214impl Foo for S { 239impl 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#"
230trait Foo { 259trait 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;
238impl Foo for S { 267impl Foo for S {
239 fn bar(&self) {} 268 fn bar(&self) {}
240<|> 269<|>
241}", 270}"#,
242 " 271 r#"
243trait Foo { 272trait 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
251impl Foo for S { 280impl 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#"
264trait Foo { fn foo(&self); } 295trait Foo { fn foo(&self); }
265struct S; 296struct S;
266impl Foo for S { <|> }", 297impl Foo for S { <|> }"#,
267 " 298 r#"
268trait Foo { fn foo(&self); } 299trait Foo { fn foo(&self); }
269struct S; 300struct S;
270impl Foo for S { 301impl 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#"
281trait Foo<T> { fn foo(&self, t: T) -> &T; } 314trait Foo<T> { fn foo(&self, t: T) -> &T; }
282struct S; 315struct S;
283impl Foo<u32> for S { <|> }", 316impl Foo<u32> for S { <|> }"#,
284 " 317 r#"
285trait Foo<T> { fn foo(&self, t: T) -> &T; } 318trait Foo<T> { fn foo(&self, t: T) -> &T; }
286struct S; 319struct S;
287impl Foo<u32> for S { 320impl 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#"
298trait Foo<T> { fn foo(&self, t: T) -> &T; } 333trait Foo<T> { fn foo(&self, t: T) -> &T; }
299struct S; 334struct S;
300impl<U> Foo<U> for S { <|> }", 335impl<U> Foo<U> for S { <|> }"#,
301 " 336 r#"
302trait Foo<T> { fn foo(&self, t: T) -> &T; } 337trait Foo<T> { fn foo(&self, t: T) -> &T; }
303struct S; 338struct S;
304impl<U> Foo<U> for S { 339impl<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#"
315trait Foo { fn foo(&self); } 352trait Foo { fn foo(&self); }
316struct S; 353struct S;
317impl Foo for S {}<|>", 354impl Foo for S {}<|>"#,
318 " 355 r#"
319trait Foo { fn foo(&self); } 356trait Foo { fn foo(&self); }
320struct S; 357struct S;
321impl Foo for S { 358impl 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#"
332mod foo { 371mod 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}
336struct S; 375struct S;
337impl foo::Foo for S { <|> }", 376impl foo::Foo for S { <|> }"#,
338 " 377 r#"
339mod foo { 378mod 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}
343struct S; 382struct S;
344impl foo::Foo for S { 383impl 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#"
355mod foo { 396mod 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}
359struct S; 400struct S;
360impl foo::Foo for S { <|> }", 401impl foo::Foo for S { <|> }"#,
361 " 402 r#"
362mod foo { 403mod 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}
366struct S; 407struct S;
367impl foo::Foo for S { 408impl 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#"
378mod foo { 421mod 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}
382struct S; 425struct S;
383impl foo::Foo<u32> for S { <|> }", 426impl foo::Foo<u32> for S { <|> }"#,
384 " 427 r#"
385mod foo { 428mod 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}
389struct S; 432struct S;
390impl foo::Foo<u32> for S { 433impl 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#"
402mod foo { 447mod 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}
406struct Param; 451struct Param;
407struct S; 452struct S;
408impl foo::Foo<Param> for S { <|> }", 453impl foo::Foo<Param> for S { <|> }"#,
409 " 454 r#"
410mod foo { 455mod 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 {
414struct Param; 459struct Param;
415struct S; 460struct S;
416impl foo::Foo<Param> for S { 461impl 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#"
427mod foo { 474mod 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}
432struct S; 479struct S;
433impl foo::Foo for S { <|> }", 480impl foo::Foo for S { <|> }"#,
434 " 481 r#"
435mod foo { 482mod 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}
440struct S; 487struct S;
441impl foo::Foo for S { 488impl 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#"
452mod foo { 501mod 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}
457struct S; 506struct S;
458impl foo::Foo for S { <|> }", 507impl foo::Foo for S { <|> }"#,
459 " 508 r#"
460mod foo { 509mod 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}
465struct S; 514struct S;
466impl foo::Foo for S { 515impl 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#"
477mod foo { 528mod 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}
481struct S; 532struct S;
482impl foo::Foo for S { <|> }", 533impl foo::Foo for S { <|> }"#,
483 " 534 r#"
484mod foo { 535mod 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}
488struct S; 539struct S;
489impl foo::Foo for S { 540impl 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#"
500trait Foo; 553trait Foo;
501struct S; 554struct S;
502impl Foo for S { <|> }", 555impl 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#"
511trait Foo { 564trait 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}
515struct S; 568struct S;
516impl Foo for S { <|> }", 569impl Foo for S { <|> }"#,
517 ) 570 )
518 } 571 }
519 572
@@ -543,8 +596,10 @@ trait Foo {
543} 596}
544struct S; 597struct S;
545impl Foo for S { 598impl 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#"
557trait Foo { 612trait 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}
565struct S; 620struct S;
566impl Foo for S { <|> }", 621impl Foo for S { <|> }"#,
567 " 622 r#"
568trait Foo { 623trait Foo {
569 type Output; 624 type Output;
570 625
@@ -575,8 +630,58 @@ trait Foo {
575} 630}
576struct S; 631struct S;
577impl Foo for S { 632impl 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#"
643trait Foo<T = Self> {
644 fn bar(&self, other: &T);
645}
646
647struct S;
648impl Foo for S { <|> }"#,
649 r#"
650trait Foo<T = Self> {
651 fn bar(&self, other: &T);
652}
653
654struct S;
655impl 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#"
668trait Foo<T1, T2 = Self> {
669 fn bar(&self, this: &T1, that: &T2);
670}
671
672struct S<T>;
673impl Foo<T> for S<T> { <|> }"#,
674 r#"
675trait Foo<T1, T2 = Self> {
676 fn bar(&self, this: &T1, that: &T2);
677}
678
679struct S<T>;
680impl 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};
8use stdx::{format_to, SepBy}; 8use stdx::{format_to, SepBy};
9 9
10use crate::{Assist, AssistCtx, AssistId}; 10use crate::{AssistContext, AssistId, Assists};
11 11
12// Assist: add_new 12// Assist: add_new
13// 13//
@@ -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// ```
32pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> { 32pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let strukt = ctx.find_node_at_offset::<ast::StructDef>()?; 33 let strukt = ctx.find_node_at_offset::<ast::StructDef>()?;
34 34
35 // We want to only apply this to non-union structs with named fields 35 // We want to only apply this to non-union structs with named fields
@@ -41,33 +41,27 @@ pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> {
41 // Return early if we've found an existing new fn 41 // Return early if we've found an existing new fn
42 let impl_def = find_struct_impl(&ctx, &strukt)?; 42 let impl_def = find_struct_impl(&ctx, &strukt)?;
43 43
44 ctx.add_assist(AssistId("add_new"), "Add default constructor", |edit| { 44 let target = strukt.syntax().text_range();
45 edit.target(strukt.syntax().text_range()); 45 acc.add(AssistId("add_new"), "Add default constructor", target, |builder| {
46
47 let mut buf = String::with_capacity(512); 46 let mut buf = String::with_capacity(512);
48 47
49 if impl_def.is_some() { 48 if impl_def.is_some() {
50 buf.push('\n'); 49 buf.push('\n');
51 } 50 }
52 51
53 let vis = strukt.visibility().map(|v| format!("{} ", v)); 52 let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
54 let vis = vis.as_deref().unwrap_or("");
55 53
56 let params = field_list 54 let params = field_list
57 .fields() 55 .fields()
58 .filter_map(|f| { 56 .filter_map(|f| {
59 Some(format!( 57 Some(format!("{}: {}", f.name()?.syntax(), f.ascribed_type()?.syntax()))
60 "{}: {}",
61 f.name()?.syntax().text(),
62 f.ascribed_type()?.syntax().text()
63 ))
64 }) 58 })
65 .sep_by(", "); 59 .sep_by(", ");
66 let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", "); 60 let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", ");
67 61
68 format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields); 62 format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields);
69 63
70 let (start_offset, end_offset) = impl_def 64 let start_offset = impl_def
71 .and_then(|impl_def| { 65 .and_then(|impl_def| {
72 buf.push('\n'); 66 buf.push('\n');
73 let start = impl_def 67 let start = impl_def
@@ -77,17 +71,20 @@ pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> {
77 .text_range() 71 .text_range()
78 .end(); 72 .end();
79 73
80 Some((start, TextSize::of("\n"))) 74 Some(start)
81 }) 75 })
82 .unwrap_or_else(|| { 76 .unwrap_or_else(|| {
83 buf = generate_impl_text(&strukt, &buf); 77 buf = generate_impl_text(&strukt, &buf);
84 let start = strukt.syntax().text_range().end(); 78 strukt.syntax().text_range().end()
85
86 (start, TextSize::of("\n}\n"))
87 }); 79 });
88 80
89 edit.set_cursor(start_offset + TextSize::of(&buf) - end_offset); 81 match ctx.config.snippet_cap {
90 edit.insert(start_offset, buf); 82 None => builder.insert(start_offset, buf),
83 Some(cap) => {
84 buf = buf.replace("fn new", "fn $0new");
85 builder.insert_snippet(cap, start_offset, buf);
86 }
87 }
91 }) 88 })
92} 89}
93 90
@@ -124,7 +121,7 @@ fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String {
124// 121//
125// FIXME: change the new fn checking to a more semantic approach when that's more 122// FIXME: change the new fn checking to a more semantic approach when that's more
126// viable (e.g. we process proc macros, etc) 123// viable (e.g. we process proc macros, etc)
127fn find_struct_impl(ctx: &AssistCtx, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> { 124fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> {
128 let db = ctx.db; 125 let db = ctx.db;
129 let module = strukt.syntax().ancestors().find(|node| { 126 let module = strukt.syntax().ancestors().find(|node| {
130 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) 127 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
@@ -162,8 +159,8 @@ fn find_struct_impl(ctx: &AssistCtx, strukt: &ast::StructDef) -> Option<Option<a
162 159
163fn has_new_fn(imp: &ast::ImplDef) -> bool { 160fn has_new_fn(imp: &ast::ImplDef) -> bool {
164 if let Some(il) = imp.item_list() { 161 if let Some(il) = imp.item_list() {
165 for item in il.impl_items() { 162 for item in il.assoc_items() {
166 if let ast::ImplItem::FnDef(f) = item { 163 if let ast::AssocItem::FnDef(f) = item {
167 if let Some(name) = f.name() { 164 if let Some(name) = f.name() {
168 if name.text().eq_ignore_ascii_case("new") { 165 if name.text().eq_ignore_ascii_case("new") {
169 return true; 166 return true;
@@ -178,7 +175,7 @@ fn has_new_fn(imp: &ast::ImplDef) -> bool {
178 175
179#[cfg(test)] 176#[cfg(test)]
180mod tests { 177mod tests {
181 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 178 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
182 179
183 use super::*; 180 use super::*;
184 181
@@ -192,7 +189,7 @@ mod tests {
192"struct Foo {} 189"struct Foo {}
193 190
194impl Foo { 191impl Foo {
195 fn new() -> Self { Self { } }<|> 192 fn $0new() -> Self { Self { } }
196} 193}
197", 194",
198 ); 195 );
@@ -202,7 +199,7 @@ impl Foo {
202"struct Foo<T: Clone> {} 199"struct Foo<T: Clone> {}
203 200
204impl<T: Clone> Foo<T> { 201impl<T: Clone> Foo<T> {
205 fn new() -> Self { Self { } }<|> 202 fn $0new() -> Self { Self { } }
206} 203}
207", 204",
208 ); 205 );
@@ -212,7 +209,7 @@ impl<T: Clone> Foo<T> {
212"struct Foo<'a, T: Foo<'a>> {} 209"struct Foo<'a, T: Foo<'a>> {}
213 210
214impl<'a, T: Foo<'a>> Foo<'a, T> { 211impl<'a, T: Foo<'a>> Foo<'a, T> {
215 fn new() -> Self { Self { } }<|> 212 fn $0new() -> Self { Self { } }
216} 213}
217", 214",
218 ); 215 );
@@ -222,7 +219,7 @@ impl<'a, T: Foo<'a>> Foo<'a, T> {
222"struct Foo { baz: String } 219"struct Foo { baz: String }
223 220
224impl Foo { 221impl Foo {
225 fn new(baz: String) -> Self { Self { baz } }<|> 222 fn $0new(baz: String) -> Self { Self { baz } }
226} 223}
227", 224",
228 ); 225 );
@@ -232,7 +229,7 @@ impl Foo {
232"struct Foo { baz: String, qux: Vec<i32> } 229"struct Foo { baz: String, qux: Vec<i32> }
233 230
234impl Foo { 231impl Foo {
235 fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|> 232 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
236} 233}
237", 234",
238 ); 235 );
@@ -244,7 +241,7 @@ impl Foo {
244"struct Foo { pub baz: String, pub qux: Vec<i32> } 241"struct Foo { pub baz: String, pub qux: Vec<i32> }
245 242
246impl Foo { 243impl Foo {
247 fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|> 244 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
248} 245}
249", 246",
250 ); 247 );
@@ -259,7 +256,7 @@ impl Foo {}
259"struct Foo {} 256"struct Foo {}
260 257
261impl Foo { 258impl Foo {
262 fn new() -> Self { Self { } }<|> 259 fn $0new() -> Self { Self { } }
263} 260}
264", 261",
265 ); 262 );
@@ -274,7 +271,7 @@ impl Foo {
274"struct Foo {} 271"struct Foo {}
275 272
276impl Foo { 273impl Foo {
277 fn new() -> Self { Self { } }<|> 274 fn $0new() -> Self { Self { } }
278 275
279 fn qux(&self) {} 276 fn qux(&self) {}
280} 277}
@@ -295,7 +292,7 @@ impl Foo {
295"struct Foo {} 292"struct Foo {}
296 293
297impl Foo { 294impl Foo {
298 fn new() -> Self { Self { } }<|> 295 fn $0new() -> Self { Self { } }
299 296
300 fn qux(&self) {} 297 fn qux(&self) {}
301 fn baz() -> i32 { 298 fn baz() -> i32 {
@@ -312,7 +309,7 @@ impl Foo {
312"pub struct Foo {} 309"pub struct Foo {}
313 310
314impl Foo { 311impl Foo {
315 pub fn new() -> Self { Self { } }<|> 312 pub fn $0new() -> Self { Self { } }
316} 313}
317", 314",
318 ); 315 );
@@ -322,7 +319,7 @@ impl Foo {
322"pub(crate) struct Foo {} 319"pub(crate) struct Foo {}
323 320
324impl Foo { 321impl Foo {
325 pub(crate) fn new() -> Self { Self { } }<|> 322 pub(crate) fn $0new() -> Self { Self { } }
326} 323}
327", 324",
328 ); 325 );
@@ -415,7 +412,7 @@ pub struct Source<T> {
415} 412}
416 413
417impl<T> Source<T> { 414impl<T> Source<T> {
418 pub fn new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }<|> 415 pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }
419 416
420 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { 417 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
421 Source { file_id: self.file_id, ast: f(self.ast) } 418 Source { file_id: self.file_id, ast: f(self.ast) }
diff --git a/crates/ra_assists/src/handlers/add_turbo_fish.rs b/crates/ra_assists/src/handlers/add_turbo_fish.rs
new file mode 100644
index 000000000..26acf81f2
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_turbo_fish.rs
@@ -0,0 +1,134 @@
1use ra_ide_db::defs::{classify_name_ref, Definition, NameRefClass};
2use ra_syntax::{ast, AstNode, SyntaxKind, T};
3use test_utils::mark;
4
5use crate::{
6 assist_context::{AssistContext, Assists},
7 AssistId,
8};
9
10// Assist: add_turbo_fish
11//
12// Adds `::<_>` to a call of a generic method or function.
13//
14// ```
15// fn make<T>() -> T { todo!() }
16// fn main() {
17// let x = make<|>();
18// }
19// ```
20// ->
21// ```
22// fn make<T>() -> T { todo!() }
23// fn main() {
24// let x = make::<${0:_}>();
25// }
26// ```
27pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let ident = ctx.find_token_at_offset(SyntaxKind::IDENT)?;
29 let next_token = ident.next_token()?;
30 if next_token.kind() == T![::] {
31 mark::hit!(add_turbo_fish_one_fish_is_enough);
32 return None;
33 }
34 let name_ref = ast::NameRef::cast(ident.parent())?;
35 let def = match classify_name_ref(&ctx.sema, &name_ref)? {
36 NameRefClass::Definition(def) => def,
37 NameRefClass::FieldShorthand { .. } => return None,
38 };
39 let fun = match def {
40 Definition::ModuleDef(hir::ModuleDef::Function(it)) => it,
41 _ => return None,
42 };
43 let generics = hir::GenericDef::Function(fun).params(ctx.sema.db);
44 if generics.is_empty() {
45 mark::hit!(add_turbo_fish_non_generic);
46 return None;
47 }
48 acc.add(AssistId("add_turbo_fish"), "Add `::<>`", ident.text_range(), |builder| {
49 match ctx.config.snippet_cap {
50 Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"),
51 None => builder.insert(ident.text_range().end(), "::<_>"),
52 }
53 })
54}
55
56#[cfg(test)]
57mod tests {
58 use crate::tests::{check_assist, check_assist_not_applicable};
59
60 use super::*;
61 use test_utils::mark;
62
63 #[test]
64 fn add_turbo_fish_function() {
65 check_assist(
66 add_turbo_fish,
67 r#"
68fn make<T>() -> T {}
69fn main() {
70 make<|>();
71}
72"#,
73 r#"
74fn make<T>() -> T {}
75fn main() {
76 make::<${0:_}>();
77}
78"#,
79 );
80 }
81
82 #[test]
83 fn add_turbo_fish_method() {
84 check_assist(
85 add_turbo_fish,
86 r#"
87struct S;
88impl S {
89 fn make<T>(&self) -> T {}
90}
91fn main() {
92 S.make<|>();
93}
94"#,
95 r#"
96struct S;
97impl S {
98 fn make<T>(&self) -> T {}
99}
100fn main() {
101 S.make::<${0:_}>();
102}
103"#,
104 );
105 }
106
107 #[test]
108 fn add_turbo_fish_one_fish_is_enough() {
109 mark::check!(add_turbo_fish_one_fish_is_enough);
110 check_assist_not_applicable(
111 add_turbo_fish,
112 r#"
113fn make<T>() -> T {}
114fn main() {
115 make<|>::<()>();
116}
117"#,
118 );
119 }
120
121 #[test]
122 fn add_turbo_fish_non_generic() {
123 mark::check!(add_turbo_fish_non_generic);
124 check_assist_not_applicable(
125 add_turbo_fish,
126 r#"
127fn make() -> () {}
128fn main() {
129 make<|>();
130}
131"#,
132 );
133 }
134}
diff --git a/crates/ra_assists/src/handlers/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs
index 260b9e073..233e8fb8e 100644
--- a/crates/ra_assists/src/handlers/apply_demorgan.rs
+++ b/crates/ra_assists/src/handlers/apply_demorgan.rs
@@ -1,6 +1,6 @@
1use ra_syntax::ast::{self, AstNode}; 1use ra_syntax::ast::{self, AstNode};
2 2
3use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; 3use crate::{utils::invert_boolean_expression, AssistContext, AssistId, Assists};
4 4
5// Assist: apply_demorgan 5// Assist: apply_demorgan
6// 6//
@@ -21,7 +21,7 @@ use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId};
21// if !(x == 4 && y) {} 21// if !(x == 4 && y) {}
22// } 22// }
23// ``` 23// ```
24pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option<Assist> { 24pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let expr = ctx.find_node_at_offset::<ast::BinExpr>()?; 25 let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
26 let op = expr.op_kind()?; 26 let op = expr.op_kind()?;
27 let op_range = expr.op_token()?.text_range(); 27 let op_range = expr.op_token()?.text_range();
@@ -39,8 +39,7 @@ pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option<Assist> {
39 let rhs_range = rhs.syntax().text_range(); 39 let rhs_range = rhs.syntax().text_range();
40 let not_rhs = invert_boolean_expression(rhs); 40 let not_rhs = invert_boolean_expression(rhs);
41 41
42 ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", |edit| { 42 acc.add(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| {
43 edit.target(op_range);
44 edit.replace(op_range, opposite_op); 43 edit.replace(op_range, opposite_op);
45 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); 44 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
46 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); 45 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
@@ -60,26 +59,26 @@ fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> {
60mod tests { 59mod tests {
61 use super::*; 60 use super::*;
62 61
63 use crate::helpers::{check_assist, check_assist_not_applicable}; 62 use crate::tests::{check_assist, check_assist_not_applicable};
64 63
65 #[test] 64 #[test]
66 fn demorgan_turns_and_into_or() { 65 fn demorgan_turns_and_into_or() {
67 check_assist(apply_demorgan, "fn f() { !x &&<|> !x }", "fn f() { !(x ||<|> x) }") 66 check_assist(apply_demorgan, "fn f() { !x &&<|> !x }", "fn f() { !(x || x) }")
68 } 67 }
69 68
70 #[test] 69 #[test]
71 fn demorgan_turns_or_into_and() { 70 fn demorgan_turns_or_into_and() {
72 check_assist(apply_demorgan, "fn f() { !x ||<|> !x }", "fn f() { !(x &&<|> x) }") 71 check_assist(apply_demorgan, "fn f() { !x ||<|> !x }", "fn f() { !(x && x) }")
73 } 72 }
74 73
75 #[test] 74 #[test]
76 fn demorgan_removes_inequality() { 75 fn demorgan_removes_inequality() {
77 check_assist(apply_demorgan, "fn f() { x != x ||<|> !x }", "fn f() { !(x == x &&<|> x) }") 76 check_assist(apply_demorgan, "fn f() { x != x ||<|> !x }", "fn f() { !(x == x && x) }")
78 } 77 }
79 78
80 #[test] 79 #[test]
81 fn demorgan_general_case() { 80 fn demorgan_general_case() {
82 check_assist(apply_demorgan, "fn f() { x ||<|> x }", "fn f() { !(!x &&<|> !x) }") 81 check_assist(apply_demorgan, "fn f() { x ||<|> x }", "fn f() { !(!x && !x) }")
83 } 82 }
84 83
85 #[test] 84 #[test]
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
index 99682e023..edf96d50e 100644
--- a/crates/ra_assists/src/handlers/auto_import.rs
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -1,5 +1,6 @@
1use std::collections::BTreeSet; 1use std::collections::BTreeSet;
2 2
3use either::Either;
3use hir::{ 4use hir::{
4 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, 5 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait,
5 Type, 6 Type,
@@ -12,12 +13,7 @@ use ra_syntax::{
12}; 13};
13use rustc_hash::FxHashSet; 14use rustc_hash::FxHashSet;
14 15
15use crate::{ 16use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, GroupLabel};
16 assist_ctx::{Assist, AssistCtx},
17 utils::insert_use_statement,
18 AssistId,
19};
20use either::Either;
21 17
22// Assist: auto_import 18// Assist: auto_import
23// 19//
@@ -38,25 +34,32 @@ use either::Either;
38// } 34// }
39// # pub mod std { pub mod collections { pub struct HashMap { } } } 35// # pub mod std { pub mod collections { pub struct HashMap { } } }
40// ``` 36// ```
41pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { 37pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
42 let auto_import_assets = AutoImportAssets::new(&ctx)?; 38 let auto_import_assets = AutoImportAssets::new(&ctx)?;
43 let proposed_imports = auto_import_assets.search_for_imports(ctx.db); 39 let proposed_imports = auto_import_assets.search_for_imports(ctx.db);
44 if proposed_imports.is_empty() { 40 if proposed_imports.is_empty() {
45 return None; 41 return None;
46 } 42 }
47 43
48 let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message()); 44 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range;
45 let group = auto_import_assets.get_import_group_message();
49 for import in proposed_imports { 46 for import in proposed_imports {
50 group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { 47 acc.add_group(
51 edit.target(auto_import_assets.syntax_under_caret.text_range()); 48 &group,
52 insert_use_statement( 49 AssistId("auto_import"),
53 &auto_import_assets.syntax_under_caret, 50 format!("Import `{}`", &import),
54 &import, 51 range,
55 edit.text_edit_builder(), 52 |builder| {
56 ); 53 insert_use_statement(
57 }); 54 &auto_import_assets.syntax_under_caret,
58 } 55 &import,
59 group.finish() 56 ctx,
57 builder.text_edit_builder(),
58 );
59 },
60 );
61 }
62 Some(())
60} 63}
61 64
62#[derive(Debug)] 65#[derive(Debug)]
@@ -67,15 +70,15 @@ struct AutoImportAssets {
67} 70}
68 71
69impl AutoImportAssets { 72impl AutoImportAssets {
70 fn new(ctx: &AssistCtx) -> Option<Self> { 73 fn new(ctx: &AssistContext) -> Option<Self> {
71 if let Some(path_under_caret) = ctx.find_node_at_offset::<ast::Path>() { 74 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
72 Self::for_regular_path(path_under_caret, &ctx) 75 Self::for_regular_path(path_under_caret, &ctx)
73 } else { 76 } else {
74 Self::for_method_call(ctx.find_node_at_offset()?, &ctx) 77 Self::for_method_call(ctx.find_node_at_offset_with_descend()?, &ctx)
75 } 78 }
76 } 79 }
77 80
78 fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistCtx) -> Option<Self> { 81 fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistContext) -> Option<Self> {
79 let syntax_under_caret = method_call.syntax().to_owned(); 82 let syntax_under_caret = method_call.syntax().to_owned();
80 let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?; 83 let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?;
81 Some(Self { 84 Some(Self {
@@ -85,7 +88,7 @@ impl AutoImportAssets {
85 }) 88 })
86 } 89 }
87 90
88 fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistCtx) -> Option<Self> { 91 fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> {
89 let syntax_under_caret = path_under_caret.syntax().to_owned(); 92 let syntax_under_caret = path_under_caret.syntax().to_owned();
90 if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() { 93 if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() {
91 return None; 94 return None;
@@ -108,8 +111,8 @@ impl AutoImportAssets {
108 } 111 }
109 } 112 }
110 113
111 fn get_import_group_message(&self) -> String { 114 fn get_import_group_message(&self) -> GroupLabel {
112 match &self.import_candidate { 115 let name = match &self.import_candidate {
113 ImportCandidate::UnqualifiedName(name) => format!("Import {}", name), 116 ImportCandidate::UnqualifiedName(name) => format!("Import {}", name),
114 ImportCandidate::QualifierStart(qualifier_start) => { 117 ImportCandidate::QualifierStart(qualifier_start) => {
115 format!("Import {}", qualifier_start) 118 format!("Import {}", qualifier_start)
@@ -120,7 +123,8 @@ impl AutoImportAssets {
120 ImportCandidate::TraitMethod(_, trait_method_name) => { 123 ImportCandidate::TraitMethod(_, trait_method_name) => {
121 format!("Import a trait for method {}", trait_method_name) 124 format!("Import a trait for method {}", trait_method_name)
122 } 125 }
123 } 126 };
127 GroupLabel(name)
124 } 128 }
125 129
126 fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> { 130 fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> {
@@ -280,7 +284,7 @@ impl ImportCandidate {
280#[cfg(test)] 284#[cfg(test)]
281mod tests { 285mod tests {
282 use super::*; 286 use super::*;
283 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 287 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
284 288
285 #[test] 289 #[test]
286 fn applicable_when_found_an_import() { 290 fn applicable_when_found_an_import() {
@@ -294,7 +298,7 @@ mod tests {
294 } 298 }
295 ", 299 ",
296 r" 300 r"
297 <|>use PubMod::PubStruct; 301 use PubMod::PubStruct;
298 302
299 PubStruct 303 PubStruct
300 304
@@ -306,6 +310,35 @@ mod tests {
306 } 310 }
307 311
308 #[test] 312 #[test]
313 fn applicable_when_found_an_import_in_macros() {
314 check_assist(
315 auto_import,
316 r"
317 macro_rules! foo {
318 ($i:ident) => { fn foo(a: $i) {} }
319 }
320 foo!(Pub<|>Struct);
321
322 pub mod PubMod {
323 pub struct PubStruct;
324 }
325 ",
326 r"
327 use PubMod::PubStruct;
328
329 macro_rules! foo {
330 ($i:ident) => { fn foo(a: $i) {} }
331 }
332 foo!(PubStruct);
333
334 pub mod PubMod {
335 pub struct PubStruct;
336 }
337 ",
338 );
339 }
340
341 #[test]
309 fn auto_imports_are_merged() { 342 fn auto_imports_are_merged() {
310 check_assist( 343 check_assist(
311 auto_import, 344 auto_import,
@@ -327,7 +360,7 @@ mod tests {
327 use PubMod::{PubStruct2, PubStruct1}; 360 use PubMod::{PubStruct2, PubStruct1};
328 361
329 struct Test { 362 struct Test {
330 test: Pub<|>Struct2<u8>, 363 test: PubStruct2<u8>,
331 } 364 }
332 365
333 pub mod PubMod { 366 pub mod PubMod {
@@ -358,9 +391,9 @@ mod tests {
358 } 391 }
359 ", 392 ",
360 r" 393 r"
361 use PubMod1::PubStruct; 394 use PubMod3::PubStruct;
362 395
363 PubSt<|>ruct 396 PubStruct
364 397
365 pub mod PubMod1 { 398 pub mod PubMod1 {
366 pub struct PubStruct; 399 pub struct PubStruct;
@@ -441,7 +474,7 @@ mod tests {
441 r" 474 r"
442 use PubMod::test_function; 475 use PubMod::test_function;
443 476
444 test_function<|> 477 test_function
445 478
446 pub mod PubMod { 479 pub mod PubMod {
447 pub fn test_function() {}; 480 pub fn test_function() {};
@@ -468,7 +501,7 @@ mod tests {
468 r"use crate_with_macro::foo; 501 r"use crate_with_macro::foo;
469 502
470fn main() { 503fn main() {
471 foo<|> 504 foo
472} 505}
473", 506",
474 ); 507 );
@@ -554,7 +587,7 @@ fn main() {
554 } 587 }
555 588
556 fn main() { 589 fn main() {
557 TestStruct::test_function<|> 590 TestStruct::test_function
558 } 591 }
559 ", 592 ",
560 ); 593 );
@@ -587,7 +620,7 @@ fn main() {
587 } 620 }
588 621
589 fn main() { 622 fn main() {
590 TestStruct::TEST_CONST<|> 623 TestStruct::TEST_CONST
591 } 624 }
592 ", 625 ",
593 ); 626 );
@@ -626,7 +659,7 @@ fn main() {
626 } 659 }
627 660
628 fn main() { 661 fn main() {
629 test_mod::TestStruct::test_function<|> 662 test_mod::TestStruct::test_function
630 } 663 }
631 ", 664 ",
632 ); 665 );
@@ -697,7 +730,7 @@ fn main() {
697 } 730 }
698 731
699 fn main() { 732 fn main() {
700 test_mod::TestStruct::TEST_CONST<|> 733 test_mod::TestStruct::TEST_CONST
701 } 734 }
702 ", 735 ",
703 ); 736 );
@@ -770,7 +803,7 @@ fn main() {
770 803
771 fn main() { 804 fn main() {
772 let test_struct = test_mod::TestStruct {}; 805 let test_struct = test_mod::TestStruct {};
773 test_struct.test_meth<|>od() 806 test_struct.test_method()
774 } 807 }
775 ", 808 ",
776 ); 809 );
diff --git a/crates/ra_assists/src/handlers/change_return_type_to_result.rs b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
new file mode 100644
index 000000000..c6baa0a57
--- /dev/null
+++ b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
@@ -0,0 +1,961 @@
1use ra_syntax::{
2 ast::{self, BlockExpr, Expr, LoopBodyOwner},
3 AstNode, SyntaxNode,
4};
5
6use crate::{AssistContext, AssistId, Assists};
7
8// Assist: change_return_type_to_result
9//
10// Change the function's return type to Result.
11//
12// ```
13// fn foo() -> i32<|> { 42i32 }
14// ```
15// ->
16// ```
17// fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
18// ```
19pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
20 let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
21 // FIXME: extend to lambdas as well
22 let fn_def = ret_type.syntax().parent().and_then(ast::FnDef::cast)?;
23
24 let type_ref = &ret_type.type_ref()?;
25 if type_ref.syntax().text().to_string().starts_with("Result<") {
26 return None;
27 }
28
29 let block_expr = &fn_def.body()?;
30
31 acc.add(
32 AssistId("change_return_type_to_result"),
33 "Change return type to Result",
34 type_ref.syntax().text_range(),
35 |builder| {
36 let mut tail_return_expr_collector = TailReturnCollector::new();
37 tail_return_expr_collector.collect_jump_exprs(block_expr, false);
38 tail_return_expr_collector.collect_tail_exprs(block_expr);
39
40 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap {
41 builder.replace_node_and_indent(&ret_expr_arg, format!("Ok({})", ret_expr_arg));
42 }
43
44 match ctx.config.snippet_cap {
45 Some(cap) => {
46 let snippet = format!("Result<{}, ${{0:_}}>", type_ref);
47 builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet)
48 }
49 None => builder
50 .replace(type_ref.syntax().text_range(), format!("Result<{}, _>", type_ref)),
51 }
52 },
53 )
54}
55
56struct TailReturnCollector {
57 exprs_to_wrap: Vec<SyntaxNode>,
58}
59
60impl TailReturnCollector {
61 fn new() -> Self {
62 Self { exprs_to_wrap: vec![] }
63 }
64 /// Collect all`return` expression
65 fn collect_jump_exprs(&mut self, block_expr: &BlockExpr, collect_break: bool) {
66 let statements = block_expr.statements();
67 for stmt in statements {
68 let expr = match &stmt {
69 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
70 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
71 };
72 if let Some(expr) = &expr {
73 self.handle_exprs(expr, collect_break);
74 }
75 }
76
77 // Browse tail expressions for each block
78 if let Some(expr) = block_expr.expr() {
79 if let Some(last_exprs) = get_tail_expr_from_block(&expr) {
80 for last_expr in last_exprs {
81 let last_expr = match last_expr {
82 NodeType::Node(expr) | NodeType::Leaf(expr) => expr,
83 };
84
85 if let Some(last_expr) = Expr::cast(last_expr.clone()) {
86 self.handle_exprs(&last_expr, collect_break);
87 } else if let Some(expr_stmt) = ast::Stmt::cast(last_expr) {
88 let expr_stmt = match &expr_stmt {
89 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
90 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
91 };
92 if let Some(expr) = &expr_stmt {
93 self.handle_exprs(expr, collect_break);
94 }
95 }
96 }
97 }
98 }
99 }
100
101 fn handle_exprs(&mut self, expr: &Expr, collect_break: bool) {
102 match expr {
103 Expr::BlockExpr(block_expr) => {
104 self.collect_jump_exprs(&block_expr, collect_break);
105 }
106 Expr::ReturnExpr(ret_expr) => {
107 if let Some(ret_expr_arg) = &ret_expr.expr() {
108 self.exprs_to_wrap.push(ret_expr_arg.syntax().clone());
109 }
110 }
111 Expr::BreakExpr(break_expr) if collect_break => {
112 if let Some(break_expr_arg) = &break_expr.expr() {
113 self.exprs_to_wrap.push(break_expr_arg.syntax().clone());
114 }
115 }
116 Expr::IfExpr(if_expr) => {
117 for block in if_expr.blocks() {
118 self.collect_jump_exprs(&block, collect_break);
119 }
120 }
121 Expr::LoopExpr(loop_expr) => {
122 if let Some(block_expr) = loop_expr.loop_body() {
123 self.collect_jump_exprs(&block_expr, collect_break);
124 }
125 }
126 Expr::ForExpr(for_expr) => {
127 if let Some(block_expr) = for_expr.loop_body() {
128 self.collect_jump_exprs(&block_expr, collect_break);
129 }
130 }
131 Expr::WhileExpr(while_expr) => {
132 if let Some(block_expr) = while_expr.loop_body() {
133 self.collect_jump_exprs(&block_expr, collect_break);
134 }
135 }
136 Expr::MatchExpr(match_expr) => {
137 if let Some(arm_list) = match_expr.match_arm_list() {
138 arm_list.arms().filter_map(|match_arm| match_arm.expr()).for_each(|expr| {
139 self.handle_exprs(&expr, collect_break);
140 });
141 }
142 }
143 _ => {}
144 }
145 }
146
147 fn collect_tail_exprs(&mut self, block: &BlockExpr) {
148 if let Some(expr) = block.expr() {
149 self.handle_exprs(&expr, true);
150 self.fetch_tail_exprs(&expr);
151 }
152 }
153
154 fn fetch_tail_exprs(&mut self, expr: &Expr) {
155 if let Some(exprs) = get_tail_expr_from_block(expr) {
156 for node_type in &exprs {
157 match node_type {
158 NodeType::Leaf(expr) => {
159 self.exprs_to_wrap.push(expr.clone());
160 }
161 NodeType::Node(expr) => match &Expr::cast(expr.clone()) {
162 Some(last_expr) => {
163 self.fetch_tail_exprs(last_expr);
164 }
165 None => {
166 self.exprs_to_wrap.push(expr.clone());
167 }
168 },
169 }
170 }
171 }
172 }
173}
174
175#[derive(Debug)]
176enum NodeType {
177 Leaf(SyntaxNode),
178 Node(SyntaxNode),
179}
180
181/// Get a tail expression inside a block
182fn get_tail_expr_from_block(expr: &Expr) -> Option<Vec<NodeType>> {
183 match expr {
184 Expr::IfExpr(if_expr) => {
185 let mut nodes = vec![];
186 for block in if_expr.blocks() {
187 if let Some(block_expr) = block.expr() {
188 if let Some(tail_exprs) = get_tail_expr_from_block(&block_expr) {
189 nodes.extend(tail_exprs);
190 }
191 } else if let Some(last_expr) = block.syntax().last_child() {
192 nodes.push(NodeType::Node(last_expr));
193 } else {
194 nodes.push(NodeType::Node(block.syntax().clone()));
195 }
196 }
197 Some(nodes)
198 }
199 Expr::LoopExpr(loop_expr) => {
200 loop_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
201 }
202 Expr::ForExpr(for_expr) => {
203 for_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
204 }
205 Expr::WhileExpr(while_expr) => {
206 while_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
207 }
208 Expr::BlockExpr(block_expr) => {
209 block_expr.expr().map(|lc| vec![NodeType::Node(lc.syntax().clone())])
210 }
211 Expr::MatchExpr(match_expr) => {
212 let arm_list = match_expr.match_arm_list()?;
213 let arms: Vec<NodeType> = arm_list
214 .arms()
215 .filter_map(|match_arm| match_arm.expr())
216 .map(|expr| match expr {
217 Expr::ReturnExpr(ret_expr) => NodeType::Node(ret_expr.syntax().clone()),
218 Expr::BreakExpr(break_expr) => NodeType::Node(break_expr.syntax().clone()),
219 _ => match expr.syntax().last_child() {
220 Some(last_expr) => NodeType::Node(last_expr),
221 None => NodeType::Node(expr.syntax().clone()),
222 },
223 })
224 .collect();
225
226 Some(arms)
227 }
228 Expr::BreakExpr(expr) => expr.expr().map(|e| vec![NodeType::Leaf(e.syntax().clone())]),
229 Expr::ReturnExpr(ret_expr) => Some(vec![NodeType::Node(ret_expr.syntax().clone())]),
230 Expr::CallExpr(call_expr) => Some(vec![NodeType::Leaf(call_expr.syntax().clone())]),
231 Expr::Literal(lit_expr) => Some(vec![NodeType::Leaf(lit_expr.syntax().clone())]),
232 Expr::TupleExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
233 Expr::ArrayExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
234 Expr::ParenExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
235 Expr::PathExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
236 Expr::Label(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
237 Expr::RecordLit(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
238 Expr::IndexExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
239 Expr::MethodCallExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
240 Expr::AwaitExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
241 Expr::CastExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
242 Expr::RefExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
243 Expr::PrefixExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
244 Expr::RangeExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
245 Expr::BinExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
246 Expr::MacroCall(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
247 Expr::BoxExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
248 _ => None,
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use crate::tests::{check_assist, check_assist_not_applicable};
255
256 use super::*;
257
258 #[test]
259 fn change_return_type_to_result_simple() {
260 check_assist(
261 change_return_type_to_result,
262 r#"fn foo() -> i3<|>2 {
263 let test = "test";
264 return 42i32;
265 }"#,
266 r#"fn foo() -> Result<i32, ${0:_}> {
267 let test = "test";
268 return Ok(42i32);
269 }"#,
270 );
271 }
272
273 #[test]
274 fn change_return_type_to_result_simple_return_type() {
275 check_assist(
276 change_return_type_to_result,
277 r#"fn foo() -> i32<|> {
278 let test = "test";
279 return 42i32;
280 }"#,
281 r#"fn foo() -> Result<i32, ${0:_}> {
282 let test = "test";
283 return Ok(42i32);
284 }"#,
285 );
286 }
287
288 #[test]
289 fn change_return_type_to_result_simple_return_type_bad_cursor() {
290 check_assist_not_applicable(
291 change_return_type_to_result,
292 r#"fn foo() -> i32 {
293 let test = "test";<|>
294 return 42i32;
295 }"#,
296 );
297 }
298
299 #[test]
300 fn change_return_type_to_result_simple_with_cursor() {
301 check_assist(
302 change_return_type_to_result,
303 r#"fn foo() -> <|>i32 {
304 let test = "test";
305 return 42i32;
306 }"#,
307 r#"fn foo() -> Result<i32, ${0:_}> {
308 let test = "test";
309 return Ok(42i32);
310 }"#,
311 );
312 }
313
314 #[test]
315 fn change_return_type_to_result_simple_with_tail() {
316 check_assist(
317 change_return_type_to_result,
318 r#"fn foo() -><|> i32 {
319 let test = "test";
320 42i32
321 }"#,
322 r#"fn foo() -> Result<i32, ${0:_}> {
323 let test = "test";
324 Ok(42i32)
325 }"#,
326 );
327 }
328
329 #[test]
330 fn change_return_type_to_result_simple_with_tail_only() {
331 check_assist(
332 change_return_type_to_result,
333 r#"fn foo() -> i32<|> {
334 42i32
335 }"#,
336 r#"fn foo() -> Result<i32, ${0:_}> {
337 Ok(42i32)
338 }"#,
339 );
340 }
341 #[test]
342 fn change_return_type_to_result_simple_with_tail_block_like() {
343 check_assist(
344 change_return_type_to_result,
345 r#"fn foo() -> i32<|> {
346 if true {
347 42i32
348 } else {
349 24i32
350 }
351 }"#,
352 r#"fn foo() -> Result<i32, ${0:_}> {
353 if true {
354 Ok(42i32)
355 } else {
356 Ok(24i32)
357 }
358 }"#,
359 );
360 }
361
362 #[test]
363 fn change_return_type_to_result_simple_with_nested_if() {
364 check_assist(
365 change_return_type_to_result,
366 r#"fn foo() -> i32<|> {
367 if true {
368 if false {
369 1
370 } else {
371 2
372 }
373 } else {
374 24i32
375 }
376 }"#,
377 r#"fn foo() -> Result<i32, ${0:_}> {
378 if true {
379 if false {
380 Ok(1)
381 } else {
382 Ok(2)
383 }
384 } else {
385 Ok(24i32)
386 }
387 }"#,
388 );
389 }
390
391 #[test]
392 fn change_return_type_to_result_simple_with_await() {
393 check_assist(
394 change_return_type_to_result,
395 r#"async fn foo() -> i<|>32 {
396 if true {
397 if false {
398 1.await
399 } else {
400 2.await
401 }
402 } else {
403 24i32.await
404 }
405 }"#,
406 r#"async fn foo() -> Result<i32, ${0:_}> {
407 if true {
408 if false {
409 Ok(1.await)
410 } else {
411 Ok(2.await)
412 }
413 } else {
414 Ok(24i32.await)
415 }
416 }"#,
417 );
418 }
419
420 #[test]
421 fn change_return_type_to_result_simple_with_array() {
422 check_assist(
423 change_return_type_to_result,
424 r#"fn foo() -> [i32;<|> 3] {
425 [1, 2, 3]
426 }"#,
427 r#"fn foo() -> Result<[i32; 3], ${0:_}> {
428 Ok([1, 2, 3])
429 }"#,
430 );
431 }
432
433 #[test]
434 fn change_return_type_to_result_simple_with_cast() {
435 check_assist(
436 change_return_type_to_result,
437 r#"fn foo() -<|>> i32 {
438 if true {
439 if false {
440 1 as i32
441 } else {
442 2 as i32
443 }
444 } else {
445 24 as i32
446 }
447 }"#,
448 r#"fn foo() -> Result<i32, ${0:_}> {
449 if true {
450 if false {
451 Ok(1 as i32)
452 } else {
453 Ok(2 as i32)
454 }
455 } else {
456 Ok(24 as i32)
457 }
458 }"#,
459 );
460 }
461
462 #[test]
463 fn change_return_type_to_result_simple_with_tail_block_like_match() {
464 check_assist(
465 change_return_type_to_result,
466 r#"fn foo() -> i32<|> {
467 let my_var = 5;
468 match my_var {
469 5 => 42i32,
470 _ => 24i32,
471 }
472 }"#,
473 r#"fn foo() -> Result<i32, ${0:_}> {
474 let my_var = 5;
475 match my_var {
476 5 => Ok(42i32),
477 _ => Ok(24i32),
478 }
479 }"#,
480 );
481 }
482
483 #[test]
484 fn change_return_type_to_result_simple_with_loop_with_tail() {
485 check_assist(
486 change_return_type_to_result,
487 r#"fn foo() -> i32<|> {
488 let my_var = 5;
489 loop {
490 println!("test");
491 5
492 }
493
494 my_var
495 }"#,
496 r#"fn foo() -> Result<i32, ${0:_}> {
497 let my_var = 5;
498 loop {
499 println!("test");
500 5
501 }
502
503 Ok(my_var)
504 }"#,
505 );
506 }
507
508 #[test]
509 fn change_return_type_to_result_simple_with_loop_in_let_stmt() {
510 check_assist(
511 change_return_type_to_result,
512 r#"fn foo() -> i32<|> {
513 let my_var = let x = loop {
514 break 1;
515 };
516
517 my_var
518 }"#,
519 r#"fn foo() -> Result<i32, ${0:_}> {
520 let my_var = let x = loop {
521 break 1;
522 };
523
524 Ok(my_var)
525 }"#,
526 );
527 }
528
529 #[test]
530 fn change_return_type_to_result_simple_with_tail_block_like_match_return_expr() {
531 check_assist(
532 change_return_type_to_result,
533 r#"fn foo() -> i32<|> {
534 let my_var = 5;
535 let res = match my_var {
536 5 => 42i32,
537 _ => return 24i32,
538 };
539
540 res
541 }"#,
542 r#"fn foo() -> Result<i32, ${0:_}> {
543 let my_var = 5;
544 let res = match my_var {
545 5 => 42i32,
546 _ => return Ok(24i32),
547 };
548
549 Ok(res)
550 }"#,
551 );
552
553 check_assist(
554 change_return_type_to_result,
555 r#"fn foo() -> i32<|> {
556 let my_var = 5;
557 let res = if my_var == 5 {
558 42i32
559 } else {
560 return 24i32;
561 };
562
563 res
564 }"#,
565 r#"fn foo() -> Result<i32, ${0:_}> {
566 let my_var = 5;
567 let res = if my_var == 5 {
568 42i32
569 } else {
570 return Ok(24i32);
571 };
572
573 Ok(res)
574 }"#,
575 );
576 }
577
578 #[test]
579 fn change_return_type_to_result_simple_with_tail_block_like_match_deeper() {
580 check_assist(
581 change_return_type_to_result,
582 r#"fn foo() -> i32<|> {
583 let my_var = 5;
584 match my_var {
585 5 => {
586 if true {
587 42i32
588 } else {
589 25i32
590 }
591 },
592 _ => {
593 let test = "test";
594 if test == "test" {
595 return bar();
596 }
597 53i32
598 },
599 }
600 }"#,
601 r#"fn foo() -> Result<i32, ${0:_}> {
602 let my_var = 5;
603 match my_var {
604 5 => {
605 if true {
606 Ok(42i32)
607 } else {
608 Ok(25i32)
609 }
610 },
611 _ => {
612 let test = "test";
613 if test == "test" {
614 return Ok(bar());
615 }
616 Ok(53i32)
617 },
618 }
619 }"#,
620 );
621 }
622
623 #[test]
624 fn change_return_type_to_result_simple_with_tail_block_like_early_return() {
625 check_assist(
626 change_return_type_to_result,
627 r#"fn foo() -> i<|>32 {
628 let test = "test";
629 if test == "test" {
630 return 24i32;
631 }
632 53i32
633 }"#,
634 r#"fn foo() -> Result<i32, ${0:_}> {
635 let test = "test";
636 if test == "test" {
637 return Ok(24i32);
638 }
639 Ok(53i32)
640 }"#,
641 );
642 }
643
644 #[test]
645 fn change_return_type_to_result_simple_with_closure() {
646 check_assist(
647 change_return_type_to_result,
648 r#"fn foo(the_field: u32) -><|> u32 {
649 let true_closure = || {
650 return true;
651 };
652 if the_field < 5 {
653 let mut i = 0;
654
655
656 if true_closure() {
657 return 99;
658 } else {
659 return 0;
660 }
661 }
662
663 the_field
664 }"#,
665 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
666 let true_closure = || {
667 return true;
668 };
669 if the_field < 5 {
670 let mut i = 0;
671
672
673 if true_closure() {
674 return Ok(99);
675 } else {
676 return Ok(0);
677 }
678 }
679
680 Ok(the_field)
681 }"#,
682 );
683
684 check_assist(
685 change_return_type_to_result,
686 r#"fn foo(the_field: u32) -> u32<|> {
687 let true_closure = || {
688 return true;
689 };
690 if the_field < 5 {
691 let mut i = 0;
692
693
694 if true_closure() {
695 return 99;
696 } else {
697 return 0;
698 }
699 }
700 let t = None;
701
702 t.unwrap_or_else(|| the_field)
703 }"#,
704 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
705 let true_closure = || {
706 return true;
707 };
708 if the_field < 5 {
709 let mut i = 0;
710
711
712 if true_closure() {
713 return Ok(99);
714 } else {
715 return Ok(0);
716 }
717 }
718 let t = None;
719
720 Ok(t.unwrap_or_else(|| the_field))
721 }"#,
722 );
723 }
724
725 #[test]
726 fn change_return_type_to_result_simple_with_weird_forms() {
727 check_assist(
728 change_return_type_to_result,
729 r#"fn foo() -> i32<|> {
730 let test = "test";
731 if test == "test" {
732 return 24i32;
733 }
734 let mut i = 0;
735 loop {
736 if i == 1 {
737 break 55;
738 }
739 i += 1;
740 }
741 }"#,
742 r#"fn foo() -> Result<i32, ${0:_}> {
743 let test = "test";
744 if test == "test" {
745 return Ok(24i32);
746 }
747 let mut i = 0;
748 loop {
749 if i == 1 {
750 break Ok(55);
751 }
752 i += 1;
753 }
754 }"#,
755 );
756
757 check_assist(
758 change_return_type_to_result,
759 r#"fn foo() -> i32<|> {
760 let test = "test";
761 if test == "test" {
762 return 24i32;
763 }
764 let mut i = 0;
765 loop {
766 loop {
767 if i == 1 {
768 break 55;
769 }
770 i += 1;
771 }
772 }
773 }"#,
774 r#"fn foo() -> Result<i32, ${0:_}> {
775 let test = "test";
776 if test == "test" {
777 return Ok(24i32);
778 }
779 let mut i = 0;
780 loop {
781 loop {
782 if i == 1 {
783 break Ok(55);
784 }
785 i += 1;
786 }
787 }
788 }"#,
789 );
790
791 check_assist(
792 change_return_type_to_result,
793 r#"fn foo() -> i3<|>2 {
794 let test = "test";
795 let other = 5;
796 if test == "test" {
797 let res = match other {
798 5 => 43,
799 _ => return 56,
800 };
801 }
802 let mut i = 0;
803 loop {
804 loop {
805 if i == 1 {
806 break 55;
807 }
808 i += 1;
809 }
810 }
811 }"#,
812 r#"fn foo() -> Result<i32, ${0:_}> {
813 let test = "test";
814 let other = 5;
815 if test == "test" {
816 let res = match other {
817 5 => 43,
818 _ => return Ok(56),
819 };
820 }
821 let mut i = 0;
822 loop {
823 loop {
824 if i == 1 {
825 break Ok(55);
826 }
827 i += 1;
828 }
829 }
830 }"#,
831 );
832
833 check_assist(
834 change_return_type_to_result,
835 r#"fn foo(the_field: u32) -> u32<|> {
836 if the_field < 5 {
837 let mut i = 0;
838 loop {
839 if i > 5 {
840 return 55u32;
841 }
842 i += 3;
843 }
844
845 match i {
846 5 => return 99,
847 _ => return 0,
848 };
849 }
850
851 the_field
852 }"#,
853 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
854 if the_field < 5 {
855 let mut i = 0;
856 loop {
857 if i > 5 {
858 return Ok(55u32);
859 }
860 i += 3;
861 }
862
863 match i {
864 5 => return Ok(99),
865 _ => return Ok(0),
866 };
867 }
868
869 Ok(the_field)
870 }"#,
871 );
872
873 check_assist(
874 change_return_type_to_result,
875 r#"fn foo(the_field: u32) -> u3<|>2 {
876 if the_field < 5 {
877 let mut i = 0;
878
879 match i {
880 5 => return 99,
881 _ => return 0,
882 }
883 }
884
885 the_field
886 }"#,
887 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
888 if the_field < 5 {
889 let mut i = 0;
890
891 match i {
892 5 => return Ok(99),
893 _ => return Ok(0),
894 }
895 }
896
897 Ok(the_field)
898 }"#,
899 );
900
901 check_assist(
902 change_return_type_to_result,
903 r#"fn foo(the_field: u32) -> u32<|> {
904 if the_field < 5 {
905 let mut i = 0;
906
907 if i == 5 {
908 return 99
909 } else {
910 return 0
911 }
912 }
913
914 the_field
915 }"#,
916 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
917 if the_field < 5 {
918 let mut i = 0;
919
920 if i == 5 {
921 return Ok(99)
922 } else {
923 return Ok(0)
924 }
925 }
926
927 Ok(the_field)
928 }"#,
929 );
930
931 check_assist(
932 change_return_type_to_result,
933 r#"fn foo(the_field: u32) -> <|>u32 {
934 if the_field < 5 {
935 let mut i = 0;
936
937 if i == 5 {
938 return 99;
939 } else {
940 return 0;
941 }
942 }
943
944 the_field
945 }"#,
946 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
947 if the_field < 5 {
948 let mut i = 0;
949
950 if i == 5 {
951 return Ok(99);
952 } else {
953 return Ok(0);
954 }
955 }
956
957 Ok(the_field)
958 }"#,
959 );
960 }
961}
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs
index 44f6a1dae..c21d75be0 100644
--- a/crates/ra_assists/src/handlers/change_visibility.rs
+++ b/crates/ra_assists/src/handlers/change_visibility.rs
@@ -7,9 +7,9 @@ use ra_syntax::{
7 }, 7 },
8 SyntaxNode, TextSize, T, 8 SyntaxNode, TextSize, T,
9}; 9};
10use test_utils::mark;
10 11
11use crate::{Assist, AssistCtx, AssistId}; 12use crate::{AssistContext, AssistId, Assists};
12use test_utils::tested_by;
13 13
14// Assist: change_visibility 14// Assist: change_visibility
15// 15//
@@ -22,14 +22,14 @@ use test_utils::tested_by;
22// ``` 22// ```
23// pub(crate) fn frobnicate() {} 23// pub(crate) fn frobnicate() {}
24// ``` 24// ```
25pub(crate) fn change_visibility(ctx: AssistCtx) -> Option<Assist> { 25pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() { 26 if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() {
27 return change_vis(ctx, vis); 27 return change_vis(acc, vis);
28 } 28 }
29 add_vis(ctx) 29 add_vis(acc, ctx)
30} 30}
31 31
32fn add_vis(ctx: AssistCtx) -> Option<Assist> { 32fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { 33 let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() {
34 T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, 34 T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true,
35 _ => false, 35 _ => false,
@@ -47,23 +47,27 @@ fn add_vis(ctx: AssistCtx) -> Option<Assist> {
47 return None; 47 return None;
48 } 48 }
49 (vis_offset(&parent), keyword.text_range()) 49 (vis_offset(&parent), keyword.text_range())
50 } else { 50 } else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() {
51 let field_name: ast::Name = ctx.find_node_at_offset()?;
52 let field = field_name.syntax().ancestors().find_map(ast::RecordFieldDef::cast)?; 51 let field = field_name.syntax().ancestors().find_map(ast::RecordFieldDef::cast)?;
53 if field.name()? != field_name { 52 if field.name()? != field_name {
54 tested_by!(change_visibility_field_false_positive); 53 mark::hit!(change_visibility_field_false_positive);
55 return None; 54 return None;
56 } 55 }
57 if field.visibility().is_some() { 56 if field.visibility().is_some() {
58 return None; 57 return None;
59 } 58 }
60 (vis_offset(field.syntax()), field_name.syntax().text_range()) 59 (vis_offset(field.syntax()), field_name.syntax().text_range())
60 } else if let Some(field) = ctx.find_node_at_offset::<ast::TupleFieldDef>() {
61 if field.visibility().is_some() {
62 return None;
63 }
64 (vis_offset(field.syntax()), field.syntax().text_range())
65 } else {
66 return None;
61 }; 67 };
62 68
63 ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub(crate)", |edit| { 69 acc.add(AssistId("change_visibility"), "Change visibility to pub(crate)", target, |edit| {
64 edit.target(target);
65 edit.insert(offset, "pub(crate) "); 70 edit.insert(offset, "pub(crate) ");
66 edit.set_cursor(offset);
67 }) 71 })
68} 72}
69 73
@@ -78,49 +82,49 @@ fn vis_offset(node: &SyntaxNode) -> TextSize {
78 .unwrap_or_else(|| node.text_range().start()) 82 .unwrap_or_else(|| node.text_range().start())
79} 83}
80 84
81fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> { 85fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
82 if vis.syntax().text() == "pub" { 86 if vis.syntax().text() == "pub" {
83 return ctx.add_assist( 87 let target = vis.syntax().text_range();
88 return acc.add(
84 AssistId("change_visibility"), 89 AssistId("change_visibility"),
85 "Change Visibility to pub(crate)", 90 "Change Visibility to pub(crate)",
91 target,
86 |edit| { 92 |edit| {
87 edit.target(vis.syntax().text_range());
88 edit.replace(vis.syntax().text_range(), "pub(crate)"); 93 edit.replace(vis.syntax().text_range(), "pub(crate)");
89 edit.set_cursor(vis.syntax().text_range().start())
90 }, 94 },
91 ); 95 );
92 } 96 }
93 if vis.syntax().text() == "pub(crate)" { 97 if vis.syntax().text() == "pub(crate)" {
94 return ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub", |edit| { 98 let target = vis.syntax().text_range();
95 edit.target(vis.syntax().text_range()); 99 return acc.add(
96 edit.replace(vis.syntax().text_range(), "pub"); 100 AssistId("change_visibility"),
97 edit.set_cursor(vis.syntax().text_range().start()); 101 "Change visibility to pub",
98 }); 102 target,
103 |edit| {
104 edit.replace(vis.syntax().text_range(), "pub");
105 },
106 );
99 } 107 }
100 None 108 None
101} 109}
102 110
103#[cfg(test)] 111#[cfg(test)]
104mod tests { 112mod tests {
105 use test_utils::covers; 113 use test_utils::mark;
106 114
107 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 115 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
108 116
109 use super::*; 117 use super::*;
110 118
111 #[test] 119 #[test]
112 fn change_visibility_adds_pub_crate_to_items() { 120 fn change_visibility_adds_pub_crate_to_items() {
113 check_assist(change_visibility, "<|>fn foo() {}", "<|>pub(crate) fn foo() {}"); 121 check_assist(change_visibility, "<|>fn foo() {}", "pub(crate) fn foo() {}");
114 check_assist(change_visibility, "f<|>n foo() {}", "<|>pub(crate) fn foo() {}"); 122 check_assist(change_visibility, "f<|>n foo() {}", "pub(crate) fn foo() {}");
115 check_assist(change_visibility, "<|>struct Foo {}", "<|>pub(crate) struct Foo {}"); 123 check_assist(change_visibility, "<|>struct Foo {}", "pub(crate) struct Foo {}");
116 check_assist(change_visibility, "<|>mod foo {}", "<|>pub(crate) mod foo {}"); 124 check_assist(change_visibility, "<|>mod foo {}", "pub(crate) mod foo {}");
117 check_assist(change_visibility, "<|>trait Foo {}", "<|>pub(crate) trait Foo {}"); 125 check_assist(change_visibility, "<|>trait Foo {}", "pub(crate) trait Foo {}");
118 check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}"); 126 check_assist(change_visibility, "m<|>od {}", "pub(crate) mod {}");
119 check_assist( 127 check_assist(change_visibility, "unsafe f<|>n foo() {}", "pub(crate) unsafe fn foo() {}");
120 change_visibility,
121 "unsafe f<|>n foo() {}",
122 "<|>pub(crate) unsafe fn foo() {}",
123 );
124 } 128 }
125 129
126 #[test] 130 #[test]
@@ -128,13 +132,14 @@ mod tests {
128 check_assist( 132 check_assist(
129 change_visibility, 133 change_visibility,
130 r"struct S { <|>field: u32 }", 134 r"struct S { <|>field: u32 }",
131 r"struct S { <|>pub(crate) field: u32 }", 135 r"struct S { pub(crate) field: u32 }",
132 ) 136 );
137 check_assist(change_visibility, r"struct S ( <|>u32 )", r"struct S ( pub(crate) u32 )");
133 } 138 }
134 139
135 #[test] 140 #[test]
136 fn change_visibility_field_false_positive() { 141 fn change_visibility_field_false_positive() {
137 covers!(change_visibility_field_false_positive); 142 mark::check!(change_visibility_field_false_positive);
138 check_assist_not_applicable( 143 check_assist_not_applicable(
139 change_visibility, 144 change_visibility,
140 r"struct S { field: [(); { let <|>x = ();}] }", 145 r"struct S { field: [(); { let <|>x = ();}] }",
@@ -143,17 +148,17 @@ mod tests {
143 148
144 #[test] 149 #[test]
145 fn change_visibility_pub_to_pub_crate() { 150 fn change_visibility_pub_to_pub_crate() {
146 check_assist(change_visibility, "<|>pub fn foo() {}", "<|>pub(crate) fn foo() {}") 151 check_assist(change_visibility, "<|>pub fn foo() {}", "pub(crate) fn foo() {}")
147 } 152 }
148 153
149 #[test] 154 #[test]
150 fn change_visibility_pub_crate_to_pub() { 155 fn change_visibility_pub_crate_to_pub() {
151 check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "<|>pub fn foo() {}") 156 check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "pub fn foo() {}")
152 } 157 }
153 158
154 #[test] 159 #[test]
155 fn change_visibility_const() { 160 fn change_visibility_const() {
156 check_assist(change_visibility, "<|>const FOO = 3u8;", "<|>pub(crate) const FOO = 3u8;"); 161 check_assist(change_visibility, "<|>const FOO = 3u8;", "pub(crate) const FOO = 3u8;");
157 } 162 }
158 163
159 #[test] 164 #[test]
@@ -174,12 +179,21 @@ mod tests {
174 // comments 179 // comments
175 180
176 #[derive(Debug)] 181 #[derive(Debug)]
177 <|>pub(crate) struct Foo; 182 pub(crate) struct Foo;
178 ", 183 ",
179 ) 184 )
180 } 185 }
181 186
182 #[test] 187 #[test]
188 fn not_applicable_for_enum_variants() {
189 check_assist_not_applicable(
190 change_visibility,
191 r"mod foo { pub enum Foo {Foo1} }
192 fn main() { foo::Foo::Foo1<|> } ",
193 );
194 }
195
196 #[test]
183 fn change_visibility_target() { 197 fn change_visibility_target() {
184 check_assist_target(change_visibility, "<|>fn foo() {}", "fn"); 198 check_assist_target(change_visibility, "<|>fn foo() {}", "fn");
185 check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)"); 199 check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)");
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs
index ea6c56f8c..4cc75a7ce 100644
--- a/crates/ra_assists/src/handlers/early_return.rs
+++ b/crates/ra_assists/src/handlers/early_return.rs
@@ -2,14 +2,18 @@ use std::{iter::once, ops::RangeInclusive};
2 2
3use ra_syntax::{ 3use ra_syntax::{
4 algo::replace_children, 4 algo::replace_children,
5 ast::{self, edit::IndentLevel, make, Block, Pat::TupleStructPat}, 5 ast::{
6 self,
7 edit::{AstNodeEdit, IndentLevel},
8 make,
9 },
6 AstNode, 10 AstNode,
7 SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE}, 11 SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE},
8 SyntaxNode, 12 SyntaxNode,
9}; 13};
10 14
11use crate::{ 15use crate::{
12 assist_ctx::{Assist, AssistCtx}, 16 assist_context::{AssistContext, Assists},
13 utils::invert_boolean_expression, 17 utils::invert_boolean_expression,
14 AssistId, 18 AssistId,
15}; 19};
@@ -36,7 +40,7 @@ use crate::{
36// bar(); 40// bar();
37// } 41// }
38// ``` 42// ```
39pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> { 43pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; 44 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
41 if if_expr.else_branch().is_some() { 45 if if_expr.else_branch().is_some() {
42 return None; 46 return None;
@@ -47,7 +51,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
47 // Check if there is an IfLet that we can handle. 51 // Check if there is an IfLet that we can handle.
48 let if_let_pat = match cond.pat() { 52 let if_let_pat = match cond.pat() {
49 None => None, // No IfLet, supported. 53 None => None, // No IfLet, supported.
50 Some(TupleStructPat(pat)) if pat.args().count() == 1 => { 54 Some(ast::Pat::TupleStructPat(pat)) if pat.args().count() == 1 => {
51 let path = pat.path()?; 55 let path = pat.path()?;
52 match path.qualifier() { 56 match path.qualifier() {
53 None => { 57 None => {
@@ -61,9 +65,9 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
61 }; 65 };
62 66
63 let cond_expr = cond.expr()?; 67 let cond_expr = cond.expr()?;
64 let then_block = if_expr.then_branch()?.block()?; 68 let then_block = if_expr.then_branch()?;
65 69
66 let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::Block::cast)?; 70 let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?;
67 71
68 if parent_block.expr()? != if_expr.clone().into() { 72 if parent_block.expr()? != if_expr.clone().into() {
69 return None; 73 return None;
@@ -80,7 +84,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
80 return None; 84 return None;
81 } 85 }
82 86
83 let parent_container = parent_block.syntax().parent()?.parent()?; 87 let parent_container = parent_block.syntax().parent()?;
84 88
85 let early_expression: ast::Expr = match parent_container.kind() { 89 let early_expression: ast::Expr = match parent_container.kind() {
86 WHILE_EXPR | LOOP_EXPR => make::expr_continue(), 90 WHILE_EXPR | LOOP_EXPR => make::expr_continue(),
@@ -93,9 +97,9 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
93 } 97 }
94 98
95 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; 99 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
96 let cursor_position = ctx.frange.range.start();
97 100
98 ctx.add_assist(AssistId("convert_to_guarded_return"), "Convert to guarded return", |edit| { 101 let target = if_expr.syntax().text_range();
102 acc.add(AssistId("convert_to_guarded_return"), "Convert to guarded return", target, |edit| {
99 let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); 103 let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
100 let new_block = match if_let_pat { 104 let new_block = match if_let_pat {
101 None => { 105 None => {
@@ -104,8 +108,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
104 let then_branch = 108 let then_branch =
105 make::block_expr(once(make::expr_stmt(early_expression).into()), None); 109 make::block_expr(once(make::expr_stmt(early_expression).into()), None);
106 let cond = invert_boolean_expression(cond_expr); 110 let cond = invert_boolean_expression(cond_expr);
107 let e = make::expr_if(make::condition(cond, None), then_branch); 111 make::expr_if(make::condition(cond, None), then_branch).indent(if_indent_level)
108 if_indent_level.increase_indent(e)
109 }; 112 };
110 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) 113 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
111 } 114 }
@@ -139,21 +142,19 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
139 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), 142 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
140 Some(match_expr), 143 Some(match_expr),
141 ); 144 );
142 let let_stmt = if_indent_level.increase_indent(let_stmt); 145 let let_stmt = let_stmt.indent(if_indent_level);
143 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) 146 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
144 } 147 }
145 }; 148 };
146 edit.target(if_expr.syntax().text_range()); 149 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
147 edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap());
148 edit.set_cursor(cursor_position);
149 150
150 fn replace( 151 fn replace(
151 new_expr: &SyntaxNode, 152 new_expr: &SyntaxNode,
152 then_block: &Block, 153 then_block: &ast::BlockExpr,
153 parent_block: &Block, 154 parent_block: &ast::BlockExpr,
154 if_expr: &ast::IfExpr, 155 if_expr: &ast::IfExpr,
155 ) -> SyntaxNode { 156 ) -> SyntaxNode {
156 let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone()); 157 let then_block_items = then_block.dedent(IndentLevel::from(1));
157 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); 158 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
158 let end_of_then = 159 let end_of_then =
159 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { 160 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
@@ -182,7 +183,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
182 183
183#[cfg(test)] 184#[cfg(test)]
184mod tests { 185mod tests {
185 use crate::helpers::{check_assist, check_assist_not_applicable}; 186 use crate::tests::{check_assist, check_assist_not_applicable};
186 187
187 use super::*; 188 use super::*;
188 189
@@ -204,7 +205,7 @@ mod tests {
204 r#" 205 r#"
205 fn main() { 206 fn main() {
206 bar(); 207 bar();
207 if<|> !true { 208 if !true {
208 return; 209 return;
209 } 210 }
210 foo(); 211 foo();
@@ -234,7 +235,7 @@ mod tests {
234 r#" 235 r#"
235 fn main(n: Option<String>) { 236 fn main(n: Option<String>) {
236 bar(); 237 bar();
237 le<|>t n = match n { 238 let n = match n {
238 Some(it) => it, 239 Some(it) => it,
239 _ => return, 240 _ => return,
240 }; 241 };
@@ -260,7 +261,7 @@ mod tests {
260 "#, 261 "#,
261 r#" 262 r#"
262 fn main() { 263 fn main() {
263 le<|>t x = match Err(92) { 264 let x = match Err(92) {
264 Ok(it) => it, 265 Ok(it) => it,
265 _ => return, 266 _ => return,
266 }; 267 };
@@ -288,7 +289,7 @@ mod tests {
288 r#" 289 r#"
289 fn main(n: Option<String>) { 290 fn main(n: Option<String>) {
290 bar(); 291 bar();
291 le<|>t n = match n { 292 let n = match n {
292 Ok(it) => it, 293 Ok(it) => it,
293 _ => return, 294 _ => return,
294 }; 295 };
@@ -318,7 +319,7 @@ mod tests {
318 r#" 319 r#"
319 fn main() { 320 fn main() {
320 while true { 321 while true {
321 if<|> !true { 322 if !true {
322 continue; 323 continue;
323 } 324 }
324 foo(); 325 foo();
@@ -346,7 +347,7 @@ mod tests {
346 r#" 347 r#"
347 fn main() { 348 fn main() {
348 while true { 349 while true {
349 le<|>t n = match n { 350 let n = match n {
350 Some(it) => it, 351 Some(it) => it,
351 _ => continue, 352 _ => continue,
352 }; 353 };
@@ -375,7 +376,7 @@ mod tests {
375 r#" 376 r#"
376 fn main() { 377 fn main() {
377 loop { 378 loop {
378 if<|> !true { 379 if !true {
379 continue; 380 continue;
380 } 381 }
381 foo(); 382 foo();
@@ -403,7 +404,7 @@ mod tests {
403 r#" 404 r#"
404 fn main() { 405 fn main() {
405 loop { 406 loop {
406 le<|>t n = match n { 407 let n = match n {
407 Some(it) => it, 408 Some(it) => it,
408 _ => continue, 409 _ => continue,
409 }; 410 };
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
index 8d1af9933..cc303285b 100644
--- a/crates/ra_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ra_assists/src/handlers/fill_match_arms.rs
@@ -4,8 +4,12 @@ use hir::{Adt, HasSource, ModuleDef, Semantics};
4use itertools::Itertools; 4use itertools::Itertools;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; 6use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
7use test_utils::mark;
7 8
8use crate::{Assist, AssistCtx, AssistId}; 9use crate::{
10 utils::{render_snippet, Cursor, FamousDefs},
11 AssistContext, AssistId, Assists,
12};
9 13
10// Assist: fill_match_arms 14// Assist: fill_match_arms
11// 15//
@@ -26,12 +30,12 @@ use crate::{Assist, AssistCtx, AssistId};
26// 30//
27// fn handle(action: Action) { 31// fn handle(action: Action) {
28// match action { 32// match action {
29// Action::Move { distance } => {} 33// $0Action::Move { distance } => {}
30// Action::Stop => {} 34// Action::Stop => {}
31// } 35// }
32// } 36// }
33// ``` 37// ```
34pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { 38pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
35 let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?; 39 let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
36 let match_arm_list = match_expr.match_arm_list()?; 40 let match_arm_list = match_expr.match_arm_list()?;
37 41
@@ -49,12 +53,18 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
49 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { 53 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) {
50 let variants = enum_def.variants(ctx.db); 54 let variants = enum_def.variants(ctx.db);
51 55
52 variants 56 let mut variants = variants
53 .into_iter() 57 .into_iter()
54 .filter_map(|variant| build_pat(ctx.db, module, variant)) 58 .filter_map(|variant| build_pat(ctx.db, module, variant))
55 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) 59 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
56 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) 60 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
57 .collect() 61 .collect::<Vec<_>>();
62 if Some(enum_def) == FamousDefs(&ctx.sema, module.krate()).core_option_Option() {
63 // Match `Some` variant first.
64 mark::hit!(option_order);
65 variants.reverse()
66 }
67 variants
58 } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) { 68 } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
59 // Partial fill not currently supported for tuple of enums. 69 // Partial fill not currently supported for tuple of enums.
60 if !arms.is_empty() { 70 if !arms.is_empty() {
@@ -92,12 +102,24 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
92 return None; 102 return None;
93 } 103 }
94 104
95 ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| { 105 let target = match_expr.syntax().text_range();
96 let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms); 106 acc.add(AssistId("fill_match_arms"), "Fill match arms", target, |builder| {
97 107 let new_arm_list = match_arm_list.remove_placeholder();
98 edit.target(match_expr.syntax().text_range()); 108 let n_old_arms = new_arm_list.arms().count();
99 edit.set_cursor(expr.syntax().text_range().start()); 109 let new_arm_list = new_arm_list.append_arms(missing_arms);
100 edit.replace_ast(match_arm_list, new_arm_list); 110 let first_new_arm = new_arm_list.arms().nth(n_old_arms);
111 let old_range = match_arm_list.syntax().text_range();
112 match (first_new_arm, ctx.config.snippet_cap) {
113 (Some(first_new_arm), Some(cap)) => {
114 let snippet = render_snippet(
115 cap,
116 new_arm_list.syntax(),
117 Cursor::Before(first_new_arm.syntax()),
118 );
119 builder.replace_snippet(cap, old_range, snippet);
120 }
121 _ => builder.replace(old_range, new_arm_list.to_string()),
122 }
101 }) 123 })
102} 124}
103 125
@@ -168,7 +190,12 @@ fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> O
168 190
169#[cfg(test)] 191#[cfg(test)]
170mod tests { 192mod tests {
171 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 193 use test_utils::mark;
194
195 use crate::{
196 tests::{check_assist, check_assist_not_applicable, check_assist_target},
197 utils::FamousDefs,
198 };
172 199
173 use super::fill_match_arms; 200 use super::fill_match_arms;
174 201
@@ -215,12 +242,12 @@ mod tests {
215 r#" 242 r#"
216 enum A { 243 enum A {
217 As, 244 As,
218 Bs{x:i32, y:Option<i32>}, 245 Bs { x: i32, y: Option<i32> },
219 Cs(i32, Option<i32>), 246 Cs(i32, Option<i32>),
220 } 247 }
221 fn main() { 248 fn main() {
222 match A::As<|> { 249 match A::As<|> {
223 A::Bs{x,y:Some(_)} => {} 250 A::Bs { x, y: Some(_) } => {}
224 A::Cs(_, Some(_)) => {} 251 A::Cs(_, Some(_)) => {}
225 } 252 }
226 } 253 }
@@ -228,14 +255,14 @@ mod tests {
228 r#" 255 r#"
229 enum A { 256 enum A {
230 As, 257 As,
231 Bs{x:i32, y:Option<i32>}, 258 Bs { x: i32, y: Option<i32> },
232 Cs(i32, Option<i32>), 259 Cs(i32, Option<i32>),
233 } 260 }
234 fn main() { 261 fn main() {
235 match <|>A::As { 262 match A::As {
236 A::Bs{x,y:Some(_)} => {} 263 A::Bs { x, y: Some(_) } => {}
237 A::Cs(_, Some(_)) => {} 264 A::Cs(_, Some(_)) => {}
238 A::As => {} 265 $0A::As => {}
239 } 266 }
240 } 267 }
241 "#, 268 "#,
@@ -265,9 +292,9 @@ mod tests {
265 Cs(Option<i32>), 292 Cs(Option<i32>),
266 } 293 }
267 fn main() { 294 fn main() {
268 match <|>A::As { 295 match A::As {
269 A::Cs(_) | A::Bs => {} 296 A::Cs(_) | A::Bs => {}
270 A::As => {} 297 $0A::As => {}
271 } 298 }
272 } 299 }
273 "#, 300 "#,
@@ -311,11 +338,11 @@ mod tests {
311 Ys, 338 Ys,
312 } 339 }
313 fn main() { 340 fn main() {
314 match <|>A::As { 341 match A::As {
315 A::Bs if 0 < 1 => {} 342 A::Bs if 0 < 1 => {}
316 A::Ds(_value) => { let x = 1; } 343 A::Ds(_value) => { let x = 1; }
317 A::Es(B::Xs) => (), 344 A::Es(B::Xs) => (),
318 A::As => {} 345 $0A::As => {}
319 A::Cs => {} 346 A::Cs => {}
320 } 347 }
321 } 348 }
@@ -333,7 +360,7 @@ mod tests {
333 Bs, 360 Bs,
334 Cs(String), 361 Cs(String),
335 Ds(String, String), 362 Ds(String, String),
336 Es{ x: usize, y: usize } 363 Es { x: usize, y: usize }
337 } 364 }
338 365
339 fn main() { 366 fn main() {
@@ -347,13 +374,13 @@ mod tests {
347 Bs, 374 Bs,
348 Cs(String), 375 Cs(String),
349 Ds(String, String), 376 Ds(String, String),
350 Es{ x: usize, y: usize } 377 Es { x: usize, y: usize }
351 } 378 }
352 379
353 fn main() { 380 fn main() {
354 let a = A::As; 381 let a = A::As;
355 match <|>a { 382 match a {
356 A::As => {} 383 $0A::As => {}
357 A::Bs => {} 384 A::Bs => {}
358 A::Cs(_) => {} 385 A::Cs(_) => {}
359 A::Ds(_, _) => {} 386 A::Ds(_, _) => {}
@@ -369,14 +396,8 @@ mod tests {
369 check_assist( 396 check_assist(
370 fill_match_arms, 397 fill_match_arms,
371 r#" 398 r#"
372 enum A { 399 enum A { One, Two }
373 One, 400 enum B { One, Two }
374 Two,
375 }
376 enum B {
377 One,
378 Two,
379 }
380 401
381 fn main() { 402 fn main() {
382 let a = A::One; 403 let a = A::One;
@@ -385,20 +406,14 @@ mod tests {
385 } 406 }
386 "#, 407 "#,
387 r#" 408 r#"
388 enum A { 409 enum A { One, Two }
389 One, 410 enum B { One, Two }
390 Two,
391 }
392 enum B {
393 One,
394 Two,
395 }
396 411
397 fn main() { 412 fn main() {
398 let a = A::One; 413 let a = A::One;
399 let b = B::One; 414 let b = B::One;
400 match <|>(a, b) { 415 match (a, b) {
401 (A::One, B::One) => {} 416 $0(A::One, B::One) => {}
402 (A::One, B::Two) => {} 417 (A::One, B::Two) => {}
403 (A::Two, B::One) => {} 418 (A::Two, B::One) => {}
404 (A::Two, B::Two) => {} 419 (A::Two, B::Two) => {}
@@ -413,14 +428,8 @@ mod tests {
413 check_assist( 428 check_assist(
414 fill_match_arms, 429 fill_match_arms,
415 r#" 430 r#"
416 enum A { 431 enum A { One, Two }
417 One, 432 enum B { One, Two }
418 Two,
419 }
420 enum B {
421 One,
422 Two,
423 }
424 433
425 fn main() { 434 fn main() {
426 let a = A::One; 435 let a = A::One;
@@ -429,20 +438,14 @@ mod tests {
429 } 438 }
430 "#, 439 "#,
431 r#" 440 r#"
432 enum A { 441 enum A { One, Two }
433 One, 442 enum B { One, Two }
434 Two,
435 }
436 enum B {
437 One,
438 Two,
439 }
440 443
441 fn main() { 444 fn main() {
442 let a = A::One; 445 let a = A::One;
443 let b = B::One; 446 let b = B::One;
444 match <|>(&a, &b) { 447 match (&a, &b) {
445 (A::One, B::One) => {} 448 $0(A::One, B::One) => {}
446 (A::One, B::Two) => {} 449 (A::One, B::Two) => {}
447 (A::Two, B::One) => {} 450 (A::Two, B::One) => {}
448 (A::Two, B::Two) => {} 451 (A::Two, B::Two) => {}
@@ -457,14 +460,8 @@ mod tests {
457 check_assist_not_applicable( 460 check_assist_not_applicable(
458 fill_match_arms, 461 fill_match_arms,
459 r#" 462 r#"
460 enum A { 463 enum A { One, Two }
461 One, 464 enum B { One, Two }
462 Two,
463 }
464 enum B {
465 One,
466 Two,
467 }
468 465
469 fn main() { 466 fn main() {
470 let a = A::One; 467 let a = A::One;
@@ -482,14 +479,8 @@ mod tests {
482 check_assist_not_applicable( 479 check_assist_not_applicable(
483 fill_match_arms, 480 fill_match_arms,
484 r#" 481 r#"
485 enum A { 482 enum A { One, Two }
486 One, 483 enum B { One, Two }
487 Two,
488 }
489 enum B {
490 One,
491 Two,
492 }
493 484
494 fn main() { 485 fn main() {
495 let a = A::One; 486 let a = A::One;
@@ -513,10 +504,7 @@ mod tests {
513 check_assist_not_applicable( 504 check_assist_not_applicable(
514 fill_match_arms, 505 fill_match_arms,
515 r#" 506 r#"
516 enum A { 507 enum A { One, Two }
517 One,
518 Two,
519 }
520 508
521 fn main() { 509 fn main() {
522 let a = A::One; 510 let a = A::One;
@@ -532,9 +520,7 @@ mod tests {
532 check_assist( 520 check_assist(
533 fill_match_arms, 521 fill_match_arms,
534 r#" 522 r#"
535 enum A { 523 enum A { As }
536 As,
537 }
538 524
539 fn foo(a: &A) { 525 fn foo(a: &A) {
540 match a<|> { 526 match a<|> {
@@ -542,13 +528,11 @@ mod tests {
542 } 528 }
543 "#, 529 "#,
544 r#" 530 r#"
545 enum A { 531 enum A { As }
546 As,
547 }
548 532
549 fn foo(a: &A) { 533 fn foo(a: &A) {
550 match <|>a { 534 match a {
551 A::As => {} 535 $0A::As => {}
552 } 536 }
553 } 537 }
554 "#, 538 "#,
@@ -558,7 +542,7 @@ mod tests {
558 fill_match_arms, 542 fill_match_arms,
559 r#" 543 r#"
560 enum A { 544 enum A {
561 Es{ x: usize, y: usize } 545 Es { x: usize, y: usize }
562 } 546 }
563 547
564 fn foo(a: &mut A) { 548 fn foo(a: &mut A) {
@@ -568,12 +552,12 @@ mod tests {
568 "#, 552 "#,
569 r#" 553 r#"
570 enum A { 554 enum A {
571 Es{ x: usize, y: usize } 555 Es { x: usize, y: usize }
572 } 556 }
573 557
574 fn foo(a: &mut A) { 558 fn foo(a: &mut A) {
575 match <|>a { 559 match a {
576 A::Es { x, y } => {} 560 $0A::Es { x, y } => {}
577 } 561 }
578 } 562 }
579 "#, 563 "#,
@@ -612,8 +596,8 @@ mod tests {
612 enum E { X, Y } 596 enum E { X, Y }
613 597
614 fn main() { 598 fn main() {
615 match <|>E::X { 599 match E::X {
616 E::X => {} 600 $0E::X => {}
617 E::Y => {} 601 E::Y => {}
618 } 602 }
619 } 603 }
@@ -640,8 +624,8 @@ mod tests {
640 use foo::E::X; 624 use foo::E::X;
641 625
642 fn main() { 626 fn main() {
643 match <|>X { 627 match X {
644 X => {} 628 $0X => {}
645 foo::E::Y => {} 629 foo::E::Y => {}
646 } 630 }
647 } 631 }
@@ -654,10 +638,7 @@ mod tests {
654 check_assist( 638 check_assist(
655 fill_match_arms, 639 fill_match_arms,
656 r#" 640 r#"
657 enum A { 641 enum A { One, Two }
658 One,
659 Two,
660 }
661 fn foo(a: A) { 642 fn foo(a: A) {
662 match a { 643 match a {
663 // foo bar baz<|> 644 // foo bar baz<|>
@@ -667,16 +648,13 @@ mod tests {
667 } 648 }
668 "#, 649 "#,
669 r#" 650 r#"
670 enum A { 651 enum A { One, Two }
671 One,
672 Two,
673 }
674 fn foo(a: A) { 652 fn foo(a: A) {
675 match <|>a { 653 match a {
676 // foo bar baz 654 // foo bar baz
677 A::One => {} 655 A::One => {}
678 // This is where the rest should be 656 // This is where the rest should be
679 A::Two => {} 657 $0A::Two => {}
680 } 658 }
681 } 659 }
682 "#, 660 "#,
@@ -688,10 +666,7 @@ mod tests {
688 check_assist( 666 check_assist(
689 fill_match_arms, 667 fill_match_arms,
690 r#" 668 r#"
691 enum A { 669 enum A { One, Two }
692 One,
693 Two,
694 }
695 fn foo(a: A) { 670 fn foo(a: A) {
696 match a { 671 match a {
697 // foo bar baz<|> 672 // foo bar baz<|>
@@ -699,14 +674,11 @@ mod tests {
699 } 674 }
700 "#, 675 "#,
701 r#" 676 r#"
702 enum A { 677 enum A { One, Two }
703 One,
704 Two,
705 }
706 fn foo(a: A) { 678 fn foo(a: A) {
707 match <|>a { 679 match a {
708 // foo bar baz 680 // foo bar baz
709 A::One => {} 681 $0A::One => {}
710 A::Two => {} 682 A::Two => {}
711 } 683 }
712 } 684 }
@@ -729,12 +701,37 @@ mod tests {
729 r#" 701 r#"
730 enum A { One, Two, } 702 enum A { One, Two, }
731 fn foo(a: A) { 703 fn foo(a: A) {
732 match <|>a { 704 match a {
733 A::One => {} 705 $0A::One => {}
734 A::Two => {} 706 A::Two => {}
735 } 707 }
736 } 708 }
737 "#, 709 "#,
738 ); 710 );
739 } 711 }
712
713 #[test]
714 fn option_order() {
715 mark::check!(option_order);
716 let before = r#"
717fn foo(opt: Option<i32>) {
718 match opt<|> {
719 }
720}"#;
721 let before =
722 &format!("//- main.rs crate:main deps:core\n{}{}", before, FamousDefs::FIXTURE);
723
724 check_assist(
725 fill_match_arms,
726 before,
727 r#"
728fn foo(opt: Option<i32>) {
729 match opt {
730 $0Some(_) => {}
731 None => {}
732 }
733}
734"#,
735 );
736 }
740} 737}
diff --git a/crates/ra_assists/src/handlers/fix_visibility.rs b/crates/ra_assists/src/handlers/fix_visibility.rs
new file mode 100644
index 000000000..9ec42f568
--- /dev/null
+++ b/crates/ra_assists/src/handlers/fix_visibility.rs
@@ -0,0 +1,559 @@
1use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
2use ra_db::FileId;
3use ra_syntax::{
4 ast, AstNode,
5 SyntaxKind::{ATTR, COMMENT, WHITESPACE},
6 SyntaxNode, TextRange, TextSize,
7};
8
9use crate::{AssistContext, AssistId, Assists};
10
11// FIXME: this really should be a fix for diagnostic, rather than an assist.
12
13// Assist: fix_visibility
14//
15// Makes inaccessible item public.
16//
17// ```
18// mod m {
19// fn frobnicate() {}
20// }
21// fn main() {
22// m::frobnicate<|>() {}
23// }
24// ```
25// ->
26// ```
27// mod m {
28// $0pub(crate) fn frobnicate() {}
29// }
30// fn main() {
31// m::frobnicate() {}
32// }
33// ```
34pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
35 add_vis_to_referenced_module_def(acc, ctx)
36 .or_else(|| add_vis_to_referenced_record_field(acc, ctx))
37}
38
39fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let path: ast::Path = ctx.find_node_at_offset()?;
41 let path_res = ctx.sema.resolve_path(&path)?;
42 let def = match path_res {
43 PathResolution::Def(def) => def,
44 _ => return None,
45 };
46
47 let current_module = ctx.sema.scope(&path.syntax()).module()?;
48 let target_module = def.module(ctx.db)?;
49
50 let vis = target_module.visibility_of(ctx.db, &def)?;
51 if vis.is_visible_from(ctx.db, current_module.into()) {
52 return None;
53 };
54
55 let (offset, target, target_file, target_name) = target_data_for_def(ctx.db, def)?;
56
57 let missing_visibility =
58 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
59
60 let assist_label = match target_name {
61 None => format!("Change visibility to {}", missing_visibility),
62 Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
63 };
64
65 acc.add(AssistId("fix_visibility"), assist_label, target, |builder| {
66 builder.set_file(target_file);
67 match ctx.config.snippet_cap {
68 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
69 None => builder.insert(offset, format!("{} ", missing_visibility)),
70 }
71 })
72}
73
74fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
75 let record_field: ast::RecordField = ctx.find_node_at_offset()?;
76 let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?;
77
78 let current_module = ctx.sema.scope(record_field.syntax()).module()?;
79 let visibility = record_field_def.visibility(ctx.db);
80 if visibility.is_visible_from(ctx.db, current_module.into()) {
81 return None;
82 }
83
84 let parent = record_field_def.parent_def(ctx.db);
85 let parent_name = parent.name(ctx.db);
86 let target_module = parent.module(ctx.db);
87
88 let in_file_source = record_field_def.source(ctx.db);
89 let (offset, target) = match in_file_source.value {
90 hir::FieldSource::Named(it) => {
91 let s = it.syntax();
92 (vis_offset(s), s.text_range())
93 }
94 hir::FieldSource::Pos(it) => {
95 let s = it.syntax();
96 (vis_offset(s), s.text_range())
97 }
98 };
99
100 let missing_visibility =
101 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
102 let target_file = in_file_source.file_id.original_file(ctx.db);
103
104 let target_name = record_field_def.name(ctx.db);
105 let assist_label =
106 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
107
108 acc.add(AssistId("fix_visibility"), assist_label, target, |builder| {
109 builder.set_file(target_file);
110 match ctx.config.snippet_cap {
111 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
112 None => builder.insert(offset, format!("{} ", missing_visibility)),
113 }
114 })
115}
116
117fn target_data_for_def(
118 db: &dyn HirDatabase,
119 def: hir::ModuleDef,
120) -> Option<(TextSize, TextRange, FileId, Option<hir::Name>)> {
121 fn offset_target_and_file_id<S, Ast>(
122 db: &dyn HirDatabase,
123 x: S,
124 ) -> (TextSize, TextRange, FileId)
125 where
126 S: HasSource<Ast = Ast>,
127 Ast: AstNode,
128 {
129 let source = x.source(db);
130 let in_file_syntax = source.syntax();
131 let file_id = in_file_syntax.file_id;
132 let syntax = in_file_syntax.value;
133 (vis_offset(syntax), syntax.text_range(), file_id.original_file(db.upcast()))
134 }
135
136 let target_name;
137 let (offset, target, target_file) = match def {
138 hir::ModuleDef::Function(f) => {
139 target_name = Some(f.name(db));
140 offset_target_and_file_id(db, f)
141 }
142 hir::ModuleDef::Adt(adt) => {
143 target_name = Some(adt.name(db));
144 match adt {
145 hir::Adt::Struct(s) => offset_target_and_file_id(db, s),
146 hir::Adt::Union(u) => offset_target_and_file_id(db, u),
147 hir::Adt::Enum(e) => offset_target_and_file_id(db, e),
148 }
149 }
150 hir::ModuleDef::Const(c) => {
151 target_name = c.name(db);
152 offset_target_and_file_id(db, c)
153 }
154 hir::ModuleDef::Static(s) => {
155 target_name = s.name(db);
156 offset_target_and_file_id(db, s)
157 }
158 hir::ModuleDef::Trait(t) => {
159 target_name = Some(t.name(db));
160 offset_target_and_file_id(db, t)
161 }
162 hir::ModuleDef::TypeAlias(t) => {
163 target_name = Some(t.name(db));
164 offset_target_and_file_id(db, t)
165 }
166 hir::ModuleDef::Module(m) => {
167 target_name = m.name(db);
168 let in_file_source = m.declaration_source(db)?;
169 let file_id = in_file_source.file_id.original_file(db.upcast());
170 let syntax = in_file_source.value.syntax();
171 (vis_offset(syntax), syntax.text_range(), file_id)
172 }
173 // Enum variants can't be private, we can't modify builtin types
174 hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None,
175 };
176
177 Some((offset, target, target_file, target_name))
178}
179
180fn vis_offset(node: &SyntaxNode) -> TextSize {
181 node.children_with_tokens()
182 .skip_while(|it| match it.kind() {
183 WHITESPACE | COMMENT | ATTR => true,
184 _ => false,
185 })
186 .next()
187 .map(|it| it.text_range().start())
188 .unwrap_or_else(|| node.text_range().start())
189}
190
191#[cfg(test)]
192mod tests {
193 use crate::tests::{check_assist, check_assist_not_applicable};
194
195 use super::*;
196
197 #[test]
198 fn fix_visibility_of_fn() {
199 check_assist(
200 fix_visibility,
201 r"mod foo { fn foo() {} }
202 fn main() { foo::foo<|>() } ",
203 r"mod foo { $0pub(crate) fn foo() {} }
204 fn main() { foo::foo() } ",
205 );
206 check_assist_not_applicable(
207 fix_visibility,
208 r"mod foo { pub fn foo() {} }
209 fn main() { foo::foo<|>() } ",
210 )
211 }
212
213 #[test]
214 fn fix_visibility_of_adt_in_submodule() {
215 check_assist(
216 fix_visibility,
217 r"mod foo { struct Foo; }
218 fn main() { foo::Foo<|> } ",
219 r"mod foo { $0pub(crate) struct Foo; }
220 fn main() { foo::Foo } ",
221 );
222 check_assist_not_applicable(
223 fix_visibility,
224 r"mod foo { pub struct Foo; }
225 fn main() { foo::Foo<|> } ",
226 );
227 check_assist(
228 fix_visibility,
229 r"mod foo { enum Foo; }
230 fn main() { foo::Foo<|> } ",
231 r"mod foo { $0pub(crate) enum Foo; }
232 fn main() { foo::Foo } ",
233 );
234 check_assist_not_applicable(
235 fix_visibility,
236 r"mod foo { pub enum Foo; }
237 fn main() { foo::Foo<|> } ",
238 );
239 check_assist(
240 fix_visibility,
241 r"mod foo { union Foo; }
242 fn main() { foo::Foo<|> } ",
243 r"mod foo { $0pub(crate) union Foo; }
244 fn main() { foo::Foo } ",
245 );
246 check_assist_not_applicable(
247 fix_visibility,
248 r"mod foo { pub union Foo; }
249 fn main() { foo::Foo<|> } ",
250 );
251 }
252
253 #[test]
254 fn fix_visibility_of_adt_in_other_file() {
255 check_assist(
256 fix_visibility,
257 r"
258 //- /main.rs
259 mod foo;
260 fn main() { foo::Foo<|> }
261
262 //- /foo.rs
263 struct Foo;
264 ",
265 r"$0pub(crate) struct Foo;
266
267",
268 );
269 }
270
271 #[test]
272 fn fix_visibility_of_struct_field() {
273 check_assist(
274 fix_visibility,
275 r"mod foo { pub struct Foo { bar: (), } }
276 fn main() { foo::Foo { <|>bar: () }; } ",
277 r"mod foo { pub struct Foo { $0pub(crate) bar: (), } }
278 fn main() { foo::Foo { bar: () }; } ",
279 );
280 check_assist(
281 fix_visibility,
282 r"//- /lib.rs
283 mod foo;
284 fn main() { foo::Foo { <|>bar: () }; }
285 //- /foo.rs
286 pub struct Foo { bar: () }
287 ",
288 r"pub struct Foo { $0pub(crate) bar: () }
289
290",
291 );
292 check_assist_not_applicable(
293 fix_visibility,
294 r"mod foo { pub struct Foo { pub bar: (), } }
295 fn main() { foo::Foo { <|>bar: () }; } ",
296 );
297 check_assist_not_applicable(
298 fix_visibility,
299 r"//- /lib.rs
300 mod foo;
301 fn main() { foo::Foo { <|>bar: () }; }
302 //- /foo.rs
303 pub struct Foo { pub bar: () }
304 ",
305 );
306 }
307
308 #[test]
309 fn fix_visibility_of_enum_variant_field() {
310 check_assist(
311 fix_visibility,
312 r"mod foo { pub enum Foo { Bar { bar: () } } }
313 fn main() { foo::Foo::Bar { <|>bar: () }; } ",
314 r"mod foo { pub enum Foo { Bar { $0pub(crate) bar: () } } }
315 fn main() { foo::Foo::Bar { bar: () }; } ",
316 );
317 check_assist(
318 fix_visibility,
319 r"//- /lib.rs
320 mod foo;
321 fn main() { foo::Foo::Bar { <|>bar: () }; }
322 //- /foo.rs
323 pub enum Foo { Bar { bar: () } }
324 ",
325 r"pub enum Foo { Bar { $0pub(crate) bar: () } }
326
327",
328 );
329 check_assist_not_applicable(
330 fix_visibility,
331 r"mod foo { pub struct Foo { pub bar: (), } }
332 fn main() { foo::Foo { <|>bar: () }; } ",
333 );
334 check_assist_not_applicable(
335 fix_visibility,
336 r"//- /lib.rs
337 mod foo;
338 fn main() { foo::Foo { <|>bar: () }; }
339 //- /foo.rs
340 pub struct Foo { pub bar: () }
341 ",
342 );
343 }
344
345 #[test]
346 #[ignore]
347 // FIXME reenable this test when `Semantics::resolve_record_field` works with union fields
348 fn fix_visibility_of_union_field() {
349 check_assist(
350 fix_visibility,
351 r"mod foo { pub union Foo { bar: (), } }
352 fn main() { foo::Foo { <|>bar: () }; } ",
353 r"mod foo { pub union Foo { $0pub(crate) bar: (), } }
354 fn main() { foo::Foo { bar: () }; } ",
355 );
356 check_assist(
357 fix_visibility,
358 r"//- /lib.rs
359 mod foo;
360 fn main() { foo::Foo { <|>bar: () }; }
361 //- /foo.rs
362 pub union Foo { bar: () }
363 ",
364 r"pub union Foo { $0pub(crate) bar: () }
365
366",
367 );
368 check_assist_not_applicable(
369 fix_visibility,
370 r"mod foo { pub union Foo { pub bar: (), } }
371 fn main() { foo::Foo { <|>bar: () }; } ",
372 );
373 check_assist_not_applicable(
374 fix_visibility,
375 r"//- /lib.rs
376 mod foo;
377 fn main() { foo::Foo { <|>bar: () }; }
378 //- /foo.rs
379 pub union Foo { pub bar: () }
380 ",
381 );
382 }
383
384 #[test]
385 fn fix_visibility_of_const() {
386 check_assist(
387 fix_visibility,
388 r"mod foo { const FOO: () = (); }
389 fn main() { foo::FOO<|> } ",
390 r"mod foo { $0pub(crate) const FOO: () = (); }
391 fn main() { foo::FOO } ",
392 );
393 check_assist_not_applicable(
394 fix_visibility,
395 r"mod foo { pub const FOO: () = (); }
396 fn main() { foo::FOO<|> } ",
397 );
398 }
399
400 #[test]
401 fn fix_visibility_of_static() {
402 check_assist(
403 fix_visibility,
404 r"mod foo { static FOO: () = (); }
405 fn main() { foo::FOO<|> } ",
406 r"mod foo { $0pub(crate) static FOO: () = (); }
407 fn main() { foo::FOO } ",
408 );
409 check_assist_not_applicable(
410 fix_visibility,
411 r"mod foo { pub static FOO: () = (); }
412 fn main() { foo::FOO<|> } ",
413 );
414 }
415
416 #[test]
417 fn fix_visibility_of_trait() {
418 check_assist(
419 fix_visibility,
420 r"mod foo { trait Foo { fn foo(&self) {} } }
421 fn main() { let x: &dyn foo::<|>Foo; } ",
422 r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } }
423 fn main() { let x: &dyn foo::Foo; } ",
424 );
425 check_assist_not_applicable(
426 fix_visibility,
427 r"mod foo { pub trait Foo { fn foo(&self) {} } }
428 fn main() { let x: &dyn foo::Foo<|>; } ",
429 );
430 }
431
432 #[test]
433 fn fix_visibility_of_type_alias() {
434 check_assist(
435 fix_visibility,
436 r"mod foo { type Foo = (); }
437 fn main() { let x: foo::Foo<|>; } ",
438 r"mod foo { $0pub(crate) type Foo = (); }
439 fn main() { let x: foo::Foo; } ",
440 );
441 check_assist_not_applicable(
442 fix_visibility,
443 r"mod foo { pub type Foo = (); }
444 fn main() { let x: foo::Foo<|>; } ",
445 );
446 }
447
448 #[test]
449 fn fix_visibility_of_module() {
450 check_assist(
451 fix_visibility,
452 r"mod foo { mod bar { fn bar() {} } }
453 fn main() { foo::bar<|>::bar(); } ",
454 r"mod foo { $0pub(crate) mod bar { fn bar() {} } }
455 fn main() { foo::bar::bar(); } ",
456 );
457
458 check_assist(
459 fix_visibility,
460 r"
461 //- /main.rs
462 mod foo;
463 fn main() { foo::bar<|>::baz(); }
464
465 //- /foo.rs
466 mod bar {
467 pub fn baz() {}
468 }
469 ",
470 r"$0pub(crate) mod bar {
471 pub fn baz() {}
472}
473
474",
475 );
476
477 check_assist_not_applicable(
478 fix_visibility,
479 r"mod foo { pub mod bar { pub fn bar() {} } }
480 fn main() { foo::bar<|>::bar(); } ",
481 );
482 }
483
484 #[test]
485 fn fix_visibility_of_inline_module_in_other_file() {
486 check_assist(
487 fix_visibility,
488 r"
489 //- /main.rs
490 mod foo;
491 fn main() { foo::bar<|>::baz(); }
492
493 //- /foo.rs
494 mod bar;
495
496 //- /foo/bar.rs
497 pub fn baz() {}
498 }
499 ",
500 r"$0pub(crate) mod bar;
501",
502 );
503 }
504
505 #[test]
506 fn fix_visibility_of_module_declaration_in_other_file() {
507 check_assist(
508 fix_visibility,
509 r"//- /main.rs
510 mod foo;
511 fn main() { foo::bar<|>>::baz(); }
512
513 //- /foo.rs
514 mod bar {
515 pub fn baz() {}
516 }",
517 r"$0pub(crate) mod bar {
518 pub fn baz() {}
519}
520",
521 );
522 }
523
524 #[test]
525 fn adds_pub_when_target_is_in_another_crate() {
526 check_assist(
527 fix_visibility,
528 r"//- /main.rs crate:a deps:foo
529 foo::Bar<|>
530 //- /lib.rs crate:foo
531 struct Bar;",
532 r"$0pub struct Bar;
533",
534 )
535 }
536
537 #[test]
538 #[ignore]
539 // FIXME handle reexports properly
540 fn fix_visibility_of_reexport() {
541 check_assist(
542 fix_visibility,
543 r"
544 mod foo {
545 use bar::Baz;
546 mod bar { pub(super) struct Baz; }
547 }
548 foo::Baz<|>
549 ",
550 r"
551 mod foo {
552 $0pub(crate) use bar::Baz;
553 mod bar { pub(super) struct Baz; }
554 }
555 foo::Baz
556 ",
557 )
558 }
559}
diff --git a/crates/ra_assists/src/handlers/flip_binexpr.rs b/crates/ra_assists/src/handlers/flip_binexpr.rs
index 8030efb35..573196576 100644
--- a/crates/ra_assists/src/handlers/flip_binexpr.rs
+++ b/crates/ra_assists/src/handlers/flip_binexpr.rs
@@ -1,6 +1,6 @@
1use ra_syntax::ast::{AstNode, BinExpr, BinOp}; 1use ra_syntax::ast::{AstNode, BinExpr, BinOp};
2 2
3use crate::{Assist, AssistCtx, AssistId}; 3use crate::{AssistContext, AssistId, Assists};
4 4
5// Assist: flip_binexpr 5// Assist: flip_binexpr
6// 6//
@@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId};
17// let _ = 2 + 90; 17// let _ = 2 + 90;
18// } 18// }
19// ``` 19// ```
20pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option<Assist> { 20pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let expr = ctx.find_node_at_offset::<BinExpr>()?; 21 let expr = ctx.find_node_at_offset::<BinExpr>()?;
22 let lhs = expr.lhs()?.syntax().clone(); 22 let lhs = expr.lhs()?.syntax().clone();
23 let rhs = expr.rhs()?.syntax().clone(); 23 let rhs = expr.rhs()?.syntax().clone();
@@ -33,8 +33,7 @@ pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option<Assist> {
33 return None; 33 return None;
34 } 34 }
35 35
36 ctx.add_assist(AssistId("flip_binexpr"), "Flip binary expression", |edit| { 36 acc.add(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| {
37 edit.target(op_range);
38 if let FlipAction::FlipAndReplaceOp(new_op) = action { 37 if let FlipAction::FlipAndReplaceOp(new_op) = action {
39 edit.replace(op_range, new_op); 38 edit.replace(op_range, new_op);
40 } 39 }
@@ -69,7 +68,7 @@ impl From<BinOp> for FlipAction {
69mod tests { 68mod tests {
70 use super::*; 69 use super::*;
71 70
72 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 71 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
73 72
74 #[test] 73 #[test]
75 fn flip_binexpr_target_is_the_op() { 74 fn flip_binexpr_target_is_the_op() {
@@ -86,17 +85,13 @@ mod tests {
86 check_assist( 85 check_assist(
87 flip_binexpr, 86 flip_binexpr,
88 "fn f() { let res = 1 ==<|> 2; }", 87 "fn f() { let res = 1 ==<|> 2; }",
89 "fn f() { let res = 2 ==<|> 1; }", 88 "fn f() { let res = 2 == 1; }",
90 ) 89 )
91 } 90 }
92 91
93 #[test] 92 #[test]
94 fn flip_binexpr_works_for_gt() { 93 fn flip_binexpr_works_for_gt() {
95 check_assist( 94 check_assist(flip_binexpr, "fn f() { let res = 1 ><|> 2; }", "fn f() { let res = 2 < 1; }")
96 flip_binexpr,
97 "fn f() { let res = 1 ><|> 2; }",
98 "fn f() { let res = 2 <<|> 1; }",
99 )
100 } 95 }
101 96
102 #[test] 97 #[test]
@@ -104,7 +99,7 @@ mod tests {
104 check_assist( 99 check_assist(
105 flip_binexpr, 100 flip_binexpr,
106 "fn f() { let res = 1 <=<|> 2; }", 101 "fn f() { let res = 1 <=<|> 2; }",
107 "fn f() { let res = 2 >=<|> 1; }", 102 "fn f() { let res = 2 >= 1; }",
108 ) 103 )
109 } 104 }
110 105
@@ -113,7 +108,7 @@ mod tests {
113 check_assist( 108 check_assist(
114 flip_binexpr, 109 flip_binexpr,
115 "fn f() { let res = (1 + 1) ==<|> (2 + 2); }", 110 "fn f() { let res = (1 + 1) ==<|> (2 + 2); }",
116 "fn f() { let res = (2 + 2) ==<|> (1 + 1); }", 111 "fn f() { let res = (2 + 2) == (1 + 1); }",
117 ) 112 )
118 } 113 }
119 114
@@ -133,7 +128,7 @@ mod tests {
133 fn dyn_eq(&self, other: &dyn Diagnostic) -> bool { 128 fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
134 match other.downcast_ref::<Self>() { 129 match other.downcast_ref::<Self>() {
135 None => false, 130 None => false,
136 Some(it) => self ==<|> it, 131 Some(it) => self == it,
137 } 132 }
138 } 133 }
139 "#, 134 "#,
diff --git a/crates/ra_assists/src/handlers/flip_comma.rs b/crates/ra_assists/src/handlers/flip_comma.rs
index 1dacf29f8..a57a1c463 100644
--- a/crates/ra_assists/src/handlers/flip_comma.rs
+++ b/crates/ra_assists/src/handlers/flip_comma.rs
@@ -1,6 +1,6 @@
1use ra_syntax::{algo::non_trivia_sibling, Direction, T}; 1use ra_syntax::{algo::non_trivia_sibling, Direction, T};
2 2
3use crate::{Assist, AssistCtx, AssistId}; 3use crate::{AssistContext, AssistId, Assists};
4 4
5// Assist: flip_comma 5// Assist: flip_comma
6// 6//
@@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId};
17// ((3, 4), (1, 2)); 17// ((3, 4), (1, 2));
18// } 18// }
19// ``` 19// ```
20pub(crate) fn flip_comma(ctx: AssistCtx) -> Option<Assist> { 20pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let comma = ctx.find_token_at_offset(T![,])?; 21 let comma = ctx.find_token_at_offset(T![,])?;
22 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; 22 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
23 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; 23 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
@@ -28,8 +28,7 @@ pub(crate) fn flip_comma(ctx: AssistCtx) -> Option<Assist> {
28 return None; 28 return None;
29 } 29 }
30 30
31 ctx.add_assist(AssistId("flip_comma"), "Flip comma", |edit| { 31 acc.add(AssistId("flip_comma"), "Flip comma", comma.text_range(), |edit| {
32 edit.target(comma.text_range());
33 edit.replace(prev.text_range(), next.to_string()); 32 edit.replace(prev.text_range(), next.to_string());
34 edit.replace(next.text_range(), prev.to_string()); 33 edit.replace(next.text_range(), prev.to_string());
35 }) 34 })
@@ -39,14 +38,14 @@ pub(crate) fn flip_comma(ctx: AssistCtx) -> Option<Assist> {
39mod tests { 38mod tests {
40 use super::*; 39 use super::*;
41 40
42 use crate::helpers::{check_assist, check_assist_target}; 41 use crate::tests::{check_assist, check_assist_target};
43 42
44 #[test] 43 #[test]
45 fn flip_comma_works_for_function_parameters() { 44 fn flip_comma_works_for_function_parameters() {
46 check_assist( 45 check_assist(
47 flip_comma, 46 flip_comma,
48 "fn foo(x: i32,<|> y: Result<(), ()>) {}", 47 "fn foo(x: i32,<|> y: Result<(), ()>) {}",
49 "fn foo(y: Result<(), ()>,<|> x: i32) {}", 48 "fn foo(y: Result<(), ()>, x: i32) {}",
50 ) 49 )
51 } 50 }
52 51
diff --git a/crates/ra_assists/src/handlers/flip_trait_bound.rs b/crates/ra_assists/src/handlers/flip_trait_bound.rs
index f56769624..0115adc8b 100644
--- a/crates/ra_assists/src/handlers/flip_trait_bound.rs
+++ b/crates/ra_assists/src/handlers/flip_trait_bound.rs
@@ -4,7 +4,7 @@ use ra_syntax::{
4 Direction, T, 4 Direction, T,
5}; 5};
6 6
7use crate::{Assist, AssistCtx, AssistId}; 7use crate::{AssistContext, AssistId, Assists};
8 8
9// Assist: flip_trait_bound 9// Assist: flip_trait_bound
10// 10//
@@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId};
17// ``` 17// ```
18// fn foo<T: Copy + Clone>() { } 18// fn foo<T: Copy + Clone>() { }
19// ``` 19// ```
20pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> { 20pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 // We want to replicate the behavior of `flip_binexpr` by only suggesting 21 // We want to replicate the behavior of `flip_binexpr` by only suggesting
22 // the assist when the cursor is on a `+` 22 // the assist when the cursor is on a `+`
23 let plus = ctx.find_token_at_offset(T![+])?; 23 let plus = ctx.find_token_at_offset(T![+])?;
@@ -32,8 +32,8 @@ pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> {
32 non_trivia_sibling(plus.clone().into(), Direction::Next)?, 32 non_trivia_sibling(plus.clone().into(), Direction::Next)?,
33 ); 33 );
34 34
35 ctx.add_assist(AssistId("flip_trait_bound"), "Flip trait bounds", |edit| { 35 let target = plus.text_range();
36 edit.target(plus.text_range()); 36 acc.add(AssistId("flip_trait_bound"), "Flip trait bounds", target, |edit| {
37 edit.replace(before.text_range(), after.to_string()); 37 edit.replace(before.text_range(), after.to_string());
38 edit.replace(after.text_range(), before.to_string()); 38 edit.replace(after.text_range(), before.to_string());
39 }) 39 })
@@ -43,7 +43,7 @@ pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> {
43mod tests { 43mod tests {
44 use super::*; 44 use super::*;
45 45
46 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 46 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
47 47
48 #[test] 48 #[test]
49 fn flip_trait_bound_assist_available() { 49 fn flip_trait_bound_assist_available() {
@@ -60,7 +60,7 @@ mod tests {
60 check_assist( 60 check_assist(
61 flip_trait_bound, 61 flip_trait_bound,
62 "struct S<T> where T: A <|>+ B { }", 62 "struct S<T> where T: A <|>+ B { }",
63 "struct S<T> where T: B <|>+ A { }", 63 "struct S<T> where T: B + A { }",
64 ) 64 )
65 } 65 }
66 66
@@ -69,13 +69,13 @@ mod tests {
69 check_assist( 69 check_assist(
70 flip_trait_bound, 70 flip_trait_bound,
71 "impl X for S<T> where T: A +<|> B { }", 71 "impl X for S<T> where T: A +<|> B { }",
72 "impl X for S<T> where T: B +<|> A { }", 72 "impl X for S<T> where T: B + A { }",
73 ) 73 )
74 } 74 }
75 75
76 #[test] 76 #[test]
77 fn flip_trait_bound_works_for_fn() { 77 fn flip_trait_bound_works_for_fn() {
78 check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B <|>+ A>(t: T) { }") 78 check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B + A>(t: T) { }")
79 } 79 }
80 80
81 #[test] 81 #[test]
@@ -83,7 +83,7 @@ mod tests {
83 check_assist( 83 check_assist(
84 flip_trait_bound, 84 flip_trait_bound,
85 "fn f<T>(t: T) where T: A +<|> B { }", 85 "fn f<T>(t: T) where T: A +<|> B { }",
86 "fn f<T>(t: T) where T: B +<|> A { }", 86 "fn f<T>(t: T) where T: B + A { }",
87 ) 87 )
88 } 88 }
89 89
@@ -92,7 +92,7 @@ mod tests {
92 check_assist( 92 check_assist(
93 flip_trait_bound, 93 flip_trait_bound,
94 "fn f<T>(t: T) where T: A <|>+ 'static { }", 94 "fn f<T>(t: T) where T: A <|>+ 'static { }",
95 "fn f<T>(t: T) where T: 'static <|>+ A { }", 95 "fn f<T>(t: T) where T: 'static + A { }",
96 ) 96 )
97 } 97 }
98 98
@@ -101,7 +101,7 @@ mod tests {
101 check_assist( 101 check_assist(
102 flip_trait_bound, 102 flip_trait_bound,
103 "struct S<T> where T: A<T> <|>+ b_mod::B<T> + C<T> { }", 103 "struct S<T> where T: A<T> <|>+ b_mod::B<T> + C<T> { }",
104 "struct S<T> where T: b_mod::B<T> <|>+ A<T> + C<T> { }", 104 "struct S<T> where T: b_mod::B<T> + A<T> + C<T> { }",
105 ) 105 )
106 } 106 }
107 107
@@ -110,7 +110,7 @@ mod tests {
110 check_assist( 110 check_assist(
111 flip_trait_bound, 111 flip_trait_bound,
112 "struct S<T> where T: A + B + C + D + E + F +<|> G + H + I + J { }", 112 "struct S<T> where T: A + B + C + D + E + F +<|> G + H + I + J { }",
113 "struct S<T> where T: A + B + C + D + E + G +<|> F + H + I + J { }", 113 "struct S<T> where T: A + B + C + D + E + G + F + H + I + J { }",
114 ) 114 )
115 } 115 }
116} 116}
diff --git a/crates/ra_assists/src/handlers/inline_local_variable.rs b/crates/ra_assists/src/handlers/inline_local_variable.rs
index f5702f6e0..d26e68847 100644
--- a/crates/ra_assists/src/handlers/inline_local_variable.rs
+++ b/crates/ra_assists/src/handlers/inline_local_variable.rs
@@ -3,9 +3,12 @@ use ra_syntax::{
3 ast::{self, AstNode, AstToken}, 3 ast::{self, AstNode, AstToken},
4 TextRange, 4 TextRange,
5}; 5};
6use test_utils::tested_by; 6use test_utils::mark;
7 7
8use crate::{assist_ctx::ActionBuilder, Assist, AssistCtx, AssistId}; 8use crate::{
9 assist_context::{AssistContext, Assists},
10 AssistId,
11};
9 12
10// Assist: inline_local_variable 13// Assist: inline_local_variable
11// 14//
@@ -23,18 +26,18 @@ use crate::{assist_ctx::ActionBuilder, Assist, AssistCtx, AssistId};
23// (1 + 2) * 4; 26// (1 + 2) * 4;
24// } 27// }
25// ``` 28// ```
26pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> { 29pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; 30 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
28 let bind_pat = match let_stmt.pat()? { 31 let bind_pat = match let_stmt.pat()? {
29 ast::Pat::BindPat(pat) => pat, 32 ast::Pat::BindPat(pat) => pat,
30 _ => return None, 33 _ => return None,
31 }; 34 };
32 if bind_pat.mut_token().is_some() { 35 if bind_pat.mut_token().is_some() {
33 tested_by!(test_not_inline_mut_variable); 36 mark::hit!(test_not_inline_mut_variable);
34 return None; 37 return None;
35 } 38 }
36 if !bind_pat.syntax().text_range().contains_inclusive(ctx.frange.range.start()) { 39 if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) {
37 tested_by!(not_applicable_outside_of_bind_pat); 40 mark::hit!(not_applicable_outside_of_bind_pat);
38 return None; 41 return None;
39 } 42 }
40 let initializer_expr = let_stmt.initializer()?; 43 let initializer_expr = let_stmt.initializer()?;
@@ -43,7 +46,7 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> {
43 let def = Definition::Local(def); 46 let def = Definition::Local(def);
44 let refs = def.find_usages(ctx.db, None); 47 let refs = def.find_usages(ctx.db, None);
45 if refs.is_empty() { 48 if refs.is_empty() {
46 tested_by!(test_not_applicable_if_variable_unused); 49 mark::hit!(test_not_applicable_if_variable_unused);
47 return None; 50 return None;
48 }; 51 };
49 52
@@ -89,6 +92,7 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> {
89 | (ast::Expr::ParenExpr(_), _) 92 | (ast::Expr::ParenExpr(_), _)
90 | (ast::Expr::PathExpr(_), _) 93 | (ast::Expr::PathExpr(_), _)
91 | (ast::Expr::BlockExpr(_), _) 94 | (ast::Expr::BlockExpr(_), _)
95 | (ast::Expr::EffectExpr(_), _)
92 | (_, ast::Expr::CallExpr(_)) 96 | (_, ast::Expr::CallExpr(_))
93 | (_, ast::Expr::TupleExpr(_)) 97 | (_, ast::Expr::TupleExpr(_))
94 | (_, ast::Expr::ArrayExpr(_)) 98 | (_, ast::Expr::ArrayExpr(_))
@@ -105,26 +109,21 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> {
105 let init_str = initializer_expr.syntax().text().to_string(); 109 let init_str = initializer_expr.syntax().text().to_string();
106 let init_in_paren = format!("({})", &init_str); 110 let init_in_paren = format!("({})", &init_str);
107 111
108 ctx.add_assist( 112 let target = bind_pat.syntax().text_range();
109 AssistId("inline_local_variable"), 113 acc.add(AssistId("inline_local_variable"), "Inline variable", target, move |builder| {
110 "Inline variable", 114 builder.delete(delete_range);
111 move |edit: &mut ActionBuilder| { 115 for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) {
112 edit.delete(delete_range); 116 let replacement = if should_wrap { init_in_paren.clone() } else { init_str.clone() };
113 for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { 117 builder.replace(desc.file_range.range, replacement)
114 let replacement = 118 }
115 if should_wrap { init_in_paren.clone() } else { init_str.clone() }; 119 })
116 edit.replace(desc.file_range.range, replacement)
117 }
118 edit.set_cursor(delete_range.start())
119 },
120 )
121} 120}
122 121
123#[cfg(test)] 122#[cfg(test)]
124mod tests { 123mod tests {
125 use test_utils::covers; 124 use test_utils::mark;
126 125
127 use crate::helpers::{check_assist, check_assist_not_applicable}; 126 use crate::tests::{check_assist, check_assist_not_applicable};
128 127
129 use super::*; 128 use super::*;
130 129
@@ -149,7 +148,7 @@ fn foo() {
149 r" 148 r"
150fn bar(a: usize) {} 149fn bar(a: usize) {}
151fn foo() { 150fn foo() {
152 <|>1 + 1; 151 1 + 1;
153 if 1 > 10 { 152 if 1 > 10 {
154 } 153 }
155 154
@@ -183,7 +182,7 @@ fn foo() {
183 r" 182 r"
184fn bar(a: usize) {} 183fn bar(a: usize) {}
185fn foo() { 184fn foo() {
186 <|>(1 + 1) + 1; 185 (1 + 1) + 1;
187 if (1 + 1) > 10 { 186 if (1 + 1) > 10 {
188 } 187 }
189 188
@@ -217,7 +216,7 @@ fn foo() {
217 r" 216 r"
218fn bar(a: usize) {} 217fn bar(a: usize) {}
219fn foo() { 218fn foo() {
220 <|>bar(1) + 1; 219 bar(1) + 1;
221 if bar(1) > 10 { 220 if bar(1) > 10 {
222 } 221 }
223 222
@@ -251,7 +250,7 @@ fn foo() {
251 r" 250 r"
252fn bar(a: usize): usize { a } 251fn bar(a: usize): usize { a }
253fn foo() { 252fn foo() {
254 <|>(bar(1) as u64) + 1; 253 (bar(1) as u64) + 1;
255 if (bar(1) as u64) > 10 { 254 if (bar(1) as u64) > 10 {
256 } 255 }
257 256
@@ -283,7 +282,7 @@ fn foo() {
283}", 282}",
284 r" 283 r"
285fn foo() { 284fn foo() {
286 <|>{ 10 + 1 } + 1; 285 { 10 + 1 } + 1;
287 if { 10 + 1 } > 10 { 286 if { 10 + 1 } > 10 {
288 } 287 }
289 288
@@ -315,7 +314,7 @@ fn foo() {
315}", 314}",
316 r" 315 r"
317fn foo() { 316fn foo() {
318 <|>( 10 + 1 ) + 1; 317 ( 10 + 1 ) + 1;
319 if ( 10 + 1 ) > 10 { 318 if ( 10 + 1 ) > 10 {
320 } 319 }
321 320
@@ -330,7 +329,7 @@ fn foo() {
330 329
331 #[test] 330 #[test]
332 fn test_not_inline_mut_variable() { 331 fn test_not_inline_mut_variable() {
333 covers!(test_not_inline_mut_variable); 332 mark::check!(test_not_inline_mut_variable);
334 check_assist_not_applicable( 333 check_assist_not_applicable(
335 inline_local_variable, 334 inline_local_variable,
336 r" 335 r"
@@ -353,7 +352,7 @@ fn foo() {
353}", 352}",
354 r" 353 r"
355fn foo() { 354fn foo() {
356 <|>let b = bar(10 + 1) * 10; 355 let b = bar(10 + 1) * 10;
357 let c = bar(10 + 1) as usize; 356 let c = bar(10 + 1) as usize;
358}", 357}",
359 ); 358 );
@@ -373,7 +372,7 @@ fn foo() {
373 r" 372 r"
374fn foo() { 373fn foo() {
375 let x = vec![1, 2, 3]; 374 let x = vec![1, 2, 3];
376 <|>let b = x[0] * 10; 375 let b = x[0] * 10;
377 let c = x[0] as usize; 376 let c = x[0] as usize;
378}", 377}",
379 ); 378 );
@@ -393,7 +392,7 @@ fn foo() {
393 r" 392 r"
394fn foo() { 393fn foo() {
395 let bar = vec![1]; 394 let bar = vec![1];
396 <|>let b = bar.len() * 10; 395 let b = bar.len() * 10;
397 let c = bar.len() as usize; 396 let c = bar.len() as usize;
398}", 397}",
399 ); 398 );
@@ -421,7 +420,7 @@ struct Bar {
421 420
422fn foo() { 421fn foo() {
423 let bar = Bar { foo: 1 }; 422 let bar = Bar { foo: 1 };
424 <|>let b = bar.foo * 10; 423 let b = bar.foo * 10;
425 let c = bar.foo as usize; 424 let c = bar.foo as usize;
426}", 425}",
427 ); 426 );
@@ -442,7 +441,7 @@ fn foo() -> Option<usize> {
442 r" 441 r"
443fn foo() -> Option<usize> { 442fn foo() -> Option<usize> {
444 let bar = Some(1); 443 let bar = Some(1);
445 <|>let b = bar? * 10; 444 let b = bar? * 10;
446 let c = bar? as usize; 445 let c = bar? as usize;
447 None 446 None
448}", 447}",
@@ -462,7 +461,7 @@ fn foo() {
462 r" 461 r"
463fn foo() { 462fn foo() {
464 let bar = 10; 463 let bar = 10;
465 <|>let b = &bar * 10; 464 let b = &bar * 10;
466}", 465}",
467 ); 466 );
468 } 467 }
@@ -478,7 +477,7 @@ fn foo() {
478}", 477}",
479 r" 478 r"
480fn foo() { 479fn foo() {
481 <|>let b = (10, 20)[0]; 480 let b = (10, 20)[0];
482}", 481}",
483 ); 482 );
484 } 483 }
@@ -494,7 +493,7 @@ fn foo() {
494}", 493}",
495 r" 494 r"
496fn foo() { 495fn foo() {
497 <|>let b = [1, 2, 3].len(); 496 let b = [1, 2, 3].len();
498}", 497}",
499 ); 498 );
500 } 499 }
@@ -511,7 +510,7 @@ fn foo() {
511}", 510}",
512 r" 511 r"
513fn foo() { 512fn foo() {
514 <|>let b = (10 + 20) * 10; 513 let b = (10 + 20) * 10;
515 let c = (10 + 20) as usize; 514 let c = (10 + 20) as usize;
516}", 515}",
517 ); 516 );
@@ -531,7 +530,7 @@ fn foo() {
531 r" 530 r"
532fn foo() { 531fn foo() {
533 let d = 10; 532 let d = 10;
534 <|>let b = d * 10; 533 let b = d * 10;
535 let c = d as usize; 534 let c = d as usize;
536}", 535}",
537 ); 536 );
@@ -549,7 +548,7 @@ fn foo() {
549}", 548}",
550 r" 549 r"
551fn foo() { 550fn foo() {
552 <|>let b = { 10 } * 10; 551 let b = { 10 } * 10;
553 let c = { 10 } as usize; 552 let c = { 10 } as usize;
554}", 553}",
555 ); 554 );
@@ -569,7 +568,7 @@ fn foo() {
569}", 568}",
570 r" 569 r"
571fn foo() { 570fn foo() {
572 <|>let b = (10 + 20) * 10; 571 let b = (10 + 20) * 10;
573 let c = (10 + 20, 20); 572 let c = (10 + 20, 20);
574 let d = [10 + 20, 10]; 573 let d = [10 + 20, 10];
575 let e = (10 + 20); 574 let e = (10 + 20);
@@ -588,7 +587,7 @@ fn foo() {
588}", 587}",
589 r" 588 r"
590fn foo() { 589fn foo() {
591 <|>for i in vec![10, 20] {} 590 for i in vec![10, 20] {}
592}", 591}",
593 ); 592 );
594 } 593 }
@@ -604,7 +603,7 @@ fn foo() {
604}", 603}",
605 r" 604 r"
606fn foo() { 605fn foo() {
607 <|>while 1 > 0 {} 606 while 1 > 0 {}
608}", 607}",
609 ); 608 );
610 } 609 }
@@ -622,7 +621,7 @@ fn foo() {
622}", 621}",
623 r" 622 r"
624fn foo() { 623fn foo() {
625 <|>loop { 624 loop {
626 break 1 + 1; 625 break 1 + 1;
627 } 626 }
628}", 627}",
@@ -640,7 +639,7 @@ fn foo() {
640}", 639}",
641 r" 640 r"
642fn foo() { 641fn foo() {
643 <|>return 1 > 0; 642 return 1 > 0;
644}", 643}",
645 ); 644 );
646 } 645 }
@@ -656,14 +655,14 @@ fn foo() {
656}", 655}",
657 r" 656 r"
658fn foo() { 657fn foo() {
659 <|>match 1 > 0 {} 658 match 1 > 0 {}
660}", 659}",
661 ); 660 );
662 } 661 }
663 662
664 #[test] 663 #[test]
665 fn test_not_applicable_if_variable_unused() { 664 fn test_not_applicable_if_variable_unused() {
666 covers!(test_not_applicable_if_variable_unused); 665 mark::check!(test_not_applicable_if_variable_unused);
667 check_assist_not_applicable( 666 check_assist_not_applicable(
668 inline_local_variable, 667 inline_local_variable,
669 r" 668 r"
@@ -676,7 +675,7 @@ fn foo() {
676 675
677 #[test] 676 #[test]
678 fn not_applicable_outside_of_bind_pat() { 677 fn not_applicable_outside_of_bind_pat() {
679 covers!(not_applicable_outside_of_bind_pat); 678 mark::check!(not_applicable_outside_of_bind_pat);
680 check_assist_not_applicable( 679 check_assist_not_applicable(
681 inline_local_variable, 680 inline_local_variable,
682 r" 681 r"
diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/introduce_variable.rs
index eda9ac296..31d6539f7 100644
--- a/crates/ra_assists/src/handlers/introduce_variable.rs
+++ b/crates/ra_assists/src/handlers/introduce_variable.rs
@@ -4,12 +4,12 @@ use ra_syntax::{
4 BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, 4 BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR,
5 WHITESPACE, 5 WHITESPACE,
6 }, 6 },
7 SyntaxNode, TextSize, 7 SyntaxNode,
8}; 8};
9use stdx::format_to; 9use stdx::format_to;
10use test_utils::tested_by; 10use test_utils::mark;
11 11
12use crate::{Assist, AssistCtx, AssistId}; 12use crate::{AssistContext, AssistId, Assists};
13 13
14// Assist: introduce_variable 14// Assist: introduce_variable
15// 15//
@@ -23,17 +23,17 @@ use crate::{Assist, AssistCtx, AssistId};
23// -> 23// ->
24// ``` 24// ```
25// fn main() { 25// fn main() {
26// let var_name = (1 + 2); 26// let $0var_name = (1 + 2);
27// var_name * 4; 27// var_name * 4;
28// } 28// }
29// ``` 29// ```
30pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> { 30pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
31 if ctx.frange.range.is_empty() { 31 if ctx.frange.range.is_empty() {
32 return None; 32 return None;
33 } 33 }
34 let node = ctx.covering_element(); 34 let node = ctx.covering_element();
35 if node.kind() == COMMENT { 35 if node.kind() == COMMENT {
36 tested_by!(introduce_var_in_comment_is_not_applicable); 36 mark::hit!(introduce_var_in_comment_is_not_applicable);
37 return None; 37 return None;
38 } 38 }
39 let expr = node.ancestors().find_map(valid_target_expr)?; 39 let expr = node.ancestors().find_map(valid_target_expr)?;
@@ -42,17 +42,17 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> {
42 if indent.kind() != WHITESPACE { 42 if indent.kind() != WHITESPACE {
43 return None; 43 return None;
44 } 44 }
45 ctx.add_assist(AssistId("introduce_variable"), "Extract into variable", move |edit| { 45 let target = expr.syntax().text_range();
46 acc.add(AssistId("introduce_variable"), "Extract into variable", target, move |edit| {
46 let mut buf = String::new(); 47 let mut buf = String::new();
47 48
48 let cursor_offset = if wrap_in_block { 49 if wrap_in_block {
49 buf.push_str("{ let var_name = "); 50 buf.push_str("{ let var_name = ");
50 TextSize::of("{ let ")
51 } else { 51 } else {
52 buf.push_str("let var_name = "); 52 buf.push_str("let var_name = ");
53 TextSize::of("let ")
54 }; 53 };
55 format_to!(buf, "{}", expr.syntax()); 54 format_to!(buf, "{}", expr.syntax());
55
56 let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); 56 let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone());
57 let is_full_stmt = if let Some(expr_stmt) = &full_stmt { 57 let is_full_stmt = if let Some(expr_stmt) = &full_stmt {
58 Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) 58 Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone())
@@ -60,33 +60,47 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> {
60 false 60 false
61 }; 61 };
62 if is_full_stmt { 62 if is_full_stmt {
63 tested_by!(test_introduce_var_expr_stmt); 63 mark::hit!(test_introduce_var_expr_stmt);
64 if full_stmt.unwrap().semicolon_token().is_none() { 64 if full_stmt.unwrap().semicolon_token().is_none() {
65 buf.push_str(";"); 65 buf.push_str(";");
66 } 66 }
67 edit.replace(expr.syntax().text_range(), buf); 67 let offset = expr.syntax().text_range();
68 } else { 68 match ctx.config.snippet_cap {
69 buf.push_str(";"); 69 Some(cap) => {
70 70 let snip = buf.replace("let var_name", "let $0var_name");
71 // We want to maintain the indent level, 71 edit.replace_snippet(cap, offset, snip)
72 // but we do not want to duplicate possible 72 }
73 // extra newlines in the indent block 73 None => edit.replace(offset, buf),
74 let text = indent.text();
75 if text.starts_with('\n') {
76 buf.push_str("\n");
77 buf.push_str(text.trim_start_matches('\n'));
78 } else {
79 buf.push_str(text);
80 } 74 }
75 return;
76 }
81 77
82 edit.target(expr.syntax().text_range()); 78 buf.push_str(";");
83 edit.replace(expr.syntax().text_range(), "var_name".to_string()); 79
84 edit.insert(anchor_stmt.text_range().start(), buf); 80 // We want to maintain the indent level,
85 if wrap_in_block { 81 // but we do not want to duplicate possible
86 edit.insert(anchor_stmt.text_range().end(), " }"); 82 // extra newlines in the indent block
83 let text = indent.text();
84 if text.starts_with('\n') {
85 buf.push_str("\n");
86 buf.push_str(text.trim_start_matches('\n'));
87 } else {
88 buf.push_str(text);
89 }
90
91 edit.replace(expr.syntax().text_range(), "var_name".to_string());
92 let offset = anchor_stmt.text_range().start();
93 match ctx.config.snippet_cap {
94 Some(cap) => {
95 let snip = buf.replace("let var_name", "let $0var_name");
96 edit.insert_snippet(cap, offset, snip)
87 } 97 }
98 None => edit.insert(offset, buf),
99 }
100
101 if wrap_in_block {
102 edit.insert(anchor_stmt.text_range().end(), " }");
88 } 103 }
89 edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset);
90 }) 104 })
91} 105}
92 106
@@ -111,9 +125,9 @@ fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
111/// expression like a lambda or match arm. 125/// expression like a lambda or match arm.
112fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> { 126fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> {
113 expr.syntax().ancestors().find_map(|node| { 127 expr.syntax().ancestors().find_map(|node| {
114 if let Some(expr) = node.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) { 128 if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) {
115 if expr.syntax() == &node { 129 if expr.syntax() == &node {
116 tested_by!(test_introduce_var_last_expr); 130 mark::hit!(test_introduce_var_last_expr);
117 return Some((node, false)); 131 return Some((node, false));
118 } 132 }
119 } 133 }
@@ -134,9 +148,9 @@ fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> {
134 148
135#[cfg(test)] 149#[cfg(test)]
136mod tests { 150mod tests {
137 use test_utils::covers; 151 use test_utils::mark;
138 152
139 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 153 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
140 154
141 use super::*; 155 use super::*;
142 156
@@ -144,37 +158,37 @@ mod tests {
144 fn test_introduce_var_simple() { 158 fn test_introduce_var_simple() {
145 check_assist( 159 check_assist(
146 introduce_variable, 160 introduce_variable,
147 " 161 r#"
148fn foo() { 162fn foo() {
149 foo(<|>1 + 1<|>); 163 foo(<|>1 + 1<|>);
150}", 164}"#,
151 " 165 r#"
152fn foo() { 166fn foo() {
153 let <|>var_name = 1 + 1; 167 let $0var_name = 1 + 1;
154 foo(var_name); 168 foo(var_name);
155}", 169}"#,
156 ); 170 );
157 } 171 }
158 172
159 #[test] 173 #[test]
160 fn introduce_var_in_comment_is_not_applicable() { 174 fn introduce_var_in_comment_is_not_applicable() {
161 covers!(introduce_var_in_comment_is_not_applicable); 175 mark::check!(introduce_var_in_comment_is_not_applicable);
162 check_assist_not_applicable(introduce_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }"); 176 check_assist_not_applicable(introduce_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }");
163 } 177 }
164 178
165 #[test] 179 #[test]
166 fn test_introduce_var_expr_stmt() { 180 fn test_introduce_var_expr_stmt() {
167 covers!(test_introduce_var_expr_stmt); 181 mark::check!(test_introduce_var_expr_stmt);
168 check_assist( 182 check_assist(
169 introduce_variable, 183 introduce_variable,
170 " 184 r#"
171fn foo() { 185fn foo() {
172 <|>1 + 1<|>; 186 <|>1 + 1<|>;
173}", 187}"#,
174 " 188 r#"
175fn foo() { 189fn foo() {
176 let <|>var_name = 1 + 1; 190 let $0var_name = 1 + 1;
177}", 191}"#,
178 ); 192 );
179 check_assist( 193 check_assist(
180 introduce_variable, 194 introduce_variable,
@@ -185,7 +199,7 @@ fn foo() {
185}", 199}",
186 " 200 "
187fn foo() { 201fn foo() {
188 let <|>var_name = { let x = 0; x }; 202 let $0var_name = { let x = 0; x };
189 something_else(); 203 something_else();
190}", 204}",
191 ); 205 );
@@ -201,7 +215,7 @@ fn foo() {
201}", 215}",
202 " 216 "
203fn foo() { 217fn foo() {
204 let <|>var_name = 1; 218 let $0var_name = 1;
205 var_name + 1; 219 var_name + 1;
206}", 220}",
207 ); 221 );
@@ -209,7 +223,7 @@ fn foo() {
209 223
210 #[test] 224 #[test]
211 fn test_introduce_var_last_expr() { 225 fn test_introduce_var_last_expr() {
212 covers!(test_introduce_var_last_expr); 226 mark::check!(test_introduce_var_last_expr);
213 check_assist( 227 check_assist(
214 introduce_variable, 228 introduce_variable,
215 " 229 "
@@ -218,7 +232,7 @@ fn foo() {
218}", 232}",
219 " 233 "
220fn foo() { 234fn foo() {
221 let <|>var_name = 1 + 1; 235 let $0var_name = 1 + 1;
222 bar(var_name) 236 bar(var_name)
223}", 237}",
224 ); 238 );
@@ -230,7 +244,7 @@ fn foo() {
230}", 244}",
231 " 245 "
232fn foo() { 246fn foo() {
233 let <|>var_name = bar(1 + 1); 247 let $0var_name = bar(1 + 1);
234 var_name 248 var_name
235}", 249}",
236 ) 250 )
@@ -253,7 +267,7 @@ fn main() {
253fn main() { 267fn main() {
254 let x = true; 268 let x = true;
255 let tuple = match x { 269 let tuple = match x {
256 true => { let <|>var_name = 2 + 2; (var_name, true) } 270 true => { let $0var_name = 2 + 2; (var_name, true) }
257 _ => (0, false) 271 _ => (0, false)
258 }; 272 };
259} 273}
@@ -283,7 +297,7 @@ fn main() {
283 let tuple = match x { 297 let tuple = match x {
284 true => { 298 true => {
285 let y = 1; 299 let y = 1;
286 let <|>var_name = 2 + y; 300 let $0var_name = 2 + y;
287 (var_name, true) 301 (var_name, true)
288 } 302 }
289 _ => (0, false) 303 _ => (0, false)
@@ -304,7 +318,7 @@ fn main() {
304", 318",
305 " 319 "
306fn main() { 320fn main() {
307 let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; 321 let lambda = |x: u32| { let $0var_name = x * 2; var_name };
308} 322}
309", 323",
310 ); 324 );
@@ -321,7 +335,7 @@ fn main() {
321", 335",
322 " 336 "
323fn main() { 337fn main() {
324 let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; 338 let lambda = |x: u32| { let $0var_name = x * 2; var_name };
325} 339}
326", 340",
327 ); 341 );
@@ -338,7 +352,7 @@ fn main() {
338", 352",
339 " 353 "
340fn main() { 354fn main() {
341 let <|>var_name = Some(true); 355 let $0var_name = Some(true);
342 let o = var_name; 356 let o = var_name;
343} 357}
344", 358",
@@ -356,7 +370,7 @@ fn main() {
356", 370",
357 " 371 "
358fn main() { 372fn main() {
359 let <|>var_name = bar.foo(); 373 let $0var_name = bar.foo();
360 let v = var_name; 374 let v = var_name;
361} 375}
362", 376",
@@ -374,7 +388,7 @@ fn foo() -> u32 {
374", 388",
375 " 389 "
376fn foo() -> u32 { 390fn foo() -> u32 {
377 let <|>var_name = 2 + 2; 391 let $0var_name = 2 + 2;
378 return var_name; 392 return var_name;
379} 393}
380", 394",
@@ -396,7 +410,7 @@ fn foo() -> u32 {
396fn foo() -> u32 { 410fn foo() -> u32 {
397 411
398 412
399 let <|>var_name = 2 + 2; 413 let $0var_name = 2 + 2;
400 return var_name; 414 return var_name;
401} 415}
402", 416",
@@ -413,7 +427,7 @@ fn foo() -> u32 {
413 " 427 "
414fn foo() -> u32 { 428fn foo() -> u32 {
415 429
416 let <|>var_name = 2 + 2; 430 let $0var_name = 2 + 2;
417 return var_name; 431 return var_name;
418} 432}
419", 433",
@@ -438,7 +452,7 @@ fn foo() -> u32 {
438 // bar 452 // bar
439 453
440 454
441 let <|>var_name = 2 + 2; 455 let $0var_name = 2 + 2;
442 return var_name; 456 return var_name;
443} 457}
444", 458",
@@ -459,7 +473,7 @@ fn main() {
459 " 473 "
460fn main() { 474fn main() {
461 let result = loop { 475 let result = loop {
462 let <|>var_name = 2 + 2; 476 let $0var_name = 2 + 2;
463 break var_name; 477 break var_name;
464 }; 478 };
465} 479}
@@ -478,7 +492,7 @@ fn main() {
478", 492",
479 " 493 "
480fn main() { 494fn main() {
481 let <|>var_name = 0f32 as u32; 495 let $0var_name = 0f32 as u32;
482 let v = var_name; 496 let v = var_name;
483} 497}
484", 498",
diff --git a/crates/ra_assists/src/handlers/invert_if.rs b/crates/ra_assists/src/handlers/invert_if.rs
index 682e08512..59d278eb9 100644
--- a/crates/ra_assists/src/handlers/invert_if.rs
+++ b/crates/ra_assists/src/handlers/invert_if.rs
@@ -3,7 +3,11 @@ use ra_syntax::{
3 T, 3 T,
4}; 4};
5 5
6use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; 6use crate::{
7 assist_context::{AssistContext, Assists},
8 utils::invert_boolean_expression,
9 AssistId,
10};
7 11
8// Assist: invert_if 12// Assist: invert_if
9// 13//
@@ -24,7 +28,7 @@ use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId};
24// } 28// }
25// ``` 29// ```
26 30
27pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> { 31pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let if_keyword = ctx.find_token_at_offset(T![if])?; 32 let if_keyword = ctx.find_token_at_offset(T![if])?;
29 let expr = ast::IfExpr::cast(if_keyword.parent())?; 33 let expr = ast::IfExpr::cast(if_keyword.parent())?;
30 let if_range = if_keyword.text_range(); 34 let if_range = if_keyword.text_range();
@@ -40,36 +44,35 @@ pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> {
40 44
41 let cond = expr.condition()?.expr()?; 45 let cond = expr.condition()?.expr()?;
42 let then_node = expr.then_branch()?.syntax().clone(); 46 let then_node = expr.then_branch()?.syntax().clone();
47 let else_block = match expr.else_branch()? {
48 ast::ElseBranch::Block(it) => it,
49 ast::ElseBranch::IfExpr(_) => return None,
50 };
43 51
44 if let ast::ElseBranch::Block(else_block) = expr.else_branch()? { 52 let cond_range = cond.syntax().text_range();
45 let cond_range = cond.syntax().text_range(); 53 let flip_cond = invert_boolean_expression(cond);
46 let flip_cond = invert_boolean_expression(cond); 54 let else_node = else_block.syntax();
47 let else_node = else_block.syntax(); 55 let else_range = else_node.text_range();
48 let else_range = else_node.text_range(); 56 let then_range = then_node.text_range();
49 let then_range = then_node.text_range(); 57 acc.add(AssistId("invert_if"), "Invert if", if_range, |edit| {
50 return ctx.add_assist(AssistId("invert_if"), "Invert if", |edit| { 58 edit.replace(cond_range, flip_cond.syntax().text());
51 edit.target(if_range); 59 edit.replace(else_range, then_node.text());
52 edit.replace(cond_range, flip_cond.syntax().text()); 60 edit.replace(then_range, else_node.text());
53 edit.replace(else_range, then_node.text()); 61 })
54 edit.replace(then_range, else_node.text());
55 });
56 }
57
58 None
59} 62}
60 63
61#[cfg(test)] 64#[cfg(test)]
62mod tests { 65mod tests {
63 use super::*; 66 use super::*;
64 67
65 use crate::helpers::{check_assist, check_assist_not_applicable}; 68 use crate::tests::{check_assist, check_assist_not_applicable};
66 69
67 #[test] 70 #[test]
68 fn invert_if_remove_inequality() { 71 fn invert_if_remove_inequality() {
69 check_assist( 72 check_assist(
70 invert_if, 73 invert_if,
71 "fn f() { i<|>f x != 3 { 1 } else { 3 + 2 } }", 74 "fn f() { i<|>f x != 3 { 1 } else { 3 + 2 } }",
72 "fn f() { i<|>f x == 3 { 3 + 2 } else { 1 } }", 75 "fn f() { if x == 3 { 3 + 2 } else { 1 } }",
73 ) 76 )
74 } 77 }
75 78
@@ -78,7 +81,7 @@ mod tests {
78 check_assist( 81 check_assist(
79 invert_if, 82 invert_if,
80 "fn f() { <|>if !cond { 3 * 2 } else { 1 } }", 83 "fn f() { <|>if !cond { 3 * 2 } else { 1 } }",
81 "fn f() { <|>if cond { 1 } else { 3 * 2 } }", 84 "fn f() { if cond { 1 } else { 3 * 2 } }",
82 ) 85 )
83 } 86 }
84 87
@@ -87,7 +90,7 @@ mod tests {
87 check_assist( 90 check_assist(
88 invert_if, 91 invert_if,
89 "fn f() { i<|>f cond { 3 * 2 } else { 1 } }", 92 "fn f() { i<|>f cond { 3 * 2 } else { 1 } }",
90 "fn f() { i<|>f !cond { 1 } else { 3 * 2 } }", 93 "fn f() { if !cond { 1 } else { 3 * 2 } }",
91 ) 94 )
92 } 95 }
93 96
diff --git a/crates/ra_assists/src/handlers/merge_imports.rs b/crates/ra_assists/src/handlers/merge_imports.rs
index 4be1238f1..972d16241 100644
--- a/crates/ra_assists/src/handlers/merge_imports.rs
+++ b/crates/ra_assists/src/handlers/merge_imports.rs
@@ -6,7 +6,10 @@ use ra_syntax::{
6 AstNode, Direction, InsertPosition, SyntaxElement, T, 6 AstNode, Direction, InsertPosition, SyntaxElement, T,
7}; 7};
8 8
9use crate::{Assist, AssistCtx, AssistId}; 9use crate::{
10 assist_context::{AssistContext, Assists},
11 AssistId,
12};
10 13
11// Assist: merge_imports 14// Assist: merge_imports
12// 15//
@@ -20,10 +23,10 @@ use crate::{Assist, AssistCtx, AssistId};
20// ``` 23// ```
21// use std::{fmt::Formatter, io}; 24// use std::{fmt::Formatter, io};
22// ``` 25// ```
23pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> { 26pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let tree: ast::UseTree = ctx.find_node_at_offset()?; 27 let tree: ast::UseTree = ctx.find_node_at_offset()?;
25 let mut rewriter = SyntaxRewriter::default(); 28 let mut rewriter = SyntaxRewriter::default();
26 let mut offset = ctx.frange.range.start(); 29 let mut offset = ctx.offset();
27 30
28 if let Some(use_item) = tree.syntax().parent().and_then(ast::UseItem::cast) { 31 if let Some(use_item) = tree.syntax().parent().and_then(ast::UseItem::cast) {
29 let (merged, to_delete) = next_prev() 32 let (merged, to_delete) = next_prev()
@@ -52,10 +55,9 @@ pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> {
52 } 55 }
53 }; 56 };
54 57
55 ctx.add_assist(AssistId("merge_imports"), "Merge imports", |edit| { 58 let target = tree.syntax().text_range();
56 edit.rewrite(rewriter); 59 acc.add(AssistId("merge_imports"), "Merge imports", target, |builder| {
57 // FIXME: we only need because our diff is imprecise 60 builder.rewrite(rewriter);
58 edit.set_cursor(offset);
59 }) 61 })
60} 62}
61 63
@@ -125,7 +127,7 @@ fn first_path(path: &ast::Path) -> ast::Path {
125 127
126#[cfg(test)] 128#[cfg(test)]
127mod tests { 129mod tests {
128 use crate::helpers::check_assist; 130 use crate::tests::check_assist;
129 131
130 use super::*; 132 use super::*;
131 133
@@ -138,7 +140,7 @@ use std::fmt<|>::Debug;
138use std::fmt::Display; 140use std::fmt::Display;
139", 141",
140 r" 142 r"
141use std::fmt<|>::{Debug, Display}; 143use std::fmt::{Debug, Display};
142", 144",
143 ) 145 )
144 } 146 }
@@ -152,7 +154,7 @@ use std::fmt::Debug;
152use std::fmt<|>::Display; 154use std::fmt<|>::Display;
153", 155",
154 r" 156 r"
155use std::fmt:<|>:{Display, Debug}; 157use std::fmt::{Display, Debug};
156", 158",
157 ); 159 );
158 } 160 }
@@ -165,7 +167,7 @@ use std::fmt:<|>:{Display, Debug};
165use std::{fmt<|>::Debug, fmt::Display}; 167use std::{fmt<|>::Debug, fmt::Display};
166", 168",
167 r" 169 r"
168use std::{fmt<|>::{Debug, Display}}; 170use std::{fmt::{Debug, Display}};
169", 171",
170 ); 172 );
171 check_assist( 173 check_assist(
@@ -174,7 +176,7 @@ use std::{fmt<|>::{Debug, Display}};
174use std::{fmt::Debug, fmt<|>::Display}; 176use std::{fmt::Debug, fmt<|>::Display};
175", 177",
176 r" 178 r"
177use std::{fmt::<|>{Display, Debug}}; 179use std::{fmt::{Display, Debug}};
178", 180",
179 ); 181 );
180 } 182 }
@@ -188,7 +190,7 @@ use std<|>::cell::*;
188use std::str; 190use std::str;
189", 191",
190 r" 192 r"
191use std<|>::{cell::*, str}; 193use std::{cell::*, str};
192", 194",
193 ) 195 )
194 } 196 }
@@ -202,7 +204,7 @@ use std<|>::cell::*;
202use std::str::*; 204use std::str::*;
203", 205",
204 r" 206 r"
205use std<|>::{cell::*, str::*}; 207use std::{cell::*, str::*};
206", 208",
207 ) 209 )
208 } 210 }
@@ -218,7 +220,7 @@ use foo::baz;
218/// Doc comment 220/// Doc comment
219", 221",
220 r" 222 r"
221use foo<|>::{bar, baz}; 223use foo::{bar, baz};
222 224
223/// Doc comment 225/// Doc comment
224", 226",
@@ -237,7 +239,7 @@ use {
237", 239",
238 r" 240 r"
239use { 241use {
240 foo<|>::{bar, baz}, 242 foo::{bar, baz},
241}; 243};
242", 244",
243 ); 245 );
@@ -251,7 +253,7 @@ use {
251", 253",
252 r" 254 r"
253use { 255use {
254 foo::{bar<|>, baz}, 256 foo::{bar, baz},
255}; 257};
256", 258",
257 ); 259 );
@@ -268,7 +270,7 @@ use foo::<|>{
268}; 270};
269", 271",
270 r" 272 r"
271use foo::{<|> 273use foo::{
272 FooBar, 274 FooBar,
273bar::baz}; 275bar::baz};
274", 276",
diff --git a/crates/ra_assists/src/handlers/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs
index 5a77d3dbc..ca04ec671 100644
--- a/crates/ra_assists/src/handlers/merge_match_arms.rs
+++ b/crates/ra_assists/src/handlers/merge_match_arms.rs
@@ -3,10 +3,10 @@ use std::iter::successors;
3use ra_syntax::{ 3use ra_syntax::{
4 algo::neighbor, 4 algo::neighbor,
5 ast::{self, AstNode}, 5 ast::{self, AstNode},
6 Direction, TextSize, 6 Direction,
7}; 7};
8 8
9use crate::{Assist, AssistCtx, AssistId, TextRange}; 9use crate::{AssistContext, AssistId, Assists, TextRange};
10 10
11// Assist: merge_match_arms 11// Assist: merge_match_arms
12// 12//
@@ -32,7 +32,7 @@ use crate::{Assist, AssistCtx, AssistId, TextRange};
32// } 32// }
33// } 33// }
34// ``` 34// ```
35pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> { 35pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36 let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?; 36 let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
37 // Don't try to handle arms with guards for now - can add support for this later 37 // Don't try to handle arms with guards for now - can add support for this later
38 if current_arm.guard().is_some() { 38 if current_arm.guard().is_some() {
@@ -41,17 +41,6 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> {
41 let current_expr = current_arm.expr()?; 41 let current_expr = current_arm.expr()?;
42 let current_text_range = current_arm.syntax().text_range(); 42 let current_text_range = current_arm.syntax().text_range();
43 43
44 enum CursorPos {
45 InExpr(TextSize),
46 InPat(TextSize),
47 }
48 let cursor_pos = ctx.frange.range.start();
49 let cursor_pos = if current_expr.syntax().text_range().contains(cursor_pos) {
50 CursorPos::InExpr(current_text_range.end() - cursor_pos)
51 } else {
52 CursorPos::InPat(cursor_pos)
53 };
54
55 // We check if the following match arms match this one. We could, but don't, 44 // We check if the following match arms match this one. We could, but don't,
56 // compare to the previous match arm as well. 45 // compare to the previous match arm as well.
57 let arms_to_merge = successors(Some(current_arm), |it| neighbor(it, Direction::Next)) 46 let arms_to_merge = successors(Some(current_arm), |it| neighbor(it, Direction::Next))
@@ -70,7 +59,7 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> {
70 return None; 59 return None;
71 } 60 }
72 61
73 ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| { 62 acc.add(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| {
74 let pats = if arms_to_merge.iter().any(contains_placeholder) { 63 let pats = if arms_to_merge.iter().any(contains_placeholder) {
75 "_".into() 64 "_".into()
76 } else { 65 } else {
@@ -87,11 +76,6 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> {
87 let start = arms_to_merge.first().unwrap().syntax().text_range().start(); 76 let start = arms_to_merge.first().unwrap().syntax().text_range().start();
88 let end = arms_to_merge.last().unwrap().syntax().text_range().end(); 77 let end = arms_to_merge.last().unwrap().syntax().text_range().end();
89 78
90 edit.target(current_text_range);
91 edit.set_cursor(match cursor_pos {
92 CursorPos::InExpr(back_offset) => start + TextSize::of(&arm) - back_offset,
93 CursorPos::InPat(offset) => offset,
94 });
95 edit.replace(TextRange::new(start, end), arm); 79 edit.replace(TextRange::new(start, end), arm);
96 }) 80 })
97} 81}
@@ -105,7 +89,7 @@ fn contains_placeholder(a: &ast::MatchArm) -> bool {
105 89
106#[cfg(test)] 90#[cfg(test)]
107mod tests { 91mod tests {
108 use crate::helpers::{check_assist, check_assist_not_applicable}; 92 use crate::tests::{check_assist, check_assist_not_applicable};
109 93
110 use super::*; 94 use super::*;
111 95
@@ -133,7 +117,7 @@ mod tests {
133 fn main() { 117 fn main() {
134 let x = X::A; 118 let x = X::A;
135 let y = match x { 119 let y = match x {
136 X::A | X::B => { 1i32<|> } 120 X::A | X::B => { 1i32 }
137 X::C => { 2i32 } 121 X::C => { 2i32 }
138 } 122 }
139 } 123 }
@@ -165,7 +149,7 @@ mod tests {
165 fn main() { 149 fn main() {
166 let x = X::A; 150 let x = X::A;
167 let y = match x { 151 let y = match x {
168 X::A | X::B | X::C | X::D => {<|> 1i32 }, 152 X::A | X::B | X::C | X::D => { 1i32 },
169 X::E => { 2i32 }, 153 X::E => { 2i32 },
170 } 154 }
171 } 155 }
@@ -198,7 +182,7 @@ mod tests {
198 let x = X::A; 182 let x = X::A;
199 let y = match x { 183 let y = match x {
200 X::A => { 1i32 }, 184 X::A => { 1i32 },
201 _ => { 2i<|>32 } 185 _ => { 2i32 }
202 } 186 }
203 } 187 }
204 "#, 188 "#,
@@ -227,7 +211,7 @@ mod tests {
227 211
228 fn main() { 212 fn main() {
229 match X::A { 213 match X::A {
230 X::A<|> | X::B | X::C => 92, 214 X::A | X::B | X::C => 92,
231 X::D => 62, 215 X::D => 62,
232 _ => panic!(), 216 _ => panic!(),
233 } 217 }
diff --git a/crates/ra_assists/src/handlers/move_bounds.rs b/crates/ra_assists/src/handlers/move_bounds.rs
index 0f26884dc..be2a7eddc 100644
--- a/crates/ra_assists/src/handlers/move_bounds.rs
+++ b/crates/ra_assists/src/handlers/move_bounds.rs
@@ -5,7 +5,7 @@ use ra_syntax::{
5 T, 5 T,
6}; 6};
7 7
8use crate::{Assist, AssistCtx, AssistId}; 8use crate::{AssistContext, AssistId, Assists};
9 9
10// Assist: move_bounds_to_where_clause 10// Assist: move_bounds_to_where_clause
11// 11//
@@ -22,7 +22,7 @@ use crate::{Assist, AssistCtx, AssistId};
22// f(x) 22// f(x)
23// } 23// }
24// ``` 24// ```
25pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> { 25pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?; 26 let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?;
27 27
28 let mut type_params = type_param_list.type_params(); 28 let mut type_params = type_param_list.type_params();
@@ -49,7 +49,8 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> {
49 } 49 }
50 }; 50 };
51 51
52 ctx.add_assist(AssistId("move_bounds_to_where_clause"), "Move to where clause", |edit| { 52 let target = type_param_list.syntax().text_range();
53 acc.add(AssistId("move_bounds_to_where_clause"), "Move to where clause", target, |edit| {
53 let new_params = type_param_list 54 let new_params = type_param_list
54 .type_params() 55 .type_params()
55 .filter(|it| it.type_bound_list().is_some()) 56 .filter(|it| it.type_bound_list().is_some())
@@ -71,7 +72,6 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> {
71 _ => format!(" {}", where_clause.syntax()), 72 _ => format!(" {}", where_clause.syntax()),
72 }; 73 };
73 edit.insert(anchor.text_range().start(), to_insert); 74 edit.insert(anchor.text_range().start(), to_insert);
74 edit.target(type_param_list.syntax().text_range());
75 }) 75 })
76} 76}
77 77
@@ -89,7 +89,7 @@ fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {
89mod tests { 89mod tests {
90 use super::*; 90 use super::*;
91 91
92 use crate::helpers::check_assist; 92 use crate::tests::check_assist;
93 93
94 #[test] 94 #[test]
95 fn move_bounds_to_where_clause_fn() { 95 fn move_bounds_to_where_clause_fn() {
@@ -99,7 +99,7 @@ mod tests {
99 fn foo<T: u32, <|>F: FnOnce(T) -> T>() {} 99 fn foo<T: u32, <|>F: FnOnce(T) -> T>() {}
100 "#, 100 "#,
101 r#" 101 r#"
102 fn foo<T, <|>F>() where T: u32, F: FnOnce(T) -> T {} 102 fn foo<T, F>() where T: u32, F: FnOnce(T) -> T {}
103 "#, 103 "#,
104 ); 104 );
105 } 105 }
@@ -112,7 +112,7 @@ mod tests {
112 impl<U: u32, <|>T> A<U, T> {} 112 impl<U: u32, <|>T> A<U, T> {}
113 "#, 113 "#,
114 r#" 114 r#"
115 impl<U, <|>T> A<U, T> where U: u32 {} 115 impl<U, T> A<U, T> where U: u32 {}
116 "#, 116 "#,
117 ); 117 );
118 } 118 }
@@ -125,7 +125,7 @@ mod tests {
125 struct A<<|>T: Iterator<Item = u32>> {} 125 struct A<<|>T: Iterator<Item = u32>> {}
126 "#, 126 "#,
127 r#" 127 r#"
128 struct A<<|>T> where T: Iterator<Item = u32> {} 128 struct A<T> where T: Iterator<Item = u32> {}
129 "#, 129 "#,
130 ); 130 );
131 } 131 }
@@ -138,7 +138,7 @@ mod tests {
138 struct Pair<<|>T: u32>(T, T); 138 struct Pair<<|>T: u32>(T, T);
139 "#, 139 "#,
140 r#" 140 r#"
141 struct Pair<<|>T>(T, T) where T: u32; 141 struct Pair<T>(T, T) where T: u32;
142 "#, 142 "#,
143 ); 143 );
144 } 144 }
diff --git a/crates/ra_assists/src/handlers/move_guard.rs b/crates/ra_assists/src/handlers/move_guard.rs
index d5ccdd91c..7edcf0748 100644
--- a/crates/ra_assists/src/handlers/move_guard.rs
+++ b/crates/ra_assists/src/handlers/move_guard.rs
@@ -1,10 +1,9 @@
1use ra_syntax::{ 1use ra_syntax::{
2 ast, 2 ast::{AstNode, IfExpr, MatchArm},
3 ast::{AstNode, AstToken, IfExpr, MatchArm}, 3 SyntaxKind::WHITESPACE,
4 TextSize,
5}; 4};
6 5
7use crate::{Assist, AssistCtx, AssistId}; 6use crate::{AssistContext, AssistId, Assists};
8 7
9// Assist: move_guard_to_arm_body 8// Assist: move_guard_to_arm_body
10// 9//
@@ -31,7 +30,7 @@ use crate::{Assist, AssistCtx, AssistId};
31// } 30// }
32// } 31// }
33// ``` 32// ```
34pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> { 33pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
35 let match_arm = ctx.find_node_at_offset::<MatchArm>()?; 34 let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
36 let guard = match_arm.guard()?; 35 let guard = match_arm.guard()?;
37 let space_before_guard = guard.syntax().prev_sibling_or_token(); 36 let space_before_guard = guard.syntax().prev_sibling_or_token();
@@ -40,26 +39,17 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> {
40 let arm_expr = match_arm.expr()?; 39 let arm_expr = match_arm.expr()?;
41 let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); 40 let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text());
42 41
43 ctx.add_assist(AssistId("move_guard_to_arm_body"), "Move guard to arm body", |edit| { 42 let target = guard.syntax().text_range();
44 edit.target(guard.syntax().text_range()); 43 acc.add(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| {
45 let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { 44 match space_before_guard {
46 Some(tok) => { 45 Some(element) if element.kind() == WHITESPACE => {
47 if ast::Whitespace::cast(tok.clone()).is_some() { 46 edit.delete(element.text_range());
48 let ele = tok.text_range();
49 edit.delete(ele);
50 ele.len()
51 } else {
52 TextSize::from(0)
53 }
54 } 47 }
55 _ => TextSize::from(0), 48 _ => (),
56 }; 49 };
57 50
58 edit.delete(guard.syntax().text_range()); 51 edit.delete(guard.syntax().text_range());
59 edit.replace_node_and_indent(arm_expr.syntax(), buf); 52 edit.replace_node_and_indent(arm_expr.syntax(), buf);
60 edit.set_cursor(
61 arm_expr.syntax().text_range().start() + TextSize::from(3) - offseting_amount,
62 );
63 }) 53 })
64} 54}
65 55
@@ -88,7 +78,7 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> {
88// } 78// }
89// } 79// }
90// ``` 80// ```
91pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> { 81pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
92 let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?; 82 let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
93 let match_pat = match_arm.pat()?; 83 let match_pat = match_arm.pat()?;
94 84
@@ -108,14 +98,15 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> {
108 98
109 let buf = format!(" if {}", cond.syntax().text()); 99 let buf = format!(" if {}", cond.syntax().text());
110 100
111 ctx.add_assist( 101 let target = if_expr.syntax().text_range();
102 acc.add(
112 AssistId("move_arm_cond_to_match_guard"), 103 AssistId("move_arm_cond_to_match_guard"),
113 "Move condition to match guard", 104 "Move condition to match guard",
105 target,
114 |edit| { 106 |edit| {
115 edit.target(if_expr.syntax().text_range()); 107 let then_only_expr = then_block.statements().next().is_none();
116 let then_only_expr = then_block.block().and_then(|it| it.statements().next()).is_none();
117 108
118 match &then_block.block().and_then(|it| it.expr()) { 109 match &then_block.expr() {
119 Some(then_expr) if then_only_expr => { 110 Some(then_expr) if then_only_expr => {
120 edit.replace(if_expr.syntax().text_range(), then_expr.syntax().text()) 111 edit.replace(if_expr.syntax().text_range(), then_expr.syntax().text())
121 } 112 }
@@ -123,7 +114,6 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> {
123 } 114 }
124 115
125 edit.insert(match_pat.syntax().text_range().end(), buf); 116 edit.insert(match_pat.syntax().text_range().end(), buf);
126 edit.set_cursor(match_pat.syntax().text_range().end() + TextSize::from(1));
127 }, 117 },
128 ) 118 )
129} 119}
@@ -132,7 +122,7 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> {
132mod tests { 122mod tests {
133 use super::*; 123 use super::*;
134 124
135 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 125 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
136 126
137 #[test] 127 #[test]
138 fn move_guard_to_arm_body_target() { 128 fn move_guard_to_arm_body_target() {
@@ -171,7 +161,7 @@ mod tests {
171 let t = 'a'; 161 let t = 'a';
172 let chars = "abcd"; 162 let chars = "abcd";
173 match t { 163 match t {
174 '\r' => if chars.clone().next() == Some('\n') { <|>false }, 164 '\r' => if chars.clone().next() == Some('\n') { false },
175 _ => true 165 _ => true
176 } 166 }
177 } 167 }
@@ -194,7 +184,7 @@ mod tests {
194 r#" 184 r#"
195 fn f() { 185 fn f() {
196 match x { 186 match x {
197 y @ 4 | y @ 5 => if y > 5 { <|>true }, 187 y @ 4 | y @ 5 => if y > 5 { true },
198 _ => false 188 _ => false
199 } 189 }
200 } 190 }
@@ -221,7 +211,7 @@ mod tests {
221 let t = 'a'; 211 let t = 'a';
222 let chars = "abcd"; 212 let chars = "abcd";
223 match t { 213 match t {
224 '\r' <|>if chars.clone().next() == Some('\n') => false, 214 '\r' if chars.clone().next() == Some('\n') => false,
225 _ => true 215 _ => true
226 } 216 }
227 } 217 }
@@ -265,7 +255,7 @@ mod tests {
265 let t = 'a'; 255 let t = 'a';
266 let chars = "abcd"; 256 let chars = "abcd";
267 match t { 257 match t {
268 '\r' <|>if chars.clone().next().is_some() => { }, 258 '\r' if chars.clone().next().is_some() => { },
269 _ => true 259 _ => true
270 } 260 }
271 } 261 }
@@ -295,7 +285,7 @@ mod tests {
295 let mut t = 'a'; 285 let mut t = 'a';
296 let chars = "abcd"; 286 let chars = "abcd";
297 match t { 287 match t {
298 '\r' <|>if chars.clone().next().is_some() => { 288 '\r' if chars.clone().next().is_some() => {
299 t = 'e'; 289 t = 'e';
300 false 290 false
301 }, 291 },
diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs
index 567400b9c..16002d2ac 100644
--- a/crates/ra_assists/src/handlers/raw_string.rs
+++ b/crates/ra_assists/src/handlers/raw_string.rs
@@ -5,7 +5,7 @@ use ra_syntax::{
5 TextSize, 5 TextSize,
6}; 6};
7 7
8use crate::{Assist, AssistCtx, AssistId}; 8use crate::{AssistContext, AssistId, Assists};
9 9
10// Assist: make_raw_string 10// Assist: make_raw_string
11// 11//
@@ -22,11 +22,11 @@ use crate::{Assist, AssistCtx, AssistId};
22// r#"Hello, World!"#; 22// r#"Hello, World!"#;
23// } 23// }
24// ``` 24// ```
25pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> { 25pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; 26 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
27 let value = token.value()?; 27 let value = token.value()?;
28 ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", |edit| { 28 let target = token.syntax().text_range();
29 edit.target(token.syntax().text_range()); 29 acc.add(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| {
30 let max_hash_streak = count_hashes(&value); 30 let max_hash_streak = count_hashes(&value);
31 let mut hashes = String::with_capacity(max_hash_streak + 1); 31 let mut hashes = String::with_capacity(max_hash_streak + 1);
32 for _ in 0..hashes.capacity() { 32 for _ in 0..hashes.capacity() {
@@ -51,11 +51,11 @@ pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> {
51// "Hello, \"World!\""; 51// "Hello, \"World!\"";
52// } 52// }
53// ``` 53// ```
54pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> { 54pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
55 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; 55 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
56 let value = token.value()?; 56 let value = token.value()?;
57 ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", |edit| { 57 let target = token.syntax().text_range();
58 edit.target(token.syntax().text_range()); 58 acc.add(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| {
59 // parse inside string to escape `"` 59 // parse inside string to escape `"`
60 let escaped = value.escape_default().to_string(); 60 let escaped = value.escape_default().to_string();
61 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); 61 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
@@ -77,10 +77,10 @@ pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
77// r##"Hello, World!"##; 77// r##"Hello, World!"##;
78// } 78// }
79// ``` 79// ```
80pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> { 80pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
81 let token = ctx.find_token_at_offset(RAW_STRING)?; 81 let token = ctx.find_token_at_offset(RAW_STRING)?;
82 ctx.add_assist(AssistId("add_hash"), "Add # to raw string", |edit| { 82 let target = token.text_range();
83 edit.target(token.text_range()); 83 acc.add(AssistId("add_hash"), "Add # to raw string", target, |edit| {
84 edit.insert(token.text_range().start() + TextSize::of('r'), "#"); 84 edit.insert(token.text_range().start() + TextSize::of('r'), "#");
85 edit.insert(token.text_range().end(), "#"); 85 edit.insert(token.text_range().end(), "#");
86 }) 86 })
@@ -101,15 +101,15 @@ pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> {
101// r"Hello, World!"; 101// r"Hello, World!";
102// } 102// }
103// ``` 103// ```
104pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> { 104pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
105 let token = ctx.find_token_at_offset(RAW_STRING)?; 105 let token = ctx.find_token_at_offset(RAW_STRING)?;
106 let text = token.text().as_str(); 106 let text = token.text().as_str();
107 if text.starts_with("r\"") { 107 if text.starts_with("r\"") {
108 // no hash to remove 108 // no hash to remove
109 return None; 109 return None;
110 } 110 }
111 ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", |edit| { 111 let target = token.text_range();
112 edit.target(token.text_range()); 112 acc.add(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| {
113 let result = &text[2..text.len() - 1]; 113 let result = &text[2..text.len() - 1];
114 let result = if result.starts_with('\"') { 114 let result = if result.starts_with('\"') {
115 // FIXME: this logic is wrong, not only the last has has to handled specially 115 // FIXME: this logic is wrong, not only the last has has to handled specially
@@ -138,7 +138,7 @@ fn count_hashes(s: &str) -> usize {
138#[cfg(test)] 138#[cfg(test)]
139mod test { 139mod test {
140 use super::*; 140 use super::*;
141 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 141 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
142 142
143 #[test] 143 #[test]
144 fn make_raw_string_target() { 144 fn make_raw_string_target() {
@@ -164,7 +164,7 @@ mod test {
164 "#, 164 "#,
165 r##" 165 r##"
166 fn f() { 166 fn f() {
167 let s = <|>r#"random 167 let s = r#"random
168string"#; 168string"#;
169 } 169 }
170 "##, 170 "##,
@@ -182,7 +182,7 @@ string"#;
182 "#, 182 "#,
183 r##" 183 r##"
184 fn f() { 184 fn f() {
185 format!(<|>r#"x = {}"#, 92) 185 format!(r#"x = {}"#, 92)
186 } 186 }
187 "##, 187 "##,
188 ) 188 )
@@ -199,7 +199,7 @@ string"#;
199 "###, 199 "###,
200 r####" 200 r####"
201 fn f() { 201 fn f() {
202 let s = <|>r#"#random## 202 let s = r#"#random##
203string"#; 203string"#;
204 } 204 }
205 "####, 205 "####,
@@ -217,7 +217,7 @@ string"#;
217 "###, 217 "###,
218 r####" 218 r####"
219 fn f() { 219 fn f() {
220 let s = <|>r###"#random"## 220 let s = r###"#random"##
221string"###; 221string"###;
222 } 222 }
223 "####, 223 "####,
@@ -235,7 +235,7 @@ string"###;
235 "#, 235 "#,
236 r##" 236 r##"
237 fn f() { 237 fn f() {
238 let s = <|>r#"random string"#; 238 let s = r#"random string"#;
239 } 239 }
240 "##, 240 "##,
241 ) 241 )
@@ -289,7 +289,7 @@ string"###;
289 "#, 289 "#,
290 r##" 290 r##"
291 fn f() { 291 fn f() {
292 let s = <|>r#"random string"#; 292 let s = r#"random string"#;
293 } 293 }
294 "##, 294 "##,
295 ) 295 )
@@ -306,7 +306,7 @@ string"###;
306 "##, 306 "##,
307 r###" 307 r###"
308 fn f() { 308 fn f() {
309 let s = <|>r##"random"string"##; 309 let s = r##"random"string"##;
310 } 310 }
311 "###, 311 "###,
312 ) 312 )
@@ -348,7 +348,7 @@ string"###;
348 "##, 348 "##,
349 r#" 349 r#"
350 fn f() { 350 fn f() {
351 let s = <|>r"random string"; 351 let s = r"random string";
352 } 352 }
353 "#, 353 "#,
354 ) 354 )
@@ -365,7 +365,7 @@ string"###;
365 "##, 365 "##,
366 r#" 366 r#"
367 fn f() { 367 fn f() {
368 let s = <|>r"random\"str\"ing"; 368 let s = r"random\"str\"ing";
369 } 369 }
370 "#, 370 "#,
371 ) 371 )
@@ -382,7 +382,7 @@ string"###;
382 "###, 382 "###,
383 r##" 383 r##"
384 fn f() { 384 fn f() {
385 let s = <|>r#"random string"#; 385 let s = r#"random string"#;
386 } 386 }
387 "##, 387 "##,
388 ) 388 )
@@ -436,7 +436,7 @@ string"###;
436 "##, 436 "##,
437 r#" 437 r#"
438 fn f() { 438 fn f() {
439 let s = <|>"random string"; 439 let s = "random string";
440 } 440 }
441 "#, 441 "#,
442 ) 442 )
@@ -453,7 +453,7 @@ string"###;
453 "##, 453 "##,
454 r#" 454 r#"
455 fn f() { 455 fn f() {
456 let s = <|>"random\"str\"ing"; 456 let s = "random\"str\"ing";
457 } 457 }
458 "#, 458 "#,
459 ) 459 )
@@ -470,7 +470,7 @@ string"###;
470 "###, 470 "###,
471 r##" 471 r##"
472 fn f() { 472 fn f() {
473 let s = <|>"random string"; 473 let s = "random string";
474 } 474 }
475 "##, 475 "##,
476 ) 476 )
diff --git a/crates/ra_assists/src/handlers/remove_dbg.rs b/crates/ra_assists/src/handlers/remove_dbg.rs
index 4e5eb4350..961ee1731 100644
--- a/crates/ra_assists/src/handlers/remove_dbg.rs
+++ b/crates/ra_assists/src/handlers/remove_dbg.rs
@@ -3,7 +3,7 @@ use ra_syntax::{
3 TextSize, T, 3 TextSize, T,
4}; 4};
5 5
6use crate::{Assist, AssistCtx, AssistId}; 6use crate::{AssistContext, AssistId, Assists};
7 7
8// Assist: remove_dbg 8// Assist: remove_dbg
9// 9//
@@ -20,7 +20,7 @@ use crate::{Assist, AssistCtx, AssistId};
20// 92; 20// 92;
21// } 21// }
22// ``` 22// ```
23pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> { 23pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; 24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
25 25
26 if !is_valid_macrocall(&macro_call, "dbg")? { 26 if !is_valid_macrocall(&macro_call, "dbg")? {
@@ -29,26 +29,6 @@ pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> {
29 29
30 let macro_range = macro_call.syntax().text_range(); 30 let macro_range = macro_call.syntax().text_range();
31 31
32 // If the cursor is inside the macro call, we'll try to maintain the cursor
33 // position by subtracting the length of dbg!( from the start of the file
34 // range, otherwise we'll default to using the start of the macro call
35 let cursor_pos = {
36 let file_range = ctx.frange.range;
37
38 let offset_start = file_range
39 .start()
40 .checked_sub(macro_range.start())
41 .unwrap_or_else(|| TextSize::from(0));
42
43 let dbg_size = TextSize::of("dbg!(");
44
45 if offset_start > dbg_size {
46 file_range.start() - dbg_size
47 } else {
48 macro_range.start()
49 }
50 };
51
52 let macro_content = { 32 let macro_content = {
53 let macro_args = macro_call.token_tree()?.syntax().clone(); 33 let macro_args = macro_call.token_tree()?.syntax().clone();
54 34
@@ -57,10 +37,9 @@ pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> {
57 text.slice(without_parens).to_string() 37 text.slice(without_parens).to_string()
58 }; 38 };
59 39
60 ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", |edit| { 40 let target = macro_call.syntax().text_range();
61 edit.target(macro_call.syntax().text_range()); 41 acc.add(AssistId("remove_dbg"), "Remove dbg!()", target, |builder| {
62 edit.replace(macro_range, macro_content); 42 builder.replace(macro_range, macro_content);
63 edit.set_cursor(cursor_pos);
64 }) 43 })
65} 44}
66 45
@@ -90,17 +69,17 @@ fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<b
90#[cfg(test)] 69#[cfg(test)]
91mod tests { 70mod tests {
92 use super::*; 71 use super::*;
93 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 72 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
94 73
95 #[test] 74 #[test]
96 fn test_remove_dbg() { 75 fn test_remove_dbg() {
97 check_assist(remove_dbg, "<|>dbg!(1 + 1)", "<|>1 + 1"); 76 check_assist(remove_dbg, "<|>dbg!(1 + 1)", "1 + 1");
98 77
99 check_assist(remove_dbg, "dbg!<|>((1 + 1))", "<|>(1 + 1)"); 78 check_assist(remove_dbg, "dbg!<|>((1 + 1))", "(1 + 1)");
100 79
101 check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 <|>+ 1"); 80 check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 + 1");
102 81
103 check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = <|>1 + 1"); 82 check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = 1 + 1");
104 83
105 check_assist( 84 check_assist(
106 remove_dbg, 85 remove_dbg,
@@ -113,7 +92,7 @@ fn foo(n: usize) {
113", 92",
114 " 93 "
115fn foo(n: usize) { 94fn foo(n: usize) {
116 if let Some(_) = n.<|>checked_sub(4) { 95 if let Some(_) = n.checked_sub(4) {
117 // ... 96 // ...
118 } 97 }
119} 98}
@@ -122,8 +101,8 @@ fn foo(n: usize) {
122 } 101 }
123 #[test] 102 #[test]
124 fn test_remove_dbg_with_brackets_and_braces() { 103 fn test_remove_dbg_with_brackets_and_braces() {
125 check_assist(remove_dbg, "dbg![<|>1 + 1]", "<|>1 + 1"); 104 check_assist(remove_dbg, "dbg![<|>1 + 1]", "1 + 1");
126 check_assist(remove_dbg, "dbg!{<|>1 + 1}", "<|>1 + 1"); 105 check_assist(remove_dbg, "dbg!{<|>1 + 1}", "1 + 1");
127 } 106 }
128 107
129 #[test] 108 #[test]
diff --git a/crates/ra_assists/src/handlers/remove_mut.rs b/crates/ra_assists/src/handlers/remove_mut.rs
index e598023b2..fe4eada03 100644
--- a/crates/ra_assists/src/handlers/remove_mut.rs
+++ b/crates/ra_assists/src/handlers/remove_mut.rs
@@ -1,6 +1,6 @@
1use ra_syntax::{SyntaxKind, TextRange, T}; 1use ra_syntax::{SyntaxKind, TextRange, T};
2 2
3use crate::{Assist, AssistCtx, AssistId}; 3use crate::{AssistContext, AssistId, Assists};
4 4
5// Assist: remove_mut 5// Assist: remove_mut
6// 6//
@@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId};
17// fn feed(&self, amount: u32) {} 17// fn feed(&self, amount: u32) {}
18// } 18// }
19// ``` 19// ```
20pub(crate) fn remove_mut(ctx: AssistCtx) -> Option<Assist> { 20pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let mut_token = ctx.find_token_at_offset(T![mut])?; 21 let mut_token = ctx.find_token_at_offset(T![mut])?;
22 let delete_from = mut_token.text_range().start(); 22 let delete_from = mut_token.text_range().start();
23 let delete_to = match mut_token.next_token() { 23 let delete_to = match mut_token.next_token() {
@@ -25,8 +25,8 @@ pub(crate) fn remove_mut(ctx: AssistCtx) -> Option<Assist> {
25 _ => mut_token.text_range().end(), 25 _ => mut_token.text_range().end(),
26 }; 26 };
27 27
28 ctx.add_assist(AssistId("remove_mut"), "Remove `mut` keyword", |edit| { 28 let target = mut_token.text_range();
29 edit.set_cursor(delete_from); 29 acc.add(AssistId("remove_mut"), "Remove `mut` keyword", target, |builder| {
30 edit.delete(TextRange::new(delete_from, delete_to)); 30 builder.delete(TextRange::new(delete_from, delete_to));
31 }) 31 })
32} 32}
diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs
index 5cbb98d73..30229edc2 100644
--- a/crates/ra_assists/src/handlers/reorder_fields.rs
+++ b/crates/ra_assists/src/handlers/reorder_fields.rs
@@ -3,18 +3,9 @@ use std::collections::HashMap;
3use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct}; 3use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
4use itertools::Itertools; 4use itertools::Itertools;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode};
7 algo, 7
8 ast::{self, Path, RecordLit, RecordPat}, 8use crate::{AssistContext, AssistId, Assists};
9 match_ast, AstNode, SyntaxKind,
10 SyntaxKind::*,
11 SyntaxNode,
12};
13
14use crate::{
15 assist_ctx::{Assist, AssistCtx},
16 AssistId,
17};
18 9
19// Assist: reorder_fields 10// Assist: reorder_fields
20// 11//
@@ -31,13 +22,13 @@ use crate::{
31// const test: Foo = Foo {foo: 1, bar: 0} 22// const test: Foo = Foo {foo: 1, bar: 0}
32// ``` 23// ```
33// 24//
34pub(crate) fn reorder_fields(ctx: AssistCtx) -> Option<Assist> { 25pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
35 reorder::<RecordLit>(ctx.clone()).or_else(|| reorder::<RecordPat>(ctx)) 26 reorder::<ast::RecordLit>(acc, ctx.clone()).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
36} 27}
37 28
38fn reorder<R: AstNode>(ctx: AssistCtx) -> Option<Assist> { 29fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39 let record = ctx.find_node_at_offset::<R>()?; 30 let record = ctx.find_node_at_offset::<R>()?;
40 let path = record.syntax().children().find_map(Path::cast)?; 31 let path = record.syntax().children().find_map(ast::Path::cast)?;
41 32
42 let ranks = compute_fields_ranks(&path, &ctx)?; 33 let ranks = compute_fields_ranks(&path, &ctx)?;
43 34
@@ -50,11 +41,11 @@ fn reorder<R: AstNode>(ctx: AssistCtx) -> Option<Assist> {
50 return None; 41 return None;
51 } 42 }
52 43
53 ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", |edit| { 44 let target = record.syntax().text_range();
45 acc.add(AssistId("reorder_fields"), "Reorder record fields", target, |edit| {
54 for (old, new) in fields.iter().zip(&sorted_fields) { 46 for (old, new) in fields.iter().zip(&sorted_fields) {
55 algo::diff(old, new).into_text_edit(edit.text_edit_builder()); 47 algo::diff(old, new).into_text_edit(edit.text_edit_builder());
56 } 48 }
57 edit.target(record.syntax().text_range())
58 }) 49 })
59} 50}
60 51
@@ -96,9 +87,9 @@ fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option
96 } 87 }
97} 88}
98 89
99fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> Option<HashMap<String, usize>> { 90fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<HashMap<String, usize>> {
100 Some( 91 Some(
101 struct_definition(path, ctx.sema)? 92 struct_definition(path, &ctx.sema)?
102 .fields(ctx.db) 93 .fields(ctx.db)
103 .iter() 94 .iter()
104 .enumerate() 95 .enumerate()
@@ -109,7 +100,7 @@ fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> Option<HashMap<String,
109 100
110#[cfg(test)] 101#[cfg(test)]
111mod tests { 102mod tests {
112 use crate::helpers::{check_assist, check_assist_not_applicable}; 103 use crate::tests::{check_assist, check_assist_not_applicable};
113 104
114 use super::*; 105 use super::*;
115 106
@@ -149,7 +140,7 @@ mod tests {
149 "#, 140 "#,
150 r#" 141 r#"
151 struct Foo {foo: i32, bar: i32}; 142 struct Foo {foo: i32, bar: i32};
152 const test: Foo = <|>Foo {foo: 1, bar: 0} 143 const test: Foo = Foo {foo: 1, bar: 0}
153 "#, 144 "#,
154 ) 145 )
155 } 146 }
@@ -173,7 +164,7 @@ mod tests {
173 164
174 fn f(f: Foo) -> { 165 fn f(f: Foo) -> {
175 match f { 166 match f {
176 <|>Foo { ref mut bar, baz: 0, .. } => (), 167 Foo { ref mut bar, baz: 0, .. } => (),
177 _ => () 168 _ => ()
178 } 169 }
179 } 170 }
@@ -211,7 +202,7 @@ mod tests {
211 impl Foo { 202 impl Foo {
212 fn new() -> Foo { 203 fn new() -> Foo {
213 let foo = String::new(); 204 let foo = String::new();
214 <|>Foo { 205 Foo {
215 foo, 206 foo,
216 bar: foo.clone(), 207 bar: foo.clone(),
217 extra: "Extra field", 208 extra: "Extra field",
diff --git a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
index 9841f6980..e016f51c3 100644
--- a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
@@ -1,10 +1,14 @@
1use ra_fmt::unwrap_trivial_block; 1use ra_fmt::unwrap_trivial_block;
2use ra_syntax::{ 2use ra_syntax::{
3 ast::{self, edit::IndentLevel, make}, 3 ast::{
4 self,
5 edit::{AstNodeEdit, IndentLevel},
6 make,
7 },
4 AstNode, 8 AstNode,
5}; 9};
6 10
7use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; 11use crate::{utils::TryEnum, AssistContext, AssistId, Assists};
8 12
9// Assist: replace_if_let_with_match 13// Assist: replace_if_let_with_match
10// 14//
@@ -32,7 +36,7 @@ use crate::{utils::TryEnum, Assist, AssistCtx, AssistId};
32// } 36// }
33// } 37// }
34// ``` 38// ```
35pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> { 39pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; 40 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
37 let cond = if_expr.condition()?; 41 let cond = if_expr.condition()?;
38 let pat = cond.pat()?; 42 let pat = cond.pat()?;
@@ -43,29 +47,27 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
43 ast::ElseBranch::IfExpr(_) => return None, 47 ast::ElseBranch::IfExpr(_) => return None,
44 }; 48 };
45 49
46 let sema = ctx.sema; 50 let target = if_expr.syntax().text_range();
47 ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", move |edit| { 51 acc.add(AssistId("replace_if_let_with_match"), "Replace with match", target, move |edit| {
48 let match_expr = { 52 let match_expr = {
49 let then_arm = { 53 let then_arm = {
50 let then_expr = unwrap_trivial_block(then_block); 54 let then_expr = unwrap_trivial_block(then_block);
51 make::match_arm(vec![pat.clone()], then_expr) 55 make::match_arm(vec![pat.clone()], then_expr)
52 }; 56 };
53 let else_arm = { 57 let else_arm = {
54 let pattern = sema 58 let pattern = ctx
59 .sema
55 .type_of_pat(&pat) 60 .type_of_pat(&pat)
56 .and_then(|ty| TryEnum::from_ty(sema, &ty)) 61 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
57 .map(|it| it.sad_pattern()) 62 .map(|it| it.sad_pattern())
58 .unwrap_or_else(|| make::placeholder_pat().into()); 63 .unwrap_or_else(|| make::placeholder_pat().into());
59 let else_expr = unwrap_trivial_block(else_block); 64 let else_expr = unwrap_trivial_block(else_block);
60 make::match_arm(vec![pattern], else_expr) 65 make::match_arm(vec![pattern], else_expr)
61 }; 66 };
62 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])) 67 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
68 .indent(IndentLevel::from_node(if_expr.syntax()))
63 }; 69 };
64 70
65 let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr);
66
67 edit.target(if_expr.syntax().text_range());
68 edit.set_cursor(if_expr.syntax().text_range().start());
69 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr); 71 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
70 }) 72 })
71} 73}
@@ -74,13 +76,13 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
74mod tests { 76mod tests {
75 use super::*; 77 use super::*;
76 78
77 use crate::helpers::{check_assist, check_assist_target}; 79 use crate::tests::{check_assist, check_assist_target};
78 80
79 #[test] 81 #[test]
80 fn test_replace_if_let_with_match_unwraps_simple_expressions() { 82 fn test_replace_if_let_with_match_unwraps_simple_expressions() {
81 check_assist( 83 check_assist(
82 replace_if_let_with_match, 84 replace_if_let_with_match,
83 " 85 r#"
84impl VariantData { 86impl VariantData {
85 pub fn is_struct(&self) -> bool { 87 pub fn is_struct(&self) -> bool {
86 if <|>let VariantData::Struct(..) = *self { 88 if <|>let VariantData::Struct(..) = *self {
@@ -89,16 +91,16 @@ impl VariantData {
89 false 91 false
90 } 92 }
91 } 93 }
92} ", 94} "#,
93 " 95 r#"
94impl VariantData { 96impl VariantData {
95 pub fn is_struct(&self) -> bool { 97 pub fn is_struct(&self) -> bool {
96 <|>match *self { 98 match *self {
97 VariantData::Struct(..) => true, 99 VariantData::Struct(..) => true,
98 _ => false, 100 _ => false,
99 } 101 }
100 } 102 }
101} ", 103} "#,
102 ) 104 )
103 } 105 }
104 106
@@ -106,7 +108,7 @@ impl VariantData {
106 fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() { 108 fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() {
107 check_assist( 109 check_assist(
108 replace_if_let_with_match, 110 replace_if_let_with_match,
109 " 111 r#"
110fn foo() { 112fn foo() {
111 if <|>let VariantData::Struct(..) = a { 113 if <|>let VariantData::Struct(..) = a {
112 bar( 114 bar(
@@ -115,10 +117,10 @@ fn foo() {
115 } else { 117 } else {
116 false 118 false
117 } 119 }
118} ", 120} "#,
119 " 121 r#"
120fn foo() { 122fn foo() {
121 <|>match a { 123 match a {
122 VariantData::Struct(..) => { 124 VariantData::Struct(..) => {
123 bar( 125 bar(
124 123 126 123
@@ -126,7 +128,7 @@ fn foo() {
126 } 128 }
127 _ => false, 129 _ => false,
128 } 130 }
129} ", 131} "#,
130 ) 132 )
131 } 133 }
132 134
@@ -134,7 +136,7 @@ fn foo() {
134 fn replace_if_let_with_match_target() { 136 fn replace_if_let_with_match_target() {
135 check_assist_target( 137 check_assist_target(
136 replace_if_let_with_match, 138 replace_if_let_with_match,
137 " 139 r#"
138impl VariantData { 140impl VariantData {
139 pub fn is_struct(&self) -> bool { 141 pub fn is_struct(&self) -> bool {
140 if <|>let VariantData::Struct(..) = *self { 142 if <|>let VariantData::Struct(..) = *self {
@@ -143,7 +145,7 @@ impl VariantData {
143 false 145 false
144 } 146 }
145 } 147 }
146} ", 148} "#,
147 "if let VariantData::Struct(..) = *self { 149 "if let VariantData::Struct(..) = *self {
148 true 150 true
149 } else { 151 } else {
@@ -173,7 +175,7 @@ enum Option<T> { Some(T), None }
173use Option::*; 175use Option::*;
174 176
175fn foo(x: Option<i32>) { 177fn foo(x: Option<i32>) {
176 <|>match x { 178 match x {
177 Some(x) => println!("{}", x), 179 Some(x) => println!("{}", x),
178 None => println!("none"), 180 None => println!("none"),
179 } 181 }
@@ -203,7 +205,7 @@ enum Result<T, E> { Ok(T), Err(E) }
203use Result::*; 205use Result::*;
204 206
205fn foo(x: Result<i32, ()>) { 207fn foo(x: Result<i32, ()>) {
206 <|>match x { 208 match x {
207 Ok(x) => println!("{}", x), 209 Ok(x) => println!("{}", x),
208 Err(_) => println!("none"), 210 Err(_) => println!("none"),
209 } 211 }
diff --git a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
index 0cf23b754..761557ac0 100644
--- a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
@@ -9,11 +9,7 @@ use ra_syntax::{
9 AstNode, T, 9 AstNode, T,
10}; 10};
11 11
12use crate::{ 12use crate::{utils::TryEnum, AssistContext, AssistId, Assists};
13 assist_ctx::{Assist, AssistCtx},
14 utils::TryEnum,
15 AssistId,
16};
17 13
18// Assist: replace_let_with_if_let 14// Assist: replace_let_with_if_let
19// 15//
@@ -39,15 +35,16 @@ use crate::{
39// 35//
40// fn compute() -> Option<i32> { None } 36// fn compute() -> Option<i32> { None }
41// ``` 37// ```
42pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> { 38pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
43 let let_kw = ctx.find_token_at_offset(T![let])?; 39 let let_kw = ctx.find_token_at_offset(T![let])?;
44 let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?; 40 let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?;
45 let init = let_stmt.initializer()?; 41 let init = let_stmt.initializer()?;
46 let original_pat = let_stmt.pat()?; 42 let original_pat = let_stmt.pat()?;
47 let ty = ctx.sema.type_of_expr(&init)?; 43 let ty = ctx.sema.type_of_expr(&init)?;
48 let happy_variant = TryEnum::from_ty(ctx.sema, &ty).map(|it| it.happy_case()); 44 let happy_variant = TryEnum::from_ty(&ctx.sema, &ty).map(|it| it.happy_case());
49 45
50 ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", |edit| { 46 let target = let_kw.text_range();
47 acc.add(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| {
51 let with_placeholder: ast::Pat = match happy_variant { 48 let with_placeholder: ast::Pat = match happy_variant {
52 None => make::placeholder_pat().into(), 49 None => make::placeholder_pat().into(),
53 Some(var_name) => make::tuple_struct_pat( 50 Some(var_name) => make::tuple_struct_pat(
@@ -56,25 +53,20 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> {
56 ) 53 )
57 .into(), 54 .into(),
58 }; 55 };
59 let block = 56 let block = make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax()));
60 IndentLevel::from_node(let_stmt.syntax()).increase_indent(make::block_expr(None, None));
61 let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block); 57 let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block);
62 let stmt = make::expr_stmt(if_); 58 let stmt = make::expr_stmt(if_);
63 59
64 let placeholder = stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap(); 60 let placeholder = stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap();
65 let target_offset =
66 let_stmt.syntax().text_range().start() + placeholder.syntax().text_range().start();
67 let stmt = stmt.replace_descendant(placeholder.into(), original_pat); 61 let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
68 62
69 edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt)); 63 edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
70 edit.target(let_kw.text_range());
71 edit.set_cursor(target_offset);
72 }) 64 })
73} 65}
74 66
75#[cfg(test)] 67#[cfg(test)]
76mod tests { 68mod tests {
77 use crate::helpers::check_assist; 69 use crate::tests::check_assist;
78 70
79 use super::*; 71 use super::*;
80 72
@@ -93,7 +85,7 @@ fn main() {
93enum E<T> { X(T), Y(T) } 85enum E<T> { X(T), Y(T) }
94 86
95fn main() { 87fn main() {
96 if let <|>x = E::X(92) { 88 if let x = E::X(92) {
97 } 89 }
98} 90}
99 ", 91 ",
diff --git a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
index 918e8dd8d..0197a8cf0 100644
--- a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -1,11 +1,7 @@
1use hir; 1use hir;
2use ra_syntax::{ast, AstNode, SmolStr, TextRange}; 2use ra_syntax::{ast, AstNode, SmolStr, TextRange};
3 3
4use crate::{ 4use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists};
5 assist_ctx::{Assist, AssistCtx},
6 utils::insert_use_statement,
7 AssistId,
8};
9 5
10// Assist: replace_qualified_name_with_use 6// Assist: replace_qualified_name_with_use
11// 7//
@@ -20,7 +16,10 @@ use crate::{
20// 16//
21// fn process(map: HashMap<String, String>) {} 17// fn process(map: HashMap<String, String>) {}
22// ``` 18// ```
23pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist> { 19pub(crate) fn replace_qualified_name_with_use(
20 acc: &mut Assists,
21 ctx: &AssistContext,
22) -> Option<()> {
24 let path: ast::Path = ctx.find_node_at_offset()?; 23 let path: ast::Path = ctx.find_node_at_offset()?;
25 // We don't want to mess with use statements 24 // We don't want to mess with use statements
26 if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { 25 if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() {
@@ -33,17 +32,19 @@ pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist>
33 return None; 32 return None;
34 } 33 }
35 34
36 ctx.add_assist( 35 let target = path.syntax().text_range();
36 acc.add(
37 AssistId("replace_qualified_name_with_use"), 37 AssistId("replace_qualified_name_with_use"),
38 "Replace qualified path with use", 38 "Replace qualified path with use",
39 |edit| { 39 target,
40 |builder| {
40 let path_to_import = hir_path.mod_path().clone(); 41 let path_to_import = hir_path.mod_path().clone();
41 insert_use_statement(path.syntax(), &path_to_import, edit.text_edit_builder()); 42 insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_builder());
42 43
43 if let Some(last) = path.segment() { 44 if let Some(last) = path.segment() {
44 // Here we are assuming the assist will provide a correct use statement 45 // Here we are assuming the assist will provide a correct use statement
45 // so we can delete the path qualifier 46 // so we can delete the path qualifier
46 edit.delete(TextRange::new( 47 builder.delete(TextRange::new(
47 path.syntax().text_range().start(), 48 path.syntax().text_range().start(),
48 last.syntax().text_range().start(), 49 last.syntax().text_range().start(),
49 )); 50 ));
@@ -74,7 +75,7 @@ fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
74 75
75#[cfg(test)] 76#[cfg(test)]
76mod tests { 77mod tests {
77 use crate::helpers::{check_assist, check_assist_not_applicable}; 78 use crate::tests::{check_assist, check_assist_not_applicable};
78 79
79 use super::*; 80 use super::*;
80 81
@@ -88,7 +89,7 @@ std::fmt::Debug<|>
88 " 89 "
89use std::fmt::Debug; 90use std::fmt::Debug;
90 91
91Debug<|> 92Debug
92 ", 93 ",
93 ); 94 );
94 } 95 }
@@ -105,7 +106,7 @@ fn main() {
105 " 106 "
106use std::fmt::Debug; 107use std::fmt::Debug;
107 108
108Debug<|> 109Debug
109 110
110fn main() { 111fn main() {
111} 112}
@@ -129,7 +130,7 @@ use std::fmt::Debug;
129fn main() { 130fn main() {
130} 131}
131 132
132Debug<|> 133Debug
133 ", 134 ",
134 ); 135 );
135 } 136 }
@@ -144,7 +145,7 @@ std::fmt<|>::Debug
144 " 145 "
145use std::fmt; 146use std::fmt;
146 147
147fmt<|>::Debug 148fmt::Debug
148 ", 149 ",
149 ); 150 );
150 } 151 }
@@ -163,7 +164,7 @@ impl std::fmt::Debug<|> for Foo {
163use stdx; 164use stdx;
164use std::fmt::Debug; 165use std::fmt::Debug;
165 166
166impl Debug<|> for Foo { 167impl Debug for Foo {
167} 168}
168 ", 169 ",
169 ); 170 );
@@ -180,7 +181,7 @@ impl std::fmt::Debug<|> for Foo {
180 " 181 "
181use std::fmt::Debug; 182use std::fmt::Debug;
182 183
183impl Debug<|> for Foo { 184impl Debug for Foo {
184} 185}
185 ", 186 ",
186 ); 187 );
@@ -197,7 +198,7 @@ impl Debug<|> for Foo {
197 " 198 "
198 use std::fmt::Debug; 199 use std::fmt::Debug;
199 200
200 impl Debug<|> for Foo { 201 impl Debug for Foo {
201 } 202 }
202 ", 203 ",
203 ); 204 );
@@ -216,7 +217,7 @@ impl std::io<|> for Foo {
216 " 217 "
217use std::{io, fmt}; 218use std::{io, fmt};
218 219
219impl io<|> for Foo { 220impl io for Foo {
220} 221}
221 ", 222 ",
222 ); 223 );
@@ -235,7 +236,7 @@ impl std::fmt::Debug<|> for Foo {
235 " 236 "
236use std::fmt::{self, Debug, }; 237use std::fmt::{self, Debug, };
237 238
238impl Debug<|> for Foo { 239impl Debug for Foo {
239} 240}
240 ", 241 ",
241 ); 242 );
@@ -254,7 +255,7 @@ impl std::fmt<|> for Foo {
254 " 255 "
255use std::fmt::{self, Debug}; 256use std::fmt::{self, Debug};
256 257
257impl fmt<|> for Foo { 258impl fmt for Foo {
258} 259}
259 ", 260 ",
260 ); 261 );
@@ -273,7 +274,7 @@ impl std::fmt::nested<|> for Foo {
273 " 274 "
274use std::fmt::{Debug, nested::{Display, self}}; 275use std::fmt::{Debug, nested::{Display, self}};
275 276
276impl nested<|> for Foo { 277impl nested for Foo {
277} 278}
278", 279",
279 ); 280 );
@@ -292,7 +293,7 @@ impl std::fmt::nested<|> for Foo {
292 " 293 "
293use std::fmt::{Debug, nested::{self, Display}}; 294use std::fmt::{Debug, nested::{self, Display}};
294 295
295impl nested<|> for Foo { 296impl nested for Foo {
296} 297}
297", 298",
298 ); 299 );
@@ -311,7 +312,7 @@ impl std::fmt::nested::Debug<|> for Foo {
311 " 312 "
312use std::fmt::{Debug, nested::{Display, Debug}}; 313use std::fmt::{Debug, nested::{Display, Debug}};
313 314
314impl Debug<|> for Foo { 315impl Debug for Foo {
315} 316}
316", 317",
317 ); 318 );
@@ -330,7 +331,7 @@ impl std::fmt::nested::Display<|> for Foo {
330 " 331 "
331use std::fmt::{nested::Display, Debug}; 332use std::fmt::{nested::Display, Debug};
332 333
333impl Display<|> for Foo { 334impl Display for Foo {
334} 335}
335", 336",
336 ); 337 );
@@ -349,7 +350,7 @@ impl std::fmt::Display<|> for Foo {
349 " 350 "
350use std::fmt::{Display, nested::Debug}; 351use std::fmt::{Display, nested::Debug};
351 352
352impl Display<|> for Foo { 353impl Display for Foo {
353} 354}
354", 355",
355 ); 356 );
@@ -373,7 +374,7 @@ use crate::{
373 AssocItem, 374 AssocItem,
374}; 375};
375 376
376fn foo() { lower<|>::trait_env() } 377fn foo() { lower::trait_env() }
377", 378",
378 ); 379 );
379 } 380 }
@@ -391,7 +392,7 @@ impl foo::Debug<|> for Foo {
391 " 392 "
392use std::fmt as foo; 393use std::fmt as foo;
393 394
394impl Debug<|> for Foo { 395impl Debug for Foo {
395} 396}
396", 397",
397 ); 398 );
@@ -434,7 +435,7 @@ mod foo {
434 mod bar { 435 mod bar {
435 use std::fmt::Debug; 436 use std::fmt::Debug;
436 437
437 Debug<|> 438 Debug
438 } 439 }
439} 440}
440 ", 441 ",
@@ -457,7 +458,7 @@ fn main() {
457use std::fmt::Debug; 458use std::fmt::Debug;
458 459
459fn main() { 460fn main() {
460 Debug<|> 461 Debug
461} 462}
462 ", 463 ",
463 ); 464 );
diff --git a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
index 62d4ea522..cff7dfb81 100644
--- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
@@ -1,11 +1,18 @@
1use std::iter; 1use std::iter;
2 2
3use ra_syntax::{ 3use ra_syntax::{
4 ast::{self, edit::IndentLevel, make}, 4 ast::{
5 self,
6 edit::{AstNodeEdit, IndentLevel},
7 make,
8 },
5 AstNode, 9 AstNode,
6}; 10};
7 11
8use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; 12use crate::{
13 utils::{render_snippet, Cursor, TryEnum},
14 AssistContext, AssistId, Assists,
15};
9 16
10// Assist: replace_unwrap_with_match 17// Assist: replace_unwrap_with_match
11// 18//
@@ -25,11 +32,11 @@ use crate::{utils::TryEnum, Assist, AssistCtx, AssistId};
25// let x: Result<i32, i32> = Result::Ok(92); 32// let x: Result<i32, i32> = Result::Ok(92);
26// let y = match x { 33// let y = match x {
27// Ok(a) => a, 34// Ok(a) => a,
28// _ => unreachable!(), 35// $0_ => unreachable!(),
29// }; 36// };
30// } 37// }
31// ``` 38// ```
32pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> { 39pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?; 40 let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
34 let name = method_call.name_ref()?; 41 let name = method_call.name_ref()?;
35 if name.text() != "unwrap" { 42 if name.text() != "unwrap" {
@@ -37,9 +44,9 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
37 } 44 }
38 let caller = method_call.expr()?; 45 let caller = method_call.expr()?;
39 let ty = ctx.sema.type_of_expr(&caller)?; 46 let ty = ctx.sema.type_of_expr(&caller)?;
40 let happy_variant = TryEnum::from_ty(ctx.sema, &ty)?.happy_case(); 47 let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case();
41 48 let target = method_call.syntax().text_range();
42 ctx.add_assist(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", |edit| { 49 acc.add(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", target, |builder| {
43 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); 50 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
44 let it = make::bind_pat(make::name("a")).into(); 51 let it = make::bind_pat(make::name("a")).into();
45 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); 52 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
@@ -47,23 +54,36 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
47 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); 54 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
48 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); 55 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
49 56
50 let unreachable_call = make::unreachable_macro_call().into(); 57 let unreachable_call = make::expr_unreachable();
51 let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); 58 let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
52 59
53 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); 60 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
54 let match_expr = make::expr_match(caller.clone(), match_arm_list); 61 let match_expr = make::expr_match(caller.clone(), match_arm_list)
55 let match_expr = IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr); 62 .indent(IndentLevel::from_node(method_call.syntax()));
56 63
57 edit.target(method_call.syntax().text_range()); 64 let range = method_call.syntax().text_range();
58 edit.set_cursor(caller.syntax().text_range().start()); 65 match ctx.config.snippet_cap {
59 edit.replace_ast::<ast::Expr>(method_call.into(), match_expr); 66 Some(cap) => {
67 let err_arm = match_expr
68 .syntax()
69 .descendants()
70 .filter_map(ast::MatchArm::cast)
71 .last()
72 .unwrap();
73 let snippet =
74 render_snippet(cap, match_expr.syntax(), Cursor::Before(err_arm.syntax()));
75 builder.replace_snippet(cap, range, snippet)
76 }
77 None => builder.replace(range, match_expr.to_string()),
78 }
60 }) 79 })
61} 80}
62 81
63#[cfg(test)] 82#[cfg(test)]
64mod tests { 83mod tests {
84 use crate::tests::{check_assist, check_assist_target};
85
65 use super::*; 86 use super::*;
66 use crate::helpers::{check_assist, check_assist_target};
67 87
68 #[test] 88 #[test]
69 fn test_replace_result_unwrap_with_match() { 89 fn test_replace_result_unwrap_with_match() {
@@ -82,9 +102,9 @@ enum Result<T, E> { Ok(T), Err(E) }
82fn i<T>(a: T) -> T { a } 102fn i<T>(a: T) -> T { a }
83fn main() { 103fn main() {
84 let x: Result<i32, i32> = Result::Ok(92); 104 let x: Result<i32, i32> = Result::Ok(92);
85 let y = <|>match i(x) { 105 let y = match i(x) {
86 Ok(a) => a, 106 Ok(a) => a,
87 _ => unreachable!(), 107 $0_ => unreachable!(),
88 }; 108 };
89} 109}
90 ", 110 ",
@@ -108,9 +128,9 @@ enum Option<T> { Some(T), None }
108fn i<T>(a: T) -> T { a } 128fn i<T>(a: T) -> T { a }
109fn main() { 129fn main() {
110 let x = Option::Some(92); 130 let x = Option::Some(92);
111 let y = <|>match i(x) { 131 let y = match i(x) {
112 Some(a) => a, 132 Some(a) => a,
113 _ => unreachable!(), 133 $0_ => unreachable!(),
114 }; 134 };
115} 135}
116 ", 136 ",
@@ -134,9 +154,9 @@ enum Result<T, E> { Ok(T), Err(E) }
134fn i<T>(a: T) -> T { a } 154fn i<T>(a: T) -> T { a }
135fn main() { 155fn main() {
136 let x: Result<i32, i32> = Result::Ok(92); 156 let x: Result<i32, i32> = Result::Ok(92);
137 let y = <|>match i(x) { 157 let y = match i(x) {
138 Ok(a) => a, 158 Ok(a) => a,
139 _ => unreachable!(), 159 $0_ => unreachable!(),
140 }.count_zeroes(); 160 }.count_zeroes();
141} 161}
142 ", 162 ",
diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs
index f25826796..c7a874480 100644
--- a/crates/ra_assists/src/handlers/split_import.rs
+++ b/crates/ra_assists/src/handlers/split_import.rs
@@ -2,7 +2,7 @@ use std::iter::successors;
2 2
3use ra_syntax::{ast, AstNode, T}; 3use ra_syntax::{ast, AstNode, T};
4 4
5use crate::{Assist, AssistCtx, AssistId}; 5use crate::{AssistContext, AssistId, Assists};
6 6
7// Assist: split_import 7// Assist: split_import
8// 8//
@@ -15,7 +15,7 @@ use crate::{Assist, AssistCtx, AssistId};
15// ``` 15// ```
16// use std::{collections::HashMap}; 16// use std::{collections::HashMap};
17// ``` 17// ```
18pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> { 18pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
19 let colon_colon = ctx.find_token_at_offset(T![::])?; 19 let colon_colon = ctx.find_token_at_offset(T![::])?;
20 let path = ast::Path::cast(colon_colon.parent())?.qualifier()?; 20 let path = ast::Path::cast(colon_colon.parent())?.qualifier()?;
21 let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?; 21 let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?;
@@ -26,18 +26,16 @@ pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> {
26 if new_tree == use_tree { 26 if new_tree == use_tree {
27 return None; 27 return None;
28 } 28 }
29 let cursor = ctx.frange.range.start();
30 29
31 ctx.add_assist(AssistId("split_import"), "Split import", |edit| { 30 let target = colon_colon.text_range();
32 edit.target(colon_colon.text_range()); 31 acc.add(AssistId("split_import"), "Split import", target, |edit| {
33 edit.replace_ast(use_tree, new_tree); 32 edit.replace_ast(use_tree, new_tree);
34 edit.set_cursor(cursor);
35 }) 33 })
36} 34}
37 35
38#[cfg(test)] 36#[cfg(test)]
39mod tests { 37mod tests {
40 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; 38 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
41 39
42 use super::*; 40 use super::*;
43 41
@@ -46,7 +44,7 @@ mod tests {
46 check_assist( 44 check_assist(
47 split_import, 45 split_import,
48 "use crate::<|>db::RootDatabase;", 46 "use crate::<|>db::RootDatabase;",
49 "use crate::<|>{db::RootDatabase};", 47 "use crate::{db::RootDatabase};",
50 ) 48 )
51 } 49 }
52 50
@@ -55,7 +53,7 @@ mod tests {
55 check_assist( 53 check_assist(
56 split_import, 54 split_import,
57 "use crate:<|>:db::{RootDatabase, FileSymbol}", 55 "use crate:<|>:db::{RootDatabase, FileSymbol}",
58 "use crate:<|>:{db::{RootDatabase, FileSymbol}}", 56 "use crate::{db::{RootDatabase, FileSymbol}}",
59 ) 57 )
60 } 58 }
61 59
diff --git a/crates/ra_assists/src/handlers/unwrap_block.rs b/crates/ra_assists/src/handlers/unwrap_block.rs
new file mode 100644
index 000000000..8440c7d0f
--- /dev/null
+++ b/crates/ra_assists/src/handlers/unwrap_block.rs
@@ -0,0 +1,512 @@
1use ra_fmt::unwrap_trivial_block;
2use ra_syntax::{
3 ast::{self, ElseBranch, Expr, LoopBodyOwner},
4 match_ast, AstNode, TextRange, T,
5};
6
7use crate::{AssistContext, AssistId, Assists};
8
9// Assist: unwrap_block
10//
11// This assist removes if...else, for, while and loop control statements to just keep the body.
12//
13// ```
14// fn foo() {
15// if true {<|>
16// println!("foo");
17// }
18// }
19// ```
20// ->
21// ```
22// fn foo() {
23// println!("foo");
24// }
25// ```
26pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27 let l_curly_token = ctx.find_token_at_offset(T!['{'])?;
28 let block = ast::BlockExpr::cast(l_curly_token.parent())?;
29 let parent = block.syntax().parent()?;
30 let assist_id = AssistId("unwrap_block");
31 let assist_label = "Unwrap block";
32
33 let (expr, expr_to_unwrap) = match_ast! {
34 match parent {
35 ast::ForExpr(for_expr) => {
36 let block_expr = for_expr.loop_body()?;
37 let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?;
38 (ast::Expr::ForExpr(for_expr), expr_to_unwrap)
39 },
40 ast::WhileExpr(while_expr) => {
41 let block_expr = while_expr.loop_body()?;
42 let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?;
43 (ast::Expr::WhileExpr(while_expr), expr_to_unwrap)
44 },
45 ast::LoopExpr(loop_expr) => {
46 let block_expr = loop_expr.loop_body()?;
47 let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?;
48 (ast::Expr::LoopExpr(loop_expr), expr_to_unwrap)
49 },
50 ast::IfExpr(if_expr) => {
51 let mut resp = None;
52
53 let then_branch = if_expr.then_branch()?;
54 if then_branch.l_curly_token()?.text_range().contains_range(ctx.frange.range) {
55 if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) {
56 // For `else if` blocks
57 let ancestor_then_branch = ancestor.then_branch()?;
58 let l_curly_token = then_branch.l_curly_token()?;
59
60 let target = then_branch.syntax().text_range();
61 return acc.add(assist_id, assist_label, target, |edit| {
62 let range_to_del_else_if = TextRange::new(ancestor_then_branch.syntax().text_range().end(), l_curly_token.text_range().start());
63 let range_to_del_rest = TextRange::new(then_branch.syntax().text_range().end(), if_expr.syntax().text_range().end());
64
65 edit.delete(range_to_del_rest);
66 edit.delete(range_to_del_else_if);
67 edit.replace(target, update_expr_string(then_branch.to_string(), &[' ', '{']));
68 });
69 } else {
70 resp = Some((ast::Expr::IfExpr(if_expr.clone()), Expr::BlockExpr(then_branch)));
71 }
72 } else if let Some(else_branch) = if_expr.else_branch() {
73 match else_branch {
74 ElseBranch::Block(else_block) => {
75 let l_curly_token = else_block.l_curly_token()?;
76 if l_curly_token.text_range().contains_range(ctx.frange.range) {
77 let target = else_block.syntax().text_range();
78 return acc.add(assist_id, assist_label, target, |edit| {
79 let range_to_del = TextRange::new(then_branch.syntax().text_range().end(), l_curly_token.text_range().start());
80
81 edit.delete(range_to_del);
82 edit.replace(target, update_expr_string(else_block.to_string(), &[' ', '{']));
83 });
84 }
85 },
86 ElseBranch::IfExpr(_) => {},
87 }
88 }
89
90 resp?
91 },
92 _ => return None,
93 }
94 };
95
96 let target = expr_to_unwrap.syntax().text_range();
97 acc.add(assist_id, assist_label, target, |edit| {
98 edit.replace(
99 expr.syntax().text_range(),
100 update_expr_string(expr_to_unwrap.to_string(), &[' ', '{', '\n']),
101 );
102 })
103}
104
105fn extract_expr(cursor_range: TextRange, block: ast::BlockExpr) -> Option<ast::Expr> {
106 let cursor_in_range = block.l_curly_token()?.text_range().contains_range(cursor_range);
107
108 if cursor_in_range {
109 Some(unwrap_trivial_block(block))
110 } else {
111 None
112 }
113}
114
115fn update_expr_string(expr_str: String, trim_start_pat: &[char]) -> String {
116 let expr_string = expr_str.trim_start_matches(trim_start_pat);
117 let mut expr_string_lines: Vec<&str> = expr_string.lines().collect();
118 expr_string_lines.pop(); // Delete last line
119
120 expr_string_lines
121 .into_iter()
122 .map(|line| line.replacen(" ", "", 1)) // Delete indentation
123 .collect::<Vec<String>>()
124 .join("\n")
125}
126
127#[cfg(test)]
128mod tests {
129 use crate::tests::{check_assist, check_assist_not_applicable};
130
131 use super::*;
132
133 #[test]
134 fn simple_if() {
135 check_assist(
136 unwrap_block,
137 r#"
138 fn main() {
139 bar();
140 if true {<|>
141 foo();
142
143 //comment
144 bar();
145 } else {
146 println!("bar");
147 }
148 }
149 "#,
150 r#"
151 fn main() {
152 bar();
153 foo();
154
155 //comment
156 bar();
157 }
158 "#,
159 );
160 }
161
162 #[test]
163 fn simple_if_else() {
164 check_assist(
165 unwrap_block,
166 r#"
167 fn main() {
168 bar();
169 if true {
170 foo();
171
172 //comment
173 bar();
174 } else {<|>
175 println!("bar");
176 }
177 }
178 "#,
179 r#"
180 fn main() {
181 bar();
182 if true {
183 foo();
184
185 //comment
186 bar();
187 }
188 println!("bar");
189 }
190 "#,
191 );
192 }
193
194 #[test]
195 fn simple_if_else_if() {
196 check_assist(
197 unwrap_block,
198 r#"
199 fn main() {
200 //bar();
201 if true {
202 println!("true");
203
204 //comment
205 //bar();
206 } else if false {<|>
207 println!("bar");
208 } else {
209 println!("foo");
210 }
211 }
212 "#,
213 r#"
214 fn main() {
215 //bar();
216 if true {
217 println!("true");
218
219 //comment
220 //bar();
221 }
222 println!("bar");
223 }
224 "#,
225 );
226 }
227
228 #[test]
229 fn simple_if_else_if_nested() {
230 check_assist(
231 unwrap_block,
232 r#"
233 fn main() {
234 //bar();
235 if true {
236 println!("true");
237
238 //comment
239 //bar();
240 } else if false {
241 println!("bar");
242 } else if true {<|>
243 println!("foo");
244 }
245 }
246 "#,
247 r#"
248 fn main() {
249 //bar();
250 if true {
251 println!("true");
252
253 //comment
254 //bar();
255 } else if false {
256 println!("bar");
257 }
258 println!("foo");
259 }
260 "#,
261 );
262 }
263
264 #[test]
265 fn simple_if_else_if_nested_else() {
266 check_assist(
267 unwrap_block,
268 r#"
269 fn main() {
270 //bar();
271 if true {
272 println!("true");
273
274 //comment
275 //bar();
276 } else if false {
277 println!("bar");
278 } else if true {
279 println!("foo");
280 } else {<|>
281 println!("else");
282 }
283 }
284 "#,
285 r#"
286 fn main() {
287 //bar();
288 if true {
289 println!("true");
290
291 //comment
292 //bar();
293 } else if false {
294 println!("bar");
295 } else if true {
296 println!("foo");
297 }
298 println!("else");
299 }
300 "#,
301 );
302 }
303
304 #[test]
305 fn simple_if_else_if_nested_middle() {
306 check_assist(
307 unwrap_block,
308 r#"
309 fn main() {
310 //bar();
311 if true {
312 println!("true");
313
314 //comment
315 //bar();
316 } else if false {
317 println!("bar");
318 } else if true {<|>
319 println!("foo");
320 } else {
321 println!("else");
322 }
323 }
324 "#,
325 r#"
326 fn main() {
327 //bar();
328 if true {
329 println!("true");
330
331 //comment
332 //bar();
333 } else if false {
334 println!("bar");
335 }
336 println!("foo");
337 }
338 "#,
339 );
340 }
341
342 #[test]
343 fn simple_if_bad_cursor_position() {
344 check_assist_not_applicable(
345 unwrap_block,
346 r#"
347 fn main() {
348 bar();<|>
349 if true {
350 foo();
351
352 //comment
353 bar();
354 } else {
355 println!("bar");
356 }
357 }
358 "#,
359 );
360 }
361
362 #[test]
363 fn simple_for() {
364 check_assist(
365 unwrap_block,
366 r#"
367 fn main() {
368 for i in 0..5 {<|>
369 if true {
370 foo();
371
372 //comment
373 bar();
374 } else {
375 println!("bar");
376 }
377 }
378 }
379 "#,
380 r#"
381 fn main() {
382 if true {
383 foo();
384
385 //comment
386 bar();
387 } else {
388 println!("bar");
389 }
390 }
391 "#,
392 );
393 }
394
395 #[test]
396 fn simple_if_in_for() {
397 check_assist(
398 unwrap_block,
399 r#"
400 fn main() {
401 for i in 0..5 {
402 if true {<|>
403 foo();
404
405 //comment
406 bar();
407 } else {
408 println!("bar");
409 }
410 }
411 }
412 "#,
413 r#"
414 fn main() {
415 for i in 0..5 {
416 foo();
417
418 //comment
419 bar();
420 }
421 }
422 "#,
423 );
424 }
425
426 #[test]
427 fn simple_loop() {
428 check_assist(
429 unwrap_block,
430 r#"
431 fn main() {
432 loop {<|>
433 if true {
434 foo();
435
436 //comment
437 bar();
438 } else {
439 println!("bar");
440 }
441 }
442 }
443 "#,
444 r#"
445 fn main() {
446 if true {
447 foo();
448
449 //comment
450 bar();
451 } else {
452 println!("bar");
453 }
454 }
455 "#,
456 );
457 }
458
459 #[test]
460 fn simple_while() {
461 check_assist(
462 unwrap_block,
463 r#"
464 fn main() {
465 while true {<|>
466 if true {
467 foo();
468
469 //comment
470 bar();
471 } else {
472 println!("bar");
473 }
474 }
475 }
476 "#,
477 r#"
478 fn main() {
479 if true {
480 foo();
481
482 //comment
483 bar();
484 } else {
485 println!("bar");
486 }
487 }
488 "#,
489 );
490 }
491
492 #[test]
493 fn simple_if_in_while_bad_cursor_position() {
494 check_assist_not_applicable(
495 unwrap_block,
496 r#"
497 fn main() {
498 while true {
499 if true {
500 foo();<|>
501
502 //comment
503 bar();
504 } else {
505 println!("bar");
506 }
507 }
508 }
509 "#,
510 );
511 }
512}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 64bd87afb..464bc03dd 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -10,119 +10,113 @@ macro_rules! eprintln {
10 ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; 10 ($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
11} 11}
12 12
13mod assist_ctx; 13mod assist_config;
14mod marks; 14mod assist_context;
15#[cfg(test)] 15#[cfg(test)]
16mod doc_tests; 16mod tests;
17pub mod utils; 17pub mod utils;
18pub mod ast_transform; 18pub mod ast_transform;
19 19
20use ra_db::{FileId, FileRange};
21use ra_ide_db::RootDatabase;
22use ra_syntax::{TextRange, TextSize};
23use ra_text_edit::TextEdit;
24
25pub(crate) use crate::assist_ctx::{Assist, AssistCtx, AssistHandler};
26use hir::Semantics; 20use hir::Semantics;
21use ra_db::FileRange;
22use ra_ide_db::{source_change::SourceChange, RootDatabase};
23use ra_syntax::TextRange;
24
25pub(crate) use crate::assist_context::{AssistContext, Assists};
26
27pub use assist_config::AssistConfig;
27 28
28/// Unique identifier of the assist, should not be shown to the user 29/// Unique identifier of the assist, should not be shown to the user
29/// directly. 30/// directly.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)] 31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct AssistId(pub &'static str); 32pub struct AssistId(pub &'static str);
32 33
33#[derive(Debug, Clone)]
34pub struct AssistLabel {
35 /// Short description of the assist, as shown in the UI.
36 pub label: String,
37 pub id: AssistId,
38}
39
40#[derive(Clone, Debug)] 34#[derive(Clone, Debug)]
41pub struct GroupLabel(pub String); 35pub struct GroupLabel(pub String);
42 36
43impl AssistLabel {
44 pub(crate) fn new(label: String, id: AssistId) -> AssistLabel {
45 // FIXME: make fields private, so that this invariant can't be broken
46 assert!(label.starts_with(|c: char| c.is_uppercase()));
47 AssistLabel { label, id }
48 }
49}
50
51#[derive(Debug, Clone)] 37#[derive(Debug, Clone)]
52pub struct AssistAction { 38pub struct Assist {
53 pub edit: TextEdit, 39 pub id: AssistId,
54 pub cursor_position: Option<TextSize>, 40 /// Short description of the assist, as shown in the UI.
55 // FIXME: This belongs to `AssistLabel` 41 pub label: String,
56 pub target: Option<TextRange>, 42 pub group: Option<GroupLabel>,
57 pub file: AssistFile, 43 /// Target ranges are used to sort assists: the smaller the target range,
44 /// the more specific assist is, and so it should be sorted first.
45 pub target: TextRange,
58} 46}
59 47
60#[derive(Debug, Clone)] 48#[derive(Debug, Clone)]
61pub struct ResolvedAssist { 49pub struct ResolvedAssist {
62 pub label: AssistLabel, 50 pub assist: Assist,
63 pub group_label: Option<GroupLabel>, 51 pub source_change: SourceChange,
64 pub action: AssistAction,
65} 52}
66 53
67#[derive(Debug, Clone, Copy)] 54impl Assist {
68pub enum AssistFile { 55 /// Return all the assists applicable at the given position.
69 CurrentFile, 56 ///
70 TargetFile(FileId), 57 /// Assists are returned in the "unresolved" state, that is only labels are
71} 58 /// returned, without actual edits.
72 59 pub fn unresolved(db: &RootDatabase, config: &AssistConfig, range: FileRange) -> Vec<Assist> {
73impl Default for AssistFile { 60 let sema = Semantics::new(db);
74 fn default() -> Self { 61 let ctx = AssistContext::new(sema, config, range);
75 Self::CurrentFile 62 let mut acc = Assists::new_unresolved(&ctx);
63 handlers::all().iter().for_each(|handler| {
64 handler(&mut acc, &ctx);
65 });
66 acc.finish_unresolved()
76 } 67 }
77}
78 68
79/// Return all the assists applicable at the given position. 69 /// Return all the assists applicable at the given position.
80/// 70 ///
81/// Assists are returned in the "unresolved" state, that is only labels are 71 /// Assists are returned in the "resolved" state, that is with edit fully
82/// returned, without actual edits. 72 /// computed.
83pub fn unresolved_assists(db: &RootDatabase, range: FileRange) -> Vec<AssistLabel> { 73 pub fn resolved(
84 let sema = Semantics::new(db); 74 db: &RootDatabase,
85 let ctx = AssistCtx::new(&sema, range, false); 75 config: &AssistConfig,
86 handlers::all() 76 range: FileRange,
87 .iter() 77 ) -> Vec<ResolvedAssist> {
88 .filter_map(|f| f(ctx.clone())) 78 let sema = Semantics::new(db);
89 .flat_map(|it| it.0) 79 let ctx = AssistContext::new(sema, config, range);
90 .map(|a| a.label) 80 let mut acc = Assists::new_resolved(&ctx);
91 .collect() 81 handlers::all().iter().for_each(|handler| {
92} 82 handler(&mut acc, &ctx);
83 });
84 acc.finish_resolved()
85 }
93 86
94/// Return all the assists applicable at the given position. 87 pub(crate) fn new(
95/// 88 id: AssistId,
96/// Assists are returned in the "resolved" state, that is with edit fully 89 label: String,
97/// computed. 90 group: Option<GroupLabel>,
98pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssist> { 91 target: TextRange,
99 let sema = Semantics::new(db); 92 ) -> Assist {
100 let ctx = AssistCtx::new(&sema, range, true); 93 // FIXME: make fields private, so that this invariant can't be broken
101 let mut a = handlers::all() 94 assert!(label.starts_with(|c: char| c.is_uppercase()));
102 .iter() 95 Assist { id, label, group, target }
103 .filter_map(|f| f(ctx.clone())) 96 }
104 .flat_map(|it| it.0)
105 .map(|it| it.into_resolved().unwrap())
106 .collect::<Vec<_>>();
107 a.sort_by_key(|it| it.action.target.map_or(TextSize::from(!0u32), |it| it.len()));
108 a
109} 97}
110 98
111mod handlers { 99mod handlers {
112 use crate::AssistHandler; 100 use crate::{AssistContext, Assists};
101
102 pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>;
113 103
114 mod add_custom_impl; 104 mod add_custom_impl;
115 mod add_derive; 105 mod add_derive;
116 mod add_explicit_type; 106 mod add_explicit_type;
107 mod add_from_impl_for_enum;
117 mod add_function; 108 mod add_function;
118 mod add_impl; 109 mod add_impl;
119 mod add_missing_impl_members; 110 mod add_missing_impl_members;
120 mod add_new; 111 mod add_new;
112 mod add_turbo_fish;
121 mod apply_demorgan; 113 mod apply_demorgan;
122 mod auto_import; 114 mod auto_import;
115 mod change_return_type_to_result;
123 mod change_visibility; 116 mod change_visibility;
124 mod early_return; 117 mod early_return;
125 mod fill_match_arms; 118 mod fill_match_arms;
119 mod fix_visibility;
126 mod flip_binexpr; 120 mod flip_binexpr;
127 mod flip_comma; 121 mod flip_comma;
128 mod flip_trait_bound; 122 mod flip_trait_bound;
@@ -136,28 +130,32 @@ mod handlers {
136 mod raw_string; 130 mod raw_string;
137 mod remove_dbg; 131 mod remove_dbg;
138 mod remove_mut; 132 mod remove_mut;
133 mod reorder_fields;
139 mod replace_if_let_with_match; 134 mod replace_if_let_with_match;
140 mod replace_let_with_if_let; 135 mod replace_let_with_if_let;
141 mod replace_qualified_name_with_use; 136 mod replace_qualified_name_with_use;
142 mod replace_unwrap_with_match; 137 mod replace_unwrap_with_match;
143 mod split_import; 138 mod split_import;
144 mod add_from_impl_for_enum; 139 mod unwrap_block;
145 mod reorder_fields;
146 140
147 pub(crate) fn all() -> &'static [AssistHandler] { 141 pub(crate) fn all() -> &'static [Handler] {
148 &[ 142 &[
149 // These are alphabetic for the foolish consistency 143 // These are alphabetic for the foolish consistency
150 add_custom_impl::add_custom_impl, 144 add_custom_impl::add_custom_impl,
151 add_derive::add_derive, 145 add_derive::add_derive,
152 add_explicit_type::add_explicit_type, 146 add_explicit_type::add_explicit_type,
147 add_from_impl_for_enum::add_from_impl_for_enum,
153 add_function::add_function, 148 add_function::add_function,
154 add_impl::add_impl, 149 add_impl::add_impl,
155 add_new::add_new, 150 add_new::add_new,
151 add_turbo_fish::add_turbo_fish,
156 apply_demorgan::apply_demorgan, 152 apply_demorgan::apply_demorgan,
157 auto_import::auto_import, 153 auto_import::auto_import,
154 change_return_type_to_result::change_return_type_to_result,
158 change_visibility::change_visibility, 155 change_visibility::change_visibility,
159 early_return::convert_to_guarded_return, 156 early_return::convert_to_guarded_return,
160 fill_match_arms::fill_match_arms, 157 fill_match_arms::fill_match_arms,
158 fix_visibility::fix_visibility,
161 flip_binexpr::flip_binexpr, 159 flip_binexpr::flip_binexpr,
162 flip_comma::flip_comma, 160 flip_comma::flip_comma,
163 flip_trait_bound::flip_trait_bound, 161 flip_trait_bound::flip_trait_bound,
@@ -175,167 +173,18 @@ mod handlers {
175 raw_string::remove_hash, 173 raw_string::remove_hash,
176 remove_dbg::remove_dbg, 174 remove_dbg::remove_dbg,
177 remove_mut::remove_mut, 175 remove_mut::remove_mut,
176 reorder_fields::reorder_fields,
178 replace_if_let_with_match::replace_if_let_with_match, 177 replace_if_let_with_match::replace_if_let_with_match,
179 replace_let_with_if_let::replace_let_with_if_let, 178 replace_let_with_if_let::replace_let_with_if_let,
180 replace_qualified_name_with_use::replace_qualified_name_with_use, 179 replace_qualified_name_with_use::replace_qualified_name_with_use,
181 replace_unwrap_with_match::replace_unwrap_with_match, 180 replace_unwrap_with_match::replace_unwrap_with_match,
182 split_import::split_import, 181 split_import::split_import,
183 add_from_impl_for_enum::add_from_impl_for_enum, 182 unwrap_block::unwrap_block,
184 // These are manually sorted for better priorities 183 // These are manually sorted for better priorities
185 add_missing_impl_members::add_missing_impl_members, 184 add_missing_impl_members::add_missing_impl_members,
186 add_missing_impl_members::add_missing_default_members, 185 add_missing_impl_members::add_missing_default_members,
187 reorder_fields::reorder_fields, 186 // Are you sure you want to add new assist here, and not to the
187 // sorted list above?
188 ] 188 ]
189 } 189 }
190} 190}
191
192#[cfg(test)]
193mod helpers {
194 use std::sync::Arc;
195
196 use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
197 use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
198 use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset};
199
200 use crate::{AssistCtx, AssistFile, AssistHandler};
201 use hir::Semantics;
202
203 pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
204 let (mut db, file_id) = RootDatabase::with_single_file(text);
205 // FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`,
206 // but it looks like this might need specialization? :(
207 db.set_local_roots(Arc::new(vec![db.file_source_root(file_id)]));
208 (db, file_id)
209 }
210
211 pub(crate) fn check_assist(
212 assist: AssistHandler,
213 ra_fixture_before: &str,
214 ra_fixture_after: &str,
215 ) {
216 check(assist, ra_fixture_before, ExpectedResult::After(ra_fixture_after));
217 }
218
219 // FIXME: instead of having a separate function here, maybe use
220 // `extract_ranges` and mark the target as `<target> </target>` in the
221 // fixuture?
222 pub(crate) fn check_assist_target(assist: AssistHandler, ra_fixture: &str, target: &str) {
223 check(assist, ra_fixture, ExpectedResult::Target(target));
224 }
225
226 pub(crate) fn check_assist_not_applicable(assist: AssistHandler, ra_fixture: &str) {
227 check(assist, ra_fixture, ExpectedResult::NotApplicable);
228 }
229
230 enum ExpectedResult<'a> {
231 NotApplicable,
232 After(&'a str),
233 Target(&'a str),
234 }
235
236 fn check(assist: AssistHandler, before: &str, expected: ExpectedResult) {
237 let (text_without_caret, file_with_caret_id, range_or_offset, db) =
238 if before.contains("//-") {
239 let (mut db, position) = RootDatabase::with_position(before);
240 db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)]));
241 (
242 db.file_text(position.file_id).as_ref().to_owned(),
243 position.file_id,
244 RangeOrOffset::Offset(position.offset),
245 db,
246 )
247 } else {
248 let (range_or_offset, text_without_caret) = extract_range_or_offset(before);
249 let (db, file_id) = with_single_file(&text_without_caret);
250 (text_without_caret, file_id, range_or_offset, db)
251 };
252
253 let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
254
255 let sema = Semantics::new(&db);
256 let assist_ctx = AssistCtx::new(&sema, frange, true);
257
258 match (assist(assist_ctx), expected) {
259 (Some(assist), ExpectedResult::After(after)) => {
260 let action = assist.0[0].action.clone().unwrap();
261
262 let assisted_file_text = if let AssistFile::TargetFile(file_id) = action.file {
263 db.file_text(file_id).as_ref().to_owned()
264 } else {
265 text_without_caret
266 };
267
268 let mut actual = action.edit.apply(&assisted_file_text);
269 match action.cursor_position {
270 None => {
271 if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {
272 let off = action
273 .edit
274 .apply_to_offset(before_cursor_pos)
275 .expect("cursor position is affected by the edit");
276 actual = add_cursor(&actual, off)
277 }
278 }
279 Some(off) => actual = add_cursor(&actual, off),
280 };
281
282 assert_eq_text!(after, &actual);
283 }
284 (Some(assist), ExpectedResult::Target(target)) => {
285 let action = assist.0[0].action.clone().unwrap();
286 let range = action.target.expect("expected target on action");
287 assert_eq_text!(&text_without_caret[range], target);
288 }
289 (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),
290 (None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => {
291 panic!("code action is not applicable")
292 }
293 (None, ExpectedResult::NotApplicable) => (),
294 };
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use ra_db::FileRange;
301 use ra_syntax::TextRange;
302 use test_utils::{extract_offset, extract_range};
303
304 use crate::{helpers, resolved_assists};
305
306 #[test]
307 fn assist_order_field_struct() {
308 let before = "struct Foo { <|>bar: u32 }";
309 let (before_cursor_pos, before) = extract_offset(before);
310 let (db, file_id) = helpers::with_single_file(&before);
311 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
312 let assists = resolved_assists(&db, frange);
313 let mut assists = assists.iter();
314
315 assert_eq!(
316 assists.next().expect("expected assist").label.label,
317 "Change visibility to pub(crate)"
318 );
319 assert_eq!(assists.next().expect("expected assist").label.label, "Add `#[derive]`");
320 }
321
322 #[test]
323 fn assist_order_if_expr() {
324 let before = "
325 pub fn test_some_range(a: int) -> bool {
326 if let 2..6 = <|>5<|> {
327 true
328 } else {
329 false
330 }
331 }";
332 let (range, before) = extract_range(before);
333 let (db, file_id) = helpers::with_single_file(&before);
334 let frange = FileRange { file_id, range };
335 let assists = resolved_assists(&db, frange);
336 let mut assists = assists.iter();
337
338 assert_eq!(assists.next().expect("expected assist").label.label, "Extract into variable");
339 assert_eq!(assists.next().expect("expected assist").label.label, "Replace with match");
340 }
341}
diff --git a/crates/ra_assists/src/marks.rs b/crates/ra_assists/src/marks.rs
deleted file mode 100644
index 8d910205f..000000000
--- a/crates/ra_assists/src/marks.rs
+++ /dev/null
@@ -1,12 +0,0 @@
1//! See test_utils/src/marks.rs
2
3test_utils::marks![
4 introduce_var_in_comment_is_not_applicable
5 test_introduce_var_expr_stmt
6 test_introduce_var_last_expr
7 not_applicable_outside_of_bind_pat
8 test_not_inline_mut_variable
9 test_not_applicable_if_variable_unused
10 change_visibility_field_false_positive
11 test_add_from_impl_already_exists
12];
diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs
new file mode 100644
index 000000000..62dd3547f
--- /dev/null
+++ b/crates/ra_assists/src/tests.rs
@@ -0,0 +1,153 @@
1mod generated;
2
3use std::sync::Arc;
4
5use hir::Semantics;
6use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
7use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
8use ra_syntax::TextRange;
9use test_utils::{
10 assert_eq_text, extract_offset, extract_range, extract_range_or_offset, RangeOrOffset,
11};
12
13use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists};
14
15pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
16 let (mut db, file_id) = RootDatabase::with_single_file(text);
17 // FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`,
18 // but it looks like this might need specialization? :(
19 db.set_local_roots(Arc::new(vec![db.file_source_root(file_id)]));
20 (db, file_id)
21}
22
23pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
24 check(assist, ra_fixture_before, ExpectedResult::After(ra_fixture_after));
25}
26
27// FIXME: instead of having a separate function here, maybe use
28// `extract_ranges` and mark the target as `<target> </target>` in the
29// fixuture?
30pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) {
31 check(assist, ra_fixture, ExpectedResult::Target(target));
32}
33
34pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
35 check(assist, ra_fixture, ExpectedResult::NotApplicable);
36}
37
38fn check_doc_test(assist_id: &str, before: &str, after: &str) {
39 let (selection, before) = extract_range_or_offset(before);
40 let (db, file_id) = crate::tests::with_single_file(&before);
41 let frange = FileRange { file_id, range: selection.into() };
42
43 let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange)
44 .into_iter()
45 .find(|assist| assist.assist.id.0 == assist_id)
46 .unwrap_or_else(|| {
47 panic!(
48 "\n\nAssist is not applicable: {}\nAvailable assists: {}",
49 assist_id,
50 Assist::resolved(&db, &AssistConfig::default(), frange)
51 .into_iter()
52 .map(|assist| assist.assist.id.0)
53 .collect::<Vec<_>>()
54 .join(", ")
55 )
56 });
57
58 let actual = {
59 let change = assist.source_change.source_file_edits.pop().unwrap();
60 let mut actual = before.clone();
61 change.edit.apply(&mut actual);
62 actual
63 };
64 assert_eq_text!(after, &actual);
65}
66
67enum ExpectedResult<'a> {
68 NotApplicable,
69 After(&'a str),
70 Target(&'a str),
71}
72
73fn check(handler: Handler, before: &str, expected: ExpectedResult) {
74 let (text_without_caret, file_with_caret_id, range_or_offset, db) = if before.contains("//-") {
75 let (mut db, position) = RootDatabase::with_position(before);
76 db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)]));
77 (
78 db.file_text(position.file_id).as_ref().to_owned(),
79 position.file_id,
80 RangeOrOffset::Offset(position.offset),
81 db,
82 )
83 } else {
84 let (range_or_offset, text_without_caret) = extract_range_or_offset(before);
85 let (db, file_id) = with_single_file(&text_without_caret);
86 (text_without_caret, file_id, range_or_offset, db)
87 };
88
89 let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
90
91 let sema = Semantics::new(&db);
92 let config = AssistConfig::default();
93 let ctx = AssistContext::new(sema, &config, frange);
94 let mut acc = Assists::new_resolved(&ctx);
95 handler(&mut acc, &ctx);
96 let mut res = acc.finish_resolved();
97 let assist = res.pop();
98 match (assist, expected) {
99 (Some(assist), ExpectedResult::After(after)) => {
100 let mut source_change = assist.source_change;
101 let change = source_change.source_file_edits.pop().unwrap();
102
103 let mut actual = db.file_text(change.file_id).as_ref().to_owned();
104 change.edit.apply(&mut actual);
105 assert_eq_text!(after, &actual);
106 }
107 (Some(assist), ExpectedResult::Target(target)) => {
108 let range = assist.assist.target;
109 assert_eq_text!(&text_without_caret[range], target);
110 }
111 (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),
112 (None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => {
113 panic!("code action is not applicable")
114 }
115 (None, ExpectedResult::NotApplicable) => (),
116 };
117}
118
119#[test]
120fn assist_order_field_struct() {
121 let before = "struct Foo { <|>bar: u32 }";
122 let (before_cursor_pos, before) = extract_offset(before);
123 let (db, file_id) = with_single_file(&before);
124 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
125 let assists = Assist::resolved(&db, &AssistConfig::default(), frange);
126 let mut assists = assists.iter();
127
128 assert_eq!(
129 assists.next().expect("expected assist").assist.label,
130 "Change visibility to pub(crate)"
131 );
132 assert_eq!(assists.next().expect("expected assist").assist.label, "Add `#[derive]`");
133}
134
135#[test]
136fn assist_order_if_expr() {
137 let before = "
138 pub fn test_some_range(a: int) -> bool {
139 if let 2..6 = <|>5<|> {
140 true
141 } else {
142 false
143 }
144 }";
145 let (range, before) = extract_range(before);
146 let (db, file_id) = with_single_file(&before);
147 let frange = FileRange { file_id, range };
148 let assists = Assist::resolved(&db, &AssistConfig::default(), frange);
149 let mut assists = assists.iter();
150
151 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
152 assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match");
153}
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/tests/generated.rs
index e4fa9ee36..250e56a69 100644
--- a/crates/ra_assists/src/doc_tests/generated.rs
+++ b/crates/ra_assists/src/tests/generated.rs
@@ -1,10 +1,10 @@
1//! Generated file, do not edit by hand, see `xtask/src/codegen` 1//! Generated file, do not edit by hand, see `xtask/src/codegen`
2 2
3use super::check; 3use super::check_doc_test;
4 4
5#[test] 5#[test]
6fn doctest_add_custom_impl() { 6fn doctest_add_custom_impl() {
7 check( 7 check_doc_test(
8 "add_custom_impl", 8 "add_custom_impl",
9 r#####" 9 r#####"
10#[derive(Deb<|>ug, Display)] 10#[derive(Deb<|>ug, Display)]
@@ -15,7 +15,7 @@ struct S;
15struct S; 15struct S;
16 16
17impl Debug for S { 17impl Debug for S {
18 18 $0
19} 19}
20"#####, 20"#####,
21 ) 21 )
@@ -23,7 +23,7 @@ impl Debug for S {
23 23
24#[test] 24#[test]
25fn doctest_add_derive() { 25fn doctest_add_derive() {
26 check( 26 check_doc_test(
27 "add_derive", 27 "add_derive",
28 r#####" 28 r#####"
29struct Point { 29struct Point {
@@ -32,7 +32,7 @@ struct Point {
32} 32}
33"#####, 33"#####,
34 r#####" 34 r#####"
35#[derive()] 35#[derive($0)]
36struct Point { 36struct Point {
37 x: u32, 37 x: u32,
38 y: u32, 38 y: u32,
@@ -43,7 +43,7 @@ struct Point {
43 43
44#[test] 44#[test]
45fn doctest_add_explicit_type() { 45fn doctest_add_explicit_type() {
46 check( 46 check_doc_test(
47 "add_explicit_type", 47 "add_explicit_type",
48 r#####" 48 r#####"
49fn main() { 49fn main() {
@@ -60,7 +60,7 @@ fn main() {
60 60
61#[test] 61#[test]
62fn doctest_add_function() { 62fn doctest_add_function() {
63 check( 63 check_doc_test(
64 "add_function", 64 "add_function",
65 r#####" 65 r#####"
66struct Baz; 66struct Baz;
@@ -78,7 +78,7 @@ fn foo() {
78} 78}
79 79
80fn bar(arg: &str, baz: Baz) { 80fn bar(arg: &str, baz: Baz) {
81 todo!() 81 ${0:todo!()}
82} 82}
83 83
84"#####, 84"#####,
@@ -87,7 +87,7 @@ fn bar(arg: &str, baz: Baz) {
87 87
88#[test] 88#[test]
89fn doctest_add_hash() { 89fn doctest_add_hash() {
90 check( 90 check_doc_test(
91 "add_hash", 91 "add_hash",
92 r#####" 92 r#####"
93fn main() { 93fn main() {
@@ -104,20 +104,20 @@ fn main() {
104 104
105#[test] 105#[test]
106fn doctest_add_impl() { 106fn doctest_add_impl() {
107 check( 107 check_doc_test(
108 "add_impl", 108 "add_impl",
109 r#####" 109 r#####"
110struct Ctx<T: Clone> { 110struct Ctx<T: Clone> {
111 data: T,<|> 111 data: T,<|>
112} 112}
113"#####, 113"#####,
114 r#####" 114 r#####"
115struct Ctx<T: Clone> { 115struct Ctx<T: Clone> {
116 data: T, 116 data: T,
117} 117}
118 118
119impl<T: Clone> Ctx<T> { 119impl<T: Clone> Ctx<T> {
120 120 $0
121} 121}
122"#####, 122"#####,
123 ) 123 )
@@ -125,7 +125,7 @@ impl<T: Clone> Ctx<T> {
125 125
126#[test] 126#[test]
127fn doctest_add_impl_default_members() { 127fn doctest_add_impl_default_members() {
128 check( 128 check_doc_test(
129 "add_impl_default_members", 129 "add_impl_default_members",
130 r#####" 130 r#####"
131trait Trait { 131trait Trait {
@@ -150,7 +150,7 @@ trait Trait {
150impl Trait for () { 150impl Trait for () {
151 Type X = (); 151 Type X = ();
152 fn foo(&self) {} 152 fn foo(&self) {}
153 fn bar(&self) {} 153 $0fn bar(&self) {}
154 154
155} 155}
156"#####, 156"#####,
@@ -159,7 +159,7 @@ impl Trait for () {
159 159
160#[test] 160#[test]
161fn doctest_add_impl_missing_members() { 161fn doctest_add_impl_missing_members() {
162 check( 162 check_doc_test(
163 "add_impl_missing_members", 163 "add_impl_missing_members",
164 r#####" 164 r#####"
165trait Trait<T> { 165trait Trait<T> {
@@ -180,7 +180,9 @@ trait Trait<T> {
180} 180}
181 181
182impl Trait<u32> for () { 182impl Trait<u32> for () {
183 fn foo(&self) -> u32 { todo!() } 183 fn foo(&self) -> u32 {
184 ${0:todo!()}
185 }
184 186
185} 187}
186"#####, 188"#####,
@@ -189,7 +191,7 @@ impl Trait<u32> for () {
189 191
190#[test] 192#[test]
191fn doctest_add_new() { 193fn doctest_add_new() {
192 check( 194 check_doc_test(
193 "add_new", 195 "add_new",
194 r#####" 196 r#####"
195struct Ctx<T: Clone> { 197struct Ctx<T: Clone> {
@@ -202,7 +204,7 @@ struct Ctx<T: Clone> {
202} 204}
203 205
204impl<T: Clone> Ctx<T> { 206impl<T: Clone> Ctx<T> {
205 fn new(data: T) -> Self { Self { data } } 207 fn $0new(data: T) -> Self { Self { data } }
206} 208}
207 209
208"#####, 210"#####,
@@ -210,8 +212,27 @@ impl<T: Clone> Ctx<T> {
210} 212}
211 213
212#[test] 214#[test]
215fn doctest_add_turbo_fish() {
216 check_doc_test(
217 "add_turbo_fish",
218 r#####"
219fn make<T>() -> T { todo!() }
220fn main() {
221 let x = make<|>();
222}
223"#####,
224 r#####"
225fn make<T>() -> T { todo!() }
226fn main() {
227 let x = make::<${0:_}>();
228}
229"#####,
230 )
231}
232
233#[test]
213fn doctest_apply_demorgan() { 234fn doctest_apply_demorgan() {
214 check( 235 check_doc_test(
215 "apply_demorgan", 236 "apply_demorgan",
216 r#####" 237 r#####"
217fn main() { 238fn main() {
@@ -228,7 +249,7 @@ fn main() {
228 249
229#[test] 250#[test]
230fn doctest_auto_import() { 251fn doctest_auto_import() {
231 check( 252 check_doc_test(
232 "auto_import", 253 "auto_import",
233 r#####" 254 r#####"
234fn main() { 255fn main() {
@@ -248,8 +269,21 @@ pub mod std { pub mod collections { pub struct HashMap { } } }
248} 269}
249 270
250#[test] 271#[test]
272fn doctest_change_return_type_to_result() {
273 check_doc_test(
274 "change_return_type_to_result",
275 r#####"
276fn foo() -> i32<|> { 42i32 }
277"#####,
278 r#####"
279fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
280"#####,
281 )
282}
283
284#[test]
251fn doctest_change_visibility() { 285fn doctest_change_visibility() {
252 check( 286 check_doc_test(
253 "change_visibility", 287 "change_visibility",
254 r#####" 288 r#####"
255<|>fn frobnicate() {} 289<|>fn frobnicate() {}
@@ -262,7 +296,7 @@ pub(crate) fn frobnicate() {}
262 296
263#[test] 297#[test]
264fn doctest_convert_to_guarded_return() { 298fn doctest_convert_to_guarded_return() {
265 check( 299 check_doc_test(
266 "convert_to_guarded_return", 300 "convert_to_guarded_return",
267 r#####" 301 r#####"
268fn main() { 302fn main() {
@@ -286,7 +320,7 @@ fn main() {
286 320
287#[test] 321#[test]
288fn doctest_fill_match_arms() { 322fn doctest_fill_match_arms() {
289 check( 323 check_doc_test(
290 "fill_match_arms", 324 "fill_match_arms",
291 r#####" 325 r#####"
292enum Action { Move { distance: u32 }, Stop } 326enum Action { Move { distance: u32 }, Stop }
@@ -302,7 +336,7 @@ enum Action { Move { distance: u32 }, Stop }
302 336
303fn handle(action: Action) { 337fn handle(action: Action) {
304 match action { 338 match action {
305 Action::Move { distance } => {} 339 $0Action::Move { distance } => {}
306 Action::Stop => {} 340 Action::Stop => {}
307 } 341 }
308} 342}
@@ -311,8 +345,31 @@ fn handle(action: Action) {
311} 345}
312 346
313#[test] 347#[test]
348fn doctest_fix_visibility() {
349 check_doc_test(
350 "fix_visibility",
351 r#####"
352mod m {
353 fn frobnicate() {}
354}
355fn main() {
356 m::frobnicate<|>() {}
357}
358"#####,
359 r#####"
360mod m {
361 $0pub(crate) fn frobnicate() {}
362}
363fn main() {
364 m::frobnicate() {}
365}
366"#####,
367 )
368}
369
370#[test]
314fn doctest_flip_binexpr() { 371fn doctest_flip_binexpr() {
315 check( 372 check_doc_test(
316 "flip_binexpr", 373 "flip_binexpr",
317 r#####" 374 r#####"
318fn main() { 375fn main() {
@@ -329,7 +386,7 @@ fn main() {
329 386
330#[test] 387#[test]
331fn doctest_flip_comma() { 388fn doctest_flip_comma() {
332 check( 389 check_doc_test(
333 "flip_comma", 390 "flip_comma",
334 r#####" 391 r#####"
335fn main() { 392fn main() {
@@ -346,7 +403,7 @@ fn main() {
346 403
347#[test] 404#[test]
348fn doctest_flip_trait_bound() { 405fn doctest_flip_trait_bound() {
349 check( 406 check_doc_test(
350 "flip_trait_bound", 407 "flip_trait_bound",
351 r#####" 408 r#####"
352fn foo<T: Clone +<|> Copy>() { } 409fn foo<T: Clone +<|> Copy>() { }
@@ -359,7 +416,7 @@ fn foo<T: Copy + Clone>() { }
359 416
360#[test] 417#[test]
361fn doctest_inline_local_variable() { 418fn doctest_inline_local_variable() {
362 check( 419 check_doc_test(
363 "inline_local_variable", 420 "inline_local_variable",
364 r#####" 421 r#####"
365fn main() { 422fn main() {
@@ -377,7 +434,7 @@ fn main() {
377 434
378#[test] 435#[test]
379fn doctest_introduce_variable() { 436fn doctest_introduce_variable() {
380 check( 437 check_doc_test(
381 "introduce_variable", 438 "introduce_variable",
382 r#####" 439 r#####"
383fn main() { 440fn main() {
@@ -386,7 +443,7 @@ fn main() {
386"#####, 443"#####,
387 r#####" 444 r#####"
388fn main() { 445fn main() {
389 let var_name = (1 + 2); 446 let $0var_name = (1 + 2);
390 var_name * 4; 447 var_name * 4;
391} 448}
392"#####, 449"#####,
@@ -395,7 +452,7 @@ fn main() {
395 452
396#[test] 453#[test]
397fn doctest_invert_if() { 454fn doctest_invert_if() {
398 check( 455 check_doc_test(
399 "invert_if", 456 "invert_if",
400 r#####" 457 r#####"
401fn main() { 458fn main() {
@@ -412,7 +469,7 @@ fn main() {
412 469
413#[test] 470#[test]
414fn doctest_make_raw_string() { 471fn doctest_make_raw_string() {
415 check( 472 check_doc_test(
416 "make_raw_string", 473 "make_raw_string",
417 r#####" 474 r#####"
418fn main() { 475fn main() {
@@ -429,7 +486,7 @@ fn main() {
429 486
430#[test] 487#[test]
431fn doctest_make_usual_string() { 488fn doctest_make_usual_string() {
432 check( 489 check_doc_test(
433 "make_usual_string", 490 "make_usual_string",
434 r#####" 491 r#####"
435fn main() { 492fn main() {
@@ -446,7 +503,7 @@ fn main() {
446 503
447#[test] 504#[test]
448fn doctest_merge_imports() { 505fn doctest_merge_imports() {
449 check( 506 check_doc_test(
450 "merge_imports", 507 "merge_imports",
451 r#####" 508 r#####"
452use std::<|>fmt::Formatter; 509use std::<|>fmt::Formatter;
@@ -460,7 +517,7 @@ use std::{fmt::Formatter, io};
460 517
461#[test] 518#[test]
462fn doctest_merge_match_arms() { 519fn doctest_merge_match_arms() {
463 check( 520 check_doc_test(
464 "merge_match_arms", 521 "merge_match_arms",
465 r#####" 522 r#####"
466enum Action { Move { distance: u32 }, Stop } 523enum Action { Move { distance: u32 }, Stop }
@@ -486,7 +543,7 @@ fn handle(action: Action) {
486 543
487#[test] 544#[test]
488fn doctest_move_arm_cond_to_match_guard() { 545fn doctest_move_arm_cond_to_match_guard() {
489 check( 546 check_doc_test(
490 "move_arm_cond_to_match_guard", 547 "move_arm_cond_to_match_guard",
491 r#####" 548 r#####"
492enum Action { Move { distance: u32 }, Stop } 549enum Action { Move { distance: u32 }, Stop }
@@ -513,7 +570,7 @@ fn handle(action: Action) {
513 570
514#[test] 571#[test]
515fn doctest_move_bounds_to_where_clause() { 572fn doctest_move_bounds_to_where_clause() {
516 check( 573 check_doc_test(
517 "move_bounds_to_where_clause", 574 "move_bounds_to_where_clause",
518 r#####" 575 r#####"
519fn apply<T, U, <|>F: FnOnce(T) -> U>(f: F, x: T) -> U { 576fn apply<T, U, <|>F: FnOnce(T) -> U>(f: F, x: T) -> U {
@@ -530,7 +587,7 @@ fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
530 587
531#[test] 588#[test]
532fn doctest_move_guard_to_arm_body() { 589fn doctest_move_guard_to_arm_body() {
533 check( 590 check_doc_test(
534 "move_guard_to_arm_body", 591 "move_guard_to_arm_body",
535 r#####" 592 r#####"
536enum Action { Move { distance: u32 }, Stop } 593enum Action { Move { distance: u32 }, Stop }
@@ -557,7 +614,7 @@ fn handle(action: Action) {
557 614
558#[test] 615#[test]
559fn doctest_remove_dbg() { 616fn doctest_remove_dbg() {
560 check( 617 check_doc_test(
561 "remove_dbg", 618 "remove_dbg",
562 r#####" 619 r#####"
563fn main() { 620fn main() {
@@ -574,7 +631,7 @@ fn main() {
574 631
575#[test] 632#[test]
576fn doctest_remove_hash() { 633fn doctest_remove_hash() {
577 check( 634 check_doc_test(
578 "remove_hash", 635 "remove_hash",
579 r#####" 636 r#####"
580fn main() { 637fn main() {
@@ -591,7 +648,7 @@ fn main() {
591 648
592#[test] 649#[test]
593fn doctest_remove_mut() { 650fn doctest_remove_mut() {
594 check( 651 check_doc_test(
595 "remove_mut", 652 "remove_mut",
596 r#####" 653 r#####"
597impl Walrus { 654impl Walrus {
@@ -608,7 +665,7 @@ impl Walrus {
608 665
609#[test] 666#[test]
610fn doctest_reorder_fields() { 667fn doctest_reorder_fields() {
611 check( 668 check_doc_test(
612 "reorder_fields", 669 "reorder_fields",
613 r#####" 670 r#####"
614struct Foo {foo: i32, bar: i32}; 671struct Foo {foo: i32, bar: i32};
@@ -623,7 +680,7 @@ const test: Foo = Foo {foo: 1, bar: 0}
623 680
624#[test] 681#[test]
625fn doctest_replace_if_let_with_match() { 682fn doctest_replace_if_let_with_match() {
626 check( 683 check_doc_test(
627 "replace_if_let_with_match", 684 "replace_if_let_with_match",
628 r#####" 685 r#####"
629enum Action { Move { distance: u32 }, Stop } 686enum Action { Move { distance: u32 }, Stop }
@@ -651,7 +708,7 @@ fn handle(action: Action) {
651 708
652#[test] 709#[test]
653fn doctest_replace_let_with_if_let() { 710fn doctest_replace_let_with_if_let() {
654 check( 711 check_doc_test(
655 "replace_let_with_if_let", 712 "replace_let_with_if_let",
656 r#####" 713 r#####"
657enum Option<T> { Some(T), None } 714enum Option<T> { Some(T), None }
@@ -677,7 +734,7 @@ fn compute() -> Option<i32> { None }
677 734
678#[test] 735#[test]
679fn doctest_replace_qualified_name_with_use() { 736fn doctest_replace_qualified_name_with_use() {
680 check( 737 check_doc_test(
681 "replace_qualified_name_with_use", 738 "replace_qualified_name_with_use",
682 r#####" 739 r#####"
683fn process(map: std::collections::<|>HashMap<String, String>) {} 740fn process(map: std::collections::<|>HashMap<String, String>) {}
@@ -692,7 +749,7 @@ fn process(map: HashMap<String, String>) {}
692 749
693#[test] 750#[test]
694fn doctest_replace_unwrap_with_match() { 751fn doctest_replace_unwrap_with_match() {
695 check( 752 check_doc_test(
696 "replace_unwrap_with_match", 753 "replace_unwrap_with_match",
697 r#####" 754 r#####"
698enum Result<T, E> { Ok(T), Err(E) } 755enum Result<T, E> { Ok(T), Err(E) }
@@ -707,7 +764,7 @@ fn main() {
707 let x: Result<i32, i32> = Result::Ok(92); 764 let x: Result<i32, i32> = Result::Ok(92);
708 let y = match x { 765 let y = match x {
709 Ok(a) => a, 766 Ok(a) => a,
710 _ => unreachable!(), 767 $0_ => unreachable!(),
711 }; 768 };
712} 769}
713"#####, 770"#####,
@@ -716,7 +773,7 @@ fn main() {
716 773
717#[test] 774#[test]
718fn doctest_split_import() { 775fn doctest_split_import() {
719 check( 776 check_doc_test(
720 "split_import", 777 "split_import",
721 r#####" 778 r#####"
722use std::<|>collections::HashMap; 779use std::<|>collections::HashMap;
@@ -726,3 +783,22 @@ use std::{collections::HashMap};
726"#####, 783"#####,
727 ) 784 )
728} 785}
786
787#[test]
788fn doctest_unwrap_block() {
789 check_doc_test(
790 "unwrap_block",
791 r#####"
792fn foo() {
793 if true {<|>
794 println!("foo");
795 }
796}
797"#####,
798 r#####"
799fn foo() {
800 println!("foo");
801}
802"#####,
803 )
804}
diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs
index efd988697..0038a9764 100644
--- a/crates/ra_assists/src/utils.rs
+++ b/crates/ra_assists/src/utils.rs
@@ -1,19 +1,58 @@
1//! Assorted functions shared by several assists. 1//! Assorted functions shared by several assists.
2pub(crate) mod insert_use; 2pub(crate) mod insert_use;
3 3
4use std::iter; 4use std::{iter, ops};
5 5
6use hir::{Adt, Crate, Semantics, Trait, Type}; 6use hir::{Adt, Crate, Enum, ScopeDef, Semantics, Trait, Type};
7use ra_ide_db::RootDatabase; 7use ra_ide_db::RootDatabase;
8use ra_syntax::{ 8use ra_syntax::{
9 ast::{self, make, NameOwner}, 9 ast::{self, make, NameOwner},
10 AstNode, T, 10 AstNode, SyntaxNode, T,
11}; 11};
12use rustc_hash::FxHashSet; 12use rustc_hash::FxHashSet;
13 13
14pub use insert_use::insert_use_statement; 14use crate::assist_config::SnippetCap;
15 15
16pub fn get_missing_impl_items( 16pub(crate) use insert_use::insert_use_statement;
17
18#[derive(Clone, Copy, Debug)]
19pub(crate) enum Cursor<'a> {
20 Replace(&'a SyntaxNode),
21 Before(&'a SyntaxNode),
22}
23
24impl<'a> Cursor<'a> {
25 fn node(self) -> &'a SyntaxNode {
26 match self {
27 Cursor::Replace(node) | Cursor::Before(node) => node,
28 }
29 }
30}
31
32pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor) -> String {
33 assert!(cursor.node().ancestors().any(|it| it == *node));
34 let range = cursor.node().text_range() - node.text_range().start();
35 let range: ops::Range<usize> = range.into();
36
37 let mut placeholder = cursor.node().to_string();
38 escape(&mut placeholder);
39 let tab_stop = match cursor {
40 Cursor::Replace(placeholder) => format!("${{0:{}}}", placeholder),
41 Cursor::Before(placeholder) => format!("$0{}", placeholder),
42 };
43
44 let mut buf = node.to_string();
45 buf.replace_range(range, &tab_stop);
46 return buf;
47
48 fn escape(buf: &mut String) {
49 stdx::replace(buf, '{', r"\{");
50 stdx::replace(buf, '}', r"\}");
51 stdx::replace(buf, '$', r"\$");
52 }
53}
54
55pub fn get_missing_assoc_items(
17 sema: &Semantics<RootDatabase>, 56 sema: &Semantics<RootDatabase>,
18 impl_def: &ast::ImplDef, 57 impl_def: &ast::ImplDef,
19) -> Vec<hir::AssocItem> { 58) -> Vec<hir::AssocItem> {
@@ -23,21 +62,21 @@ pub fn get_missing_impl_items(
23 let mut impl_type = FxHashSet::default(); 62 let mut impl_type = FxHashSet::default();
24 63
25 if let Some(item_list) = impl_def.item_list() { 64 if let Some(item_list) = impl_def.item_list() {
26 for item in item_list.impl_items() { 65 for item in item_list.assoc_items() {
27 match item { 66 match item {
28 ast::ImplItem::FnDef(f) => { 67 ast::AssocItem::FnDef(f) => {
29 if let Some(n) = f.name() { 68 if let Some(n) = f.name() {
30 impl_fns_consts.insert(n.syntax().to_string()); 69 impl_fns_consts.insert(n.syntax().to_string());
31 } 70 }
32 } 71 }
33 72
34 ast::ImplItem::TypeAliasDef(t) => { 73 ast::AssocItem::TypeAliasDef(t) => {
35 if let Some(n) = t.name() { 74 if let Some(n) = t.name() {
36 impl_type.insert(n.syntax().to_string()); 75 impl_type.insert(n.syntax().to_string());
37 } 76 }
38 } 77 }
39 78
40 ast::ImplItem::ConstDef(c) => { 79 ast::AssocItem::ConstDef(c) => {
41 if let Some(n) = c.name() { 80 if let Some(n) = c.name() {
42 impl_fns_consts.insert(n.syntax().to_string()); 81 impl_fns_consts.insert(n.syntax().to_string());
43 } 82 }
@@ -103,7 +142,7 @@ fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
103} 142}
104 143
105#[derive(Clone, Copy)] 144#[derive(Clone, Copy)]
106pub(crate) enum TryEnum { 145pub enum TryEnum {
107 Result, 146 Result,
108 Option, 147 Option,
109} 148}
@@ -111,7 +150,7 @@ pub(crate) enum TryEnum {
111impl TryEnum { 150impl TryEnum {
112 const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result]; 151 const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result];
113 152
114 pub(crate) fn from_ty(sema: &Semantics<RootDatabase>, ty: &Type) -> Option<TryEnum> { 153 pub fn from_ty(sema: &Semantics<RootDatabase>, ty: &Type) -> Option<TryEnum> {
115 let enum_ = match ty.as_adt() { 154 let enum_ = match ty.as_adt() {
116 Some(Adt::Enum(it)) => it, 155 Some(Adt::Enum(it)) => it,
117 _ => return None, 156 _ => return None,
@@ -161,13 +200,19 @@ impl FamousDefs<'_, '_> {
161 #[cfg(test)] 200 #[cfg(test)]
162 pub(crate) const FIXTURE: &'static str = r#" 201 pub(crate) const FIXTURE: &'static str = r#"
163//- /libcore.rs crate:core 202//- /libcore.rs crate:core
164pub mod convert{ 203pub mod convert {
165 pub trait From<T> { 204 pub trait From<T> {
166 fn from(T) -> Self; 205 fn from(T) -> Self;
167 } 206 }
168} 207}
169 208
170pub mod prelude { pub use crate::convert::From } 209pub mod option {
210 pub enum Option<T> { None, Some(T)}
211}
212
213pub mod prelude {
214 pub use crate::{convert::From, option::Option::{self, *}};
215}
171#[prelude_import] 216#[prelude_import]
172pub use prelude::*; 217pub use prelude::*;
173"#; 218"#;
@@ -176,7 +221,25 @@ pub use prelude::*;
176 self.find_trait("core:convert:From") 221 self.find_trait("core:convert:From")
177 } 222 }
178 223
224 pub(crate) fn core_option_Option(&self) -> Option<Enum> {
225 self.find_enum("core:option:Option")
226 }
227
179 fn find_trait(&self, path: &str) -> Option<Trait> { 228 fn find_trait(&self, path: &str) -> Option<Trait> {
229 match self.find_def(path)? {
230 hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
231 _ => None,
232 }
233 }
234
235 fn find_enum(&self, path: &str) -> Option<Enum> {
236 match self.find_def(path)? {
237 hir::ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(it))) => Some(it),
238 _ => None,
239 }
240 }
241
242 fn find_def(&self, path: &str) -> Option<ScopeDef> {
180 let db = self.0.db; 243 let db = self.0.db;
181 let mut path = path.split(':'); 244 let mut path = path.split(':');
182 let trait_ = path.next_back()?; 245 let trait_ = path.next_back()?;
@@ -201,9 +264,6 @@ pub use prelude::*;
201 } 264 }
202 let def = 265 let def =
203 module.scope(db, None).into_iter().find(|(name, _def)| &name.to_string() == trait_)?.1; 266 module.scope(db, None).into_iter().find(|(name, _def)| &name.to_string() == trait_)?.1;
204 match def { 267 Some(def)
205 hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
206 _ => None,
207 }
208 } 268 }
209} 269}
diff --git a/crates/ra_assists/src/utils/insert_use.rs b/crates/ra_assists/src/utils/insert_use.rs
index c507e71e0..0ee43482f 100644
--- a/crates/ra_assists/src/utils/insert_use.rs
+++ b/crates/ra_assists/src/utils/insert_use.rs
@@ -11,17 +11,20 @@ use ra_syntax::{
11}; 11};
12use ra_text_edit::TextEditBuilder; 12use ra_text_edit::TextEditBuilder;
13 13
14use crate::assist_context::AssistContext;
15
14/// Creates and inserts a use statement for the given path to import. 16/// Creates and inserts a use statement for the given path to import.
15/// The use statement is inserted in the scope most appropriate to the 17/// The use statement is inserted in the scope most appropriate to the
16/// the cursor position given, additionally merged with the existing use imports. 18/// the cursor position given, additionally merged with the existing use imports.
17pub fn insert_use_statement( 19pub(crate) fn insert_use_statement(
18 // Ideally the position of the cursor, used to 20 // Ideally the position of the cursor, used to
19 position: &SyntaxNode, 21 position: &SyntaxNode,
20 path_to_import: &ModPath, 22 path_to_import: &ModPath,
21 edit: &mut TextEditBuilder, 23 ctx: &AssistContext,
24 builder: &mut TextEditBuilder,
22) { 25) {
23 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>(); 26 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>();
24 let container = position.ancestors().find_map(|n| { 27 let container = ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| {
25 if let Some(module) = ast::Module::cast(n.clone()) { 28 if let Some(module) = ast::Module::cast(n.clone()) {
26 return module.item_list().map(|it| it.syntax().clone()); 29 return module.item_list().map(|it| it.syntax().clone());
27 } 30 }
@@ -30,7 +33,7 @@ pub fn insert_use_statement(
30 33
31 if let Some(container) = container { 34 if let Some(container) = container {
32 let action = best_action_for_target(container, position.clone(), &target); 35 let action = best_action_for_target(container, position.clone(), &target);
33 make_assist(&action, &target, edit); 36 make_assist(&action, &target, builder);
34 } 37 }
35} 38}
36 39