diff options
Diffstat (limited to 'crates/ra_assists')
48 files changed, 2515 insertions, 1317 deletions
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml index 3bcf58ba4..bd2905f08 100644 --- a/crates/ra_assists/Cargo.toml +++ b/crates/ra_assists/Cargo.toml | |||
@@ -3,6 +3,7 @@ edition = "2018" | |||
3 | name = "ra_assists" | 3 | name = "ra_assists" |
4 | version = "0.1.0" | 4 | version = "0.1.0" |
5 | authors = ["rust-analyzer developers"] | 5 | authors = ["rust-analyzer developers"] |
6 | license = "MIT OR Apache-2.0" | ||
6 | 7 | ||
7 | [lib] | 8 | [lib] |
8 | doctest = false | 9 | doctest = false |
diff --git a/crates/ra_assists/src/assist_config.rs b/crates/ra_assists/src/assist_config.rs index c0a0226fb..cda2abfb9 100644 --- a/crates/ra_assists/src/assist_config.rs +++ b/crates/ra_assists/src/assist_config.rs | |||
@@ -4,9 +4,12 @@ | |||
4 | //! module, and we use to statically check that we only produce snippet | 4 | //! module, and we use to statically check that we only produce snippet |
5 | //! assists if we are allowed to. | 5 | //! assists if we are allowed to. |
6 | 6 | ||
7 | use crate::AssistKind; | ||
8 | |||
7 | #[derive(Clone, Debug, PartialEq, Eq)] | 9 | #[derive(Clone, Debug, PartialEq, Eq)] |
8 | pub struct AssistConfig { | 10 | pub struct AssistConfig { |
9 | pub snippet_cap: Option<SnippetCap>, | 11 | pub snippet_cap: Option<SnippetCap>, |
12 | pub allowed: Option<Vec<AssistKind>>, | ||
10 | } | 13 | } |
11 | 14 | ||
12 | impl AssistConfig { | 15 | impl AssistConfig { |
@@ -22,6 +25,6 @@ pub struct SnippetCap { | |||
22 | 25 | ||
23 | impl Default for AssistConfig { | 26 | impl Default for AssistConfig { |
24 | fn default() -> Self { | 27 | fn default() -> Self { |
25 | AssistConfig { snippet_cap: Some(SnippetCap { _private: () }) } | 28 | AssistConfig { snippet_cap: Some(SnippetCap { _private: () }), allowed: None } |
26 | } | 29 | } |
27 | } | 30 | } |
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs index 5b1a4680b..3407df856 100644 --- a/crates/ra_assists/src/assist_context.rs +++ b/crates/ra_assists/src/assist_context.rs | |||
@@ -1,5 +1,7 @@ | |||
1 | //! See `AssistContext` | 1 | //! See `AssistContext` |
2 | 2 | ||
3 | use std::mem; | ||
4 | |||
3 | use algo::find_covering_element; | 5 | use algo::find_covering_element; |
4 | use hir::Semantics; | 6 | use hir::Semantics; |
5 | use ra_db::{FileId, FileRange}; | 7 | use ra_db::{FileId, FileRange}; |
@@ -17,7 +19,7 @@ use ra_text_edit::TextEditBuilder; | |||
17 | 19 | ||
18 | use crate::{ | 20 | use crate::{ |
19 | assist_config::{AssistConfig, SnippetCap}, | 21 | assist_config::{AssistConfig, SnippetCap}, |
20 | Assist, AssistId, GroupLabel, ResolvedAssist, | 22 | Assist, AssistId, AssistKind, GroupLabel, ResolvedAssist, |
21 | }; | 23 | }; |
22 | 24 | ||
23 | /// `AssistContext` allows to apply an assist or check if it could be applied. | 25 | /// `AssistContext` allows to apply an assist or check if it could be applied. |
@@ -53,7 +55,6 @@ use crate::{ | |||
53 | pub(crate) struct AssistContext<'a> { | 55 | pub(crate) struct AssistContext<'a> { |
54 | pub(crate) config: &'a AssistConfig, | 56 | pub(crate) config: &'a AssistConfig, |
55 | pub(crate) sema: Semantics<'a, RootDatabase>, | 57 | pub(crate) sema: Semantics<'a, RootDatabase>, |
56 | pub(crate) db: &'a RootDatabase, | ||
57 | pub(crate) frange: FileRange, | 58 | pub(crate) frange: FileRange, |
58 | source_file: SourceFile, | 59 | source_file: SourceFile, |
59 | } | 60 | } |
@@ -65,8 +66,11 @@ impl<'a> AssistContext<'a> { | |||
65 | frange: FileRange, | 66 | frange: FileRange, |
66 | ) -> AssistContext<'a> { | 67 | ) -> AssistContext<'a> { |
67 | let source_file = sema.parse(frange.file_id); | 68 | let source_file = sema.parse(frange.file_id); |
68 | let db = sema.db; | 69 | AssistContext { config, sema, frange, source_file } |
69 | AssistContext { config, sema, db, frange, source_file } | 70 | } |
71 | |||
72 | pub(crate) fn db(&self) -> &RootDatabase { | ||
73 | self.sema.db | ||
70 | } | 74 | } |
71 | 75 | ||
72 | // NB, this ignores active selection. | 76 | // NB, this ignores active selection. |
@@ -99,14 +103,26 @@ pub(crate) struct Assists { | |||
99 | resolve: bool, | 103 | resolve: bool, |
100 | file: FileId, | 104 | file: FileId, |
101 | buf: Vec<(Assist, Option<SourceChange>)>, | 105 | buf: Vec<(Assist, Option<SourceChange>)>, |
106 | allowed: Option<Vec<AssistKind>>, | ||
102 | } | 107 | } |
103 | 108 | ||
104 | impl Assists { | 109 | impl Assists { |
105 | pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { | 110 | pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { |
106 | Assists { resolve: true, file: ctx.frange.file_id, buf: Vec::new() } | 111 | Assists { |
112 | resolve: true, | ||
113 | file: ctx.frange.file_id, | ||
114 | buf: Vec::new(), | ||
115 | allowed: ctx.config.allowed.clone(), | ||
116 | } | ||
107 | } | 117 | } |
118 | |||
108 | pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { | 119 | pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { |
109 | Assists { resolve: false, file: ctx.frange.file_id, buf: Vec::new() } | 120 | Assists { |
121 | resolve: false, | ||
122 | file: ctx.frange.file_id, | ||
123 | buf: Vec::new(), | ||
124 | allowed: ctx.config.allowed.clone(), | ||
125 | } | ||
110 | } | 126 | } |
111 | 127 | ||
112 | pub(crate) fn finish_unresolved(self) -> Vec<Assist> { | 128 | pub(crate) fn finish_unresolved(self) -> Vec<Assist> { |
@@ -135,9 +151,13 @@ impl Assists { | |||
135 | target: TextRange, | 151 | target: TextRange, |
136 | f: impl FnOnce(&mut AssistBuilder), | 152 | f: impl FnOnce(&mut AssistBuilder), |
137 | ) -> Option<()> { | 153 | ) -> Option<()> { |
154 | if !self.is_allowed(&id) { | ||
155 | return None; | ||
156 | } | ||
138 | let label = Assist::new(id, label.into(), None, target); | 157 | let label = Assist::new(id, label.into(), None, target); |
139 | self.add_impl(label, f) | 158 | self.add_impl(label, f) |
140 | } | 159 | } |
160 | |||
141 | pub(crate) fn add_group( | 161 | pub(crate) fn add_group( |
142 | &mut self, | 162 | &mut self, |
143 | group: &GroupLabel, | 163 | group: &GroupLabel, |
@@ -146,9 +166,14 @@ impl Assists { | |||
146 | target: TextRange, | 166 | target: TextRange, |
147 | f: impl FnOnce(&mut AssistBuilder), | 167 | f: impl FnOnce(&mut AssistBuilder), |
148 | ) -> Option<()> { | 168 | ) -> Option<()> { |
169 | if !self.is_allowed(&id) { | ||
170 | return None; | ||
171 | } | ||
172 | |||
149 | let label = Assist::new(id, label.into(), Some(group.clone()), target); | 173 | let label = Assist::new(id, label.into(), Some(group.clone()), target); |
150 | self.add_impl(label, f) | 174 | self.add_impl(label, f) |
151 | } | 175 | } |
176 | |||
152 | fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { | 177 | fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { |
153 | let source_change = if self.resolve { | 178 | let source_change = if self.resolve { |
154 | let mut builder = AssistBuilder::new(self.file); | 179 | let mut builder = AssistBuilder::new(self.file); |
@@ -166,17 +191,43 @@ impl Assists { | |||
166 | self.buf.sort_by_key(|(label, _edit)| label.target.len()); | 191 | self.buf.sort_by_key(|(label, _edit)| label.target.len()); |
167 | self.buf | 192 | self.buf |
168 | } | 193 | } |
194 | |||
195 | fn is_allowed(&self, id: &AssistId) -> bool { | ||
196 | match &self.allowed { | ||
197 | Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)), | ||
198 | None => true, | ||
199 | } | ||
200 | } | ||
169 | } | 201 | } |
170 | 202 | ||
171 | pub(crate) struct AssistBuilder { | 203 | pub(crate) struct AssistBuilder { |
172 | edit: TextEditBuilder, | 204 | edit: TextEditBuilder, |
173 | file: FileId, | 205 | file_id: FileId, |
174 | is_snippet: bool, | 206 | is_snippet: bool, |
207 | change: SourceChange, | ||
175 | } | 208 | } |
176 | 209 | ||
177 | impl AssistBuilder { | 210 | impl AssistBuilder { |
178 | pub(crate) fn new(file: FileId) -> AssistBuilder { | 211 | pub(crate) fn new(file_id: FileId) -> AssistBuilder { |
179 | AssistBuilder { edit: TextEditBuilder::default(), file, is_snippet: false } | 212 | AssistBuilder { |
213 | edit: TextEditBuilder::default(), | ||
214 | file_id, | ||
215 | is_snippet: false, | ||
216 | change: SourceChange::default(), | ||
217 | } | ||
218 | } | ||
219 | |||
220 | pub(crate) fn edit_file(&mut self, file_id: FileId) { | ||
221 | self.file_id = file_id; | ||
222 | } | ||
223 | |||
224 | fn commit(&mut self) { | ||
225 | let edit = mem::take(&mut self.edit).finish(); | ||
226 | if !edit.is_empty() { | ||
227 | let new_edit = SourceFileEdit { file_id: self.file_id, edit }; | ||
228 | assert!(!self.change.source_file_edits.iter().any(|it| it.file_id == new_edit.file_id)); | ||
229 | self.change.source_file_edits.push(new_edit); | ||
230 | } | ||
180 | } | 231 | } |
181 | 232 | ||
182 | /// Remove specified `range` of text. | 233 | /// Remove specified `range` of text. |
@@ -231,12 +282,7 @@ impl AssistBuilder { | |||
231 | pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { | 282 | pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { |
232 | let node = rewriter.rewrite_root().unwrap(); | 283 | let node = rewriter.rewrite_root().unwrap(); |
233 | let new = rewriter.rewrite(&node); | 284 | let new = rewriter.rewrite(&node); |
234 | algo::diff(&node, &new).into_text_edit(&mut self.edit) | 285 | algo::diff(&node, &new).into_text_edit(&mut self.edit); |
235 | } | ||
236 | |||
237 | // FIXME: better API | ||
238 | pub(crate) fn set_file(&mut self, assist_file: FileId) { | ||
239 | self.file = assist_file; | ||
240 | } | 286 | } |
241 | 287 | ||
242 | // FIXME: kill this API | 288 | // FIXME: kill this API |
@@ -245,13 +291,12 @@ impl AssistBuilder { | |||
245 | &mut self.edit | 291 | &mut self.edit |
246 | } | 292 | } |
247 | 293 | ||
248 | fn finish(self) -> SourceChange { | 294 | fn finish(mut self) -> SourceChange { |
249 | let edit = self.edit.finish(); | 295 | self.commit(); |
250 | let source_file_edit = SourceFileEdit { file_id: self.file, edit }; | 296 | let mut change = mem::take(&mut self.change); |
251 | let mut res: SourceChange = source_file_edit.into(); | ||
252 | if self.is_snippet { | 297 | if self.is_snippet { |
253 | res.is_snippet = true; | 298 | change.is_snippet = true; |
254 | } | 299 | } |
255 | res | 300 | change |
256 | } | 301 | } |
257 | } | 302 | } |
diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs index 3079a02a2..01adb834c 100644 --- a/crates/ra_assists/src/ast_transform.rs +++ b/crates/ra_assists/src/ast_transform.rs | |||
@@ -2,7 +2,6 @@ | |||
2 | use rustc_hash::FxHashMap; | 2 | use rustc_hash::FxHashMap; |
3 | 3 | ||
4 | use hir::{HirDisplay, PathResolution, SemanticsScope}; | 4 | use hir::{HirDisplay, PathResolution, SemanticsScope}; |
5 | use ra_ide_db::RootDatabase; | ||
6 | use ra_syntax::{ | 5 | use ra_syntax::{ |
7 | algo::SyntaxRewriter, | 6 | algo::SyntaxRewriter, |
8 | ast::{self, AstNode}, | 7 | ast::{self, AstNode}, |
@@ -32,14 +31,14 @@ impl<'a> AstTransform<'a> for NullTransformer { | |||
32 | } | 31 | } |
33 | 32 | ||
34 | pub struct SubstituteTypeParams<'a> { | 33 | pub struct SubstituteTypeParams<'a> { |
35 | source_scope: &'a SemanticsScope<'a, RootDatabase>, | 34 | source_scope: &'a SemanticsScope<'a>, |
36 | substs: FxHashMap<hir::TypeParam, ast::TypeRef>, | 35 | substs: FxHashMap<hir::TypeParam, ast::TypeRef>, |
37 | previous: Box<dyn AstTransform<'a> + 'a>, | 36 | previous: Box<dyn AstTransform<'a> + 'a>, |
38 | } | 37 | } |
39 | 38 | ||
40 | impl<'a> SubstituteTypeParams<'a> { | 39 | impl<'a> SubstituteTypeParams<'a> { |
41 | pub fn for_trait_impl( | 40 | pub fn for_trait_impl( |
42 | source_scope: &'a SemanticsScope<'a, RootDatabase>, | 41 | source_scope: &'a SemanticsScope<'a>, |
43 | // FIXME: there's implicit invariant that `trait_` and `source_scope` match... | 42 | // FIXME: there's implicit invariant that `trait_` and `source_scope` match... |
44 | trait_: hir::Trait, | 43 | trait_: hir::Trait, |
45 | impl_def: ast::ImplDef, | 44 | impl_def: ast::ImplDef, |
@@ -106,6 +105,7 @@ impl<'a> SubstituteTypeParams<'a> { | |||
106 | _ => return None, | 105 | _ => return None, |
107 | }; | 106 | }; |
108 | // FIXME: use `hir::Path::from_src` instead. | 107 | // FIXME: use `hir::Path::from_src` instead. |
108 | #[allow(deprecated)] | ||
109 | let path = hir::Path::from_ast(path)?; | 109 | let path = hir::Path::from_ast(path)?; |
110 | let resolution = self.source_scope.resolve_hir_path(&path)?; | 110 | let resolution = self.source_scope.resolve_hir_path(&path)?; |
111 | match resolution { | 111 | match resolution { |
@@ -125,16 +125,13 @@ impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> { | |||
125 | } | 125 | } |
126 | 126 | ||
127 | pub struct QualifyPaths<'a> { | 127 | pub struct QualifyPaths<'a> { |
128 | target_scope: &'a SemanticsScope<'a, RootDatabase>, | 128 | target_scope: &'a SemanticsScope<'a>, |
129 | source_scope: &'a SemanticsScope<'a, RootDatabase>, | 129 | source_scope: &'a SemanticsScope<'a>, |
130 | previous: Box<dyn AstTransform<'a> + 'a>, | 130 | previous: Box<dyn AstTransform<'a> + 'a>, |
131 | } | 131 | } |
132 | 132 | ||
133 | impl<'a> QualifyPaths<'a> { | 133 | impl<'a> QualifyPaths<'a> { |
134 | pub fn new( | 134 | pub fn new(target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>) -> Self { |
135 | target_scope: &'a SemanticsScope<'a, RootDatabase>, | ||
136 | source_scope: &'a SemanticsScope<'a, RootDatabase>, | ||
137 | ) -> Self { | ||
138 | Self { target_scope, source_scope, previous: Box::new(NullTransformer) } | 135 | Self { target_scope, source_scope, previous: Box::new(NullTransformer) } |
139 | } | 136 | } |
140 | 137 | ||
@@ -150,11 +147,12 @@ impl<'a> QualifyPaths<'a> { | |||
150 | return None; | 147 | return None; |
151 | } | 148 | } |
152 | // FIXME: use `hir::Path::from_src` instead. | 149 | // FIXME: use `hir::Path::from_src` instead. |
150 | #[allow(deprecated)] | ||
153 | let hir_path = hir::Path::from_ast(p.clone()); | 151 | let hir_path = hir::Path::from_ast(p.clone()); |
154 | let resolution = self.source_scope.resolve_hir_path(&hir_path?)?; | 152 | let resolution = self.source_scope.resolve_hir_path(&hir_path?)?; |
155 | match resolution { | 153 | match resolution { |
156 | PathResolution::Def(def) => { | 154 | PathResolution::Def(def) => { |
157 | let found_path = from.find_use_path(self.source_scope.db, def)?; | 155 | let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?; |
158 | let mut path = path_to_ast(found_path); | 156 | let mut path = path_to_ast(found_path); |
159 | 157 | ||
160 | let type_args = p | 158 | let type_args = p |
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs index fa70c8496..acb07e36a 100644 --- a/crates/ra_assists/src/handlers/add_custom_impl.rs +++ b/crates/ra_assists/src/handlers/add_custom_impl.rs | |||
@@ -8,7 +8,7 @@ use stdx::SepBy; | |||
8 | 8 | ||
9 | use crate::{ | 9 | use crate::{ |
10 | assist_context::{AssistContext, Assists}, | 10 | assist_context::{AssistContext, Assists}, |
11 | AssistId, | 11 | AssistId, AssistKind, |
12 | }; | 12 | }; |
13 | 13 | ||
14 | // Assist: add_custom_impl | 14 | // Assist: add_custom_impl |
@@ -52,7 +52,7 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
52 | 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); |
53 | 53 | ||
54 | let target = attr.syntax().text_range(); | 54 | let target = attr.syntax().text_range(); |
55 | acc.add(AssistId("add_custom_impl"), label, target, |builder| { | 55 | acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| { |
56 | let new_attr_input = input | 56 | let new_attr_input = input |
57 | .syntax() | 57 | .syntax() |
58 | .descendants_with_tokens() | 58 | .descendants_with_tokens() |
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs index ab20c6649..39a5321d1 100644 --- a/crates/ra_assists/src/handlers/add_explicit_type.rs +++ b/crates/ra_assists/src/handlers/add_explicit_type.rs | |||
@@ -4,7 +4,7 @@ use ra_syntax::{ | |||
4 | TextRange, | 4 | TextRange, |
5 | }; | 5 | }; |
6 | 6 | ||
7 | use crate::{AssistContext, AssistId, Assists}; | 7 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
8 | 8 | ||
9 | // Assist: add_explicit_type | 9 | // Assist: add_explicit_type |
10 | // | 10 | // |
@@ -57,9 +57,9 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio | |||
57 | return None; | 57 | return None; |
58 | } | 58 | } |
59 | 59 | ||
60 | let inferred_type = ty.display_source_code(ctx.db, module.into()).ok()?; | 60 | let inferred_type = ty.display_source_code(ctx.db(), module.into()).ok()?; |
61 | acc.add( | 61 | acc.add( |
62 | AssistId("add_explicit_type"), | 62 | AssistId("add_explicit_type", AssistKind::RefactorRewrite), |
63 | format!("Insert explicit type `{}`", inferred_type), | 63 | format!("Insert explicit type `{}`", inferred_type), |
64 | pat_range, | 64 | pat_range, |
65 | |builder| match ascribed_ty { | 65 | |builder| match ascribed_ty { |
@@ -195,7 +195,7 @@ struct Test<K, T = u8> { | |||
195 | } | 195 | } |
196 | 196 | ||
197 | fn main() { | 197 | fn main() { |
198 | let test<|> = Test { t: 23, k: 33 }; | 198 | let test<|> = Test { t: 23u8, k: 33 }; |
199 | }"#, | 199 | }"#, |
200 | r#" | 200 | r#" |
201 | struct Test<K, T = u8> { | 201 | struct Test<K, T = u8> { |
@@ -204,7 +204,7 @@ struct Test<K, T = u8> { | |||
204 | } | 204 | } |
205 | 205 | ||
206 | fn main() { | 206 | fn main() { |
207 | let test: Test<i32> = Test { t: 23, k: 33 }; | 207 | let test: Test<i32> = Test { t: 23u8, k: 33 }; |
208 | }"#, | 208 | }"#, |
209 | ); | 209 | ); |
210 | } | 210 | } |
diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs deleted file mode 100644 index eceba7d0a..000000000 --- a/crates/ra_assists/src/handlers/add_impl.rs +++ /dev/null | |||
@@ -1,98 +0,0 @@ | |||
1 | use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner}; | ||
2 | use stdx::{format_to, SepBy}; | ||
3 | |||
4 | use crate::{AssistContext, AssistId, Assists}; | ||
5 | |||
6 | // Assist: add_impl | ||
7 | // | ||
8 | // Adds a new inherent impl for a type. | ||
9 | // | ||
10 | // ``` | ||
11 | // struct Ctx<T: Clone> { | ||
12 | // data: T,<|> | ||
13 | // } | ||
14 | // ``` | ||
15 | // -> | ||
16 | // ``` | ||
17 | // struct Ctx<T: Clone> { | ||
18 | // data: T, | ||
19 | // } | ||
20 | // | ||
21 | // impl<T: Clone> Ctx<T> { | ||
22 | // $0 | ||
23 | // } | ||
24 | // ``` | ||
25 | pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
26 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; | ||
27 | let name = nominal.name()?; | ||
28 | let target = nominal.syntax().text_range(); | ||
29 | acc.add(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), target, |edit| { | ||
30 | let type_params = nominal.type_param_list(); | ||
31 | let start_offset = nominal.syntax().text_range().end(); | ||
32 | let mut buf = String::new(); | ||
33 | buf.push_str("\n\nimpl"); | ||
34 | if let Some(type_params) = &type_params { | ||
35 | format_to!(buf, "{}", type_params.syntax()); | ||
36 | } | ||
37 | buf.push_str(" "); | ||
38 | buf.push_str(name.text().as_str()); | ||
39 | if let Some(type_params) = type_params { | ||
40 | let lifetime_params = type_params | ||
41 | .lifetime_params() | ||
42 | .filter_map(|it| it.lifetime_token()) | ||
43 | .map(|it| it.text().clone()); | ||
44 | let type_params = | ||
45 | type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone()); | ||
46 | |||
47 | let generic_params = lifetime_params.chain(type_params).sep_by(", "); | ||
48 | format_to!(buf, "<{}>", generic_params) | ||
49 | } | ||
50 | match ctx.config.snippet_cap { | ||
51 | Some(cap) => { | ||
52 | buf.push_str(" {\n $0\n}"); | ||
53 | edit.insert_snippet(cap, start_offset, buf); | ||
54 | } | ||
55 | None => { | ||
56 | buf.push_str(" {\n}"); | ||
57 | edit.insert(start_offset, buf); | ||
58 | } | ||
59 | } | ||
60 | }) | ||
61 | } | ||
62 | |||
63 | #[cfg(test)] | ||
64 | mod tests { | ||
65 | use crate::tests::{check_assist, check_assist_target}; | ||
66 | |||
67 | use super::*; | ||
68 | |||
69 | #[test] | ||
70 | fn test_add_impl() { | ||
71 | check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n $0\n}\n"); | ||
72 | check_assist( | ||
73 | add_impl, | ||
74 | "struct Foo<T: Clone> {<|>}", | ||
75 | "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}", | ||
76 | ); | ||
77 | check_assist( | ||
78 | add_impl, | ||
79 | "struct Foo<'a, T: Foo<'a>> {<|>}", | ||
80 | "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}", | ||
81 | ); | ||
82 | } | ||
83 | |||
84 | #[test] | ||
85 | fn add_impl_target() { | ||
86 | check_assist_target( | ||
87 | add_impl, | ||
88 | " | ||
89 | struct SomeThingIrrelevant; | ||
90 | /// Has a lifetime parameter | ||
91 | struct Foo<'a, T: Foo<'a>> {<|>} | ||
92 | struct EvenMoreIrrelevant; | ||
93 | ", | ||
94 | "/// Has a lifetime parameter | ||
95 | struct Foo<'a, T: Foo<'a>> {}", | ||
96 | ); | ||
97 | } | ||
98 | } | ||
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 abacd4065..f185e61e5 100644 --- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs | |||
@@ -12,7 +12,7 @@ use crate::{ | |||
12 | assist_context::{AssistContext, Assists}, | 12 | assist_context::{AssistContext, Assists}, |
13 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | 13 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, |
14 | utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor}, | 14 | utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor}, |
15 | AssistId, | 15 | AssistId, AssistKind, |
16 | }; | 16 | }; |
17 | 17 | ||
18 | #[derive(PartialEq)] | 18 | #[derive(PartialEq)] |
@@ -128,9 +128,9 @@ fn add_missing_impl_members_inner( | |||
128 | let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def) | 128 | let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def) |
129 | .iter() | 129 | .iter() |
130 | .map(|i| match i { | 130 | .map(|i| match i { |
131 | hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value), | 131 | hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db()).value), |
132 | hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAliasDef(i.source(ctx.db).value), | 132 | hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAliasDef(i.source(ctx.db()).value), |
133 | hir::AssocItem::Const(i) => ast::AssocItem::ConstDef(i.source(ctx.db).value), | 133 | hir::AssocItem::Const(i) => ast::AssocItem::ConstDef(i.source(ctx.db()).value), |
134 | }) | 134 | }) |
135 | .filter(|t| def_name(&t).is_some()) | 135 | .filter(|t| def_name(&t).is_some()) |
136 | .filter(|t| match t { | 136 | .filter(|t| match t { |
@@ -147,7 +147,7 @@ fn add_missing_impl_members_inner( | |||
147 | } | 147 | } |
148 | 148 | ||
149 | let target = impl_def.syntax().text_range(); | 149 | let target = impl_def.syntax().text_range(); |
150 | acc.add(AssistId(assist_id), label, target, |builder| { | 150 | acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| { |
151 | let n_existing_items = impl_item_list.assoc_items().count(); | 151 | let n_existing_items = impl_item_list.assoc_items().count(); |
152 | let source_scope = ctx.sema.scope_for_def(trait_); | 152 | let source_scope = ctx.sema.scope_for_def(trait_); |
153 | let target_scope = ctx.sema.scope(impl_item_list.syntax()); | 153 | let target_scope = ctx.sema.scope(impl_item_list.syntax()); |
@@ -158,6 +158,9 @@ fn add_missing_impl_members_inner( | |||
158 | .map(|it| ast_transform::apply(&*ast_transform, it)) | 158 | .map(|it| ast_transform::apply(&*ast_transform, it)) |
159 | .map(|it| match it { | 159 | .map(|it| match it { |
160 | ast::AssocItem::FnDef(def) => ast::AssocItem::FnDef(add_body(def)), | 160 | ast::AssocItem::FnDef(def) => ast::AssocItem::FnDef(add_body(def)), |
161 | ast::AssocItem::TypeAliasDef(def) => { | ||
162 | ast::AssocItem::TypeAliasDef(def.remove_bounds()) | ||
163 | } | ||
161 | _ => it, | 164 | _ => it, |
162 | }) | 165 | }) |
163 | .map(|it| edit::remove_attrs_and_docs(&it)); | 166 | .map(|it| edit::remove_attrs_and_docs(&it)); |
@@ -684,4 +687,26 @@ impl Foo<T> for S<T> { | |||
684 | }"#, | 687 | }"#, |
685 | ) | 688 | ) |
686 | } | 689 | } |
690 | |||
691 | #[test] | ||
692 | fn test_assoc_type_bounds_are_removed() { | ||
693 | check_assist( | ||
694 | add_missing_impl_members, | ||
695 | r#" | ||
696 | trait Tr { | ||
697 | type Ty: Copy + 'static; | ||
698 | } | ||
699 | |||
700 | impl Tr for ()<|> { | ||
701 | }"#, | ||
702 | r#" | ||
703 | trait Tr { | ||
704 | type Ty: Copy + 'static; | ||
705 | } | ||
706 | |||
707 | impl Tr for () { | ||
708 | $0type Ty; | ||
709 | }"#, | ||
710 | ) | ||
711 | } | ||
687 | } | 712 | } |
diff --git a/crates/ra_assists/src/handlers/add_turbo_fish.rs b/crates/ra_assists/src/handlers/add_turbo_fish.rs index 26acf81f2..f7e1a7b05 100644 --- a/crates/ra_assists/src/handlers/add_turbo_fish.rs +++ b/crates/ra_assists/src/handlers/add_turbo_fish.rs | |||
@@ -4,7 +4,7 @@ use test_utils::mark; | |||
4 | 4 | ||
5 | use crate::{ | 5 | use crate::{ |
6 | assist_context::{AssistContext, Assists}, | 6 | assist_context::{AssistContext, Assists}, |
7 | AssistId, | 7 | AssistId, AssistKind, |
8 | }; | 8 | }; |
9 | 9 | ||
10 | // Assist: add_turbo_fish | 10 | // Assist: add_turbo_fish |
@@ -45,12 +45,15 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
45 | mark::hit!(add_turbo_fish_non_generic); | 45 | mark::hit!(add_turbo_fish_non_generic); |
46 | return None; | 46 | return None; |
47 | } | 47 | } |
48 | acc.add(AssistId("add_turbo_fish"), "Add `::<>`", ident.text_range(), |builder| { | 48 | acc.add( |
49 | match ctx.config.snippet_cap { | 49 | AssistId("add_turbo_fish", AssistKind::RefactorRewrite), |
50 | "Add `::<>`", | ||
51 | ident.text_range(), | ||
52 | |builder| match ctx.config.snippet_cap { | ||
50 | Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"), | 53 | Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"), |
51 | None => builder.insert(ident.text_range().end(), "::<_>"), | 54 | None => builder.insert(ident.text_range().end(), "::<_>"), |
52 | } | 55 | }, |
53 | }) | 56 | ) |
54 | } | 57 | } |
55 | 58 | ||
56 | #[cfg(test)] | 59 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/handlers/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs index 233e8fb8e..de701f8b8 100644 --- a/crates/ra_assists/src/handlers/apply_demorgan.rs +++ b/crates/ra_assists/src/handlers/apply_demorgan.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use ra_syntax::ast::{self, AstNode}; | 1 | use ra_syntax::ast::{self, AstNode}; |
2 | 2 | ||
3 | use crate::{utils::invert_boolean_expression, AssistContext, AssistId, Assists}; | 3 | use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists}; |
4 | 4 | ||
5 | // Assist: apply_demorgan | 5 | // Assist: apply_demorgan |
6 | // | 6 | // |
@@ -39,11 +39,16 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
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 | acc.add(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| { | 42 | acc.add( |
43 | edit.replace(op_range, opposite_op); | 43 | AssistId("apply_demorgan", AssistKind::RefactorRewrite), |
44 | edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); | 44 | "Apply De Morgan's law", |
45 | edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); | 45 | op_range, |
46 | }) | 46 | |edit| { |
47 | edit.replace(op_range, opposite_op); | ||
48 | edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); | ||
49 | edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); | ||
50 | }, | ||
51 | ) | ||
47 | } | 52 | } |
48 | 53 | ||
49 | // Return the opposite text for a given logical operator, if it makes sense | 54 | // Return the opposite text for a given logical operator, if it makes sense |
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs index edf96d50e..947be3b9b 100644 --- a/crates/ra_assists/src/handlers/auto_import.rs +++ b/crates/ra_assists/src/handlers/auto_import.rs | |||
@@ -5,7 +5,7 @@ use hir::{ | |||
5 | AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, | 5 | AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, |
6 | Type, | 6 | Type, |
7 | }; | 7 | }; |
8 | use ra_ide_db::{imports_locator::ImportsLocator, RootDatabase}; | 8 | use ra_ide_db::{imports_locator, RootDatabase}; |
9 | use ra_prof::profile; | 9 | use ra_prof::profile; |
10 | use ra_syntax::{ | 10 | use ra_syntax::{ |
11 | ast::{self, AstNode}, | 11 | ast::{self, AstNode}, |
@@ -13,7 +13,9 @@ use ra_syntax::{ | |||
13 | }; | 13 | }; |
14 | use rustc_hash::FxHashSet; | 14 | use rustc_hash::FxHashSet; |
15 | 15 | ||
16 | use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, GroupLabel}; | 16 | use crate::{ |
17 | utils::insert_use_statement, AssistContext, AssistId, AssistKind, Assists, GroupLabel, | ||
18 | }; | ||
17 | 19 | ||
18 | // Assist: auto_import | 20 | // Assist: auto_import |
19 | // | 21 | // |
@@ -35,8 +37,8 @@ use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, Group | |||
35 | // # pub mod std { pub mod collections { pub struct HashMap { } } } | 37 | // # pub mod std { pub mod collections { pub struct HashMap { } } } |
36 | // ``` | 38 | // ``` |
37 | pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 39 | pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
38 | let auto_import_assets = AutoImportAssets::new(&ctx)?; | 40 | let auto_import_assets = AutoImportAssets::new(ctx)?; |
39 | let proposed_imports = auto_import_assets.search_for_imports(ctx.db); | 41 | let proposed_imports = auto_import_assets.search_for_imports(ctx); |
40 | if proposed_imports.is_empty() { | 42 | if proposed_imports.is_empty() { |
41 | return None; | 43 | return None; |
42 | } | 44 | } |
@@ -46,7 +48,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
46 | for import in proposed_imports { | 48 | for import in proposed_imports { |
47 | acc.add_group( | 49 | acc.add_group( |
48 | &group, | 50 | &group, |
49 | AssistId("auto_import"), | 51 | AssistId("auto_import", AssistKind::QuickFix), |
50 | format!("Import `{}`", &import), | 52 | format!("Import `{}`", &import), |
51 | range, | 53 | range, |
52 | |builder| { | 54 | |builder| { |
@@ -127,11 +129,11 @@ impl AutoImportAssets { | |||
127 | GroupLabel(name) | 129 | GroupLabel(name) |
128 | } | 130 | } |
129 | 131 | ||
130 | fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> { | 132 | fn search_for_imports(&self, ctx: &AssistContext) -> BTreeSet<ModPath> { |
131 | let _p = profile("auto_import::search_for_imports"); | 133 | let _p = profile("auto_import::search_for_imports"); |
134 | let db = ctx.db(); | ||
132 | let current_crate = self.module_with_name_to_import.krate(); | 135 | let current_crate = self.module_with_name_to_import.krate(); |
133 | ImportsLocator::new(db) | 136 | imports_locator::find_imports(&ctx.sema, current_crate, &self.get_search_query()) |
134 | .find_imports(&self.get_search_query()) | ||
135 | .into_iter() | 137 | .into_iter() |
136 | .filter_map(|candidate| match &self.import_candidate { | 138 | .filter_map(|candidate| match &self.import_candidate { |
137 | ImportCandidate::TraitAssocItem(assoc_item_type, _) => { | 139 | ImportCandidate::TraitAssocItem(assoc_item_type, _) => { |
@@ -488,16 +490,17 @@ mod tests { | |||
488 | check_assist( | 490 | check_assist( |
489 | auto_import, | 491 | auto_import, |
490 | r" | 492 | r" |
491 | //- /lib.rs crate:crate_with_macro | 493 | //- /lib.rs crate:crate_with_macro |
492 | #[macro_export] | 494 | #[macro_export] |
493 | macro_rules! foo { | 495 | macro_rules! foo { |
494 | () => () | 496 | () => () |
495 | } | 497 | } |
496 | 498 | ||
497 | //- /main.rs crate:main deps:crate_with_macro | 499 | //- /main.rs crate:main deps:crate_with_macro |
498 | fn main() { | 500 | fn main() { |
499 | foo<|> | 501 | foo<|> |
500 | }", | 502 | } |
503 | ", | ||
501 | r"use crate_with_macro::foo; | 504 | r"use crate_with_macro::foo; |
502 | 505 | ||
503 | fn main() { | 506 | fn main() { |
@@ -810,6 +813,146 @@ fn main() { | |||
810 | } | 813 | } |
811 | 814 | ||
812 | #[test] | 815 | #[test] |
816 | fn trait_method_cross_crate() { | ||
817 | check_assist( | ||
818 | auto_import, | ||
819 | r" | ||
820 | //- /main.rs crate:main deps:dep | ||
821 | fn main() { | ||
822 | let test_struct = dep::test_mod::TestStruct {}; | ||
823 | test_struct.test_meth<|>od() | ||
824 | } | ||
825 | //- /dep.rs crate:dep | ||
826 | pub mod test_mod { | ||
827 | pub trait TestTrait { | ||
828 | fn test_method(&self); | ||
829 | } | ||
830 | pub struct TestStruct {} | ||
831 | impl TestTrait for TestStruct { | ||
832 | fn test_method(&self) {} | ||
833 | } | ||
834 | } | ||
835 | ", | ||
836 | r" | ||
837 | use dep::test_mod::TestTrait; | ||
838 | |||
839 | fn main() { | ||
840 | let test_struct = dep::test_mod::TestStruct {}; | ||
841 | test_struct.test_method() | ||
842 | } | ||
843 | ", | ||
844 | ); | ||
845 | } | ||
846 | |||
847 | #[test] | ||
848 | fn assoc_fn_cross_crate() { | ||
849 | check_assist( | ||
850 | auto_import, | ||
851 | r" | ||
852 | //- /main.rs crate:main deps:dep | ||
853 | fn main() { | ||
854 | dep::test_mod::TestStruct::test_func<|>tion | ||
855 | } | ||
856 | //- /dep.rs crate:dep | ||
857 | pub mod test_mod { | ||
858 | pub trait TestTrait { | ||
859 | fn test_function(); | ||
860 | } | ||
861 | pub struct TestStruct {} | ||
862 | impl TestTrait for TestStruct { | ||
863 | fn test_function() {} | ||
864 | } | ||
865 | } | ||
866 | ", | ||
867 | r" | ||
868 | use dep::test_mod::TestTrait; | ||
869 | |||
870 | fn main() { | ||
871 | dep::test_mod::TestStruct::test_function | ||
872 | } | ||
873 | ", | ||
874 | ); | ||
875 | } | ||
876 | |||
877 | #[test] | ||
878 | fn assoc_const_cross_crate() { | ||
879 | check_assist( | ||
880 | auto_import, | ||
881 | r" | ||
882 | //- /main.rs crate:main deps:dep | ||
883 | fn main() { | ||
884 | dep::test_mod::TestStruct::CONST<|> | ||
885 | } | ||
886 | //- /dep.rs crate:dep | ||
887 | pub mod test_mod { | ||
888 | pub trait TestTrait { | ||
889 | const CONST: bool; | ||
890 | } | ||
891 | pub struct TestStruct {} | ||
892 | impl TestTrait for TestStruct { | ||
893 | const CONST: bool = true; | ||
894 | } | ||
895 | } | ||
896 | ", | ||
897 | r" | ||
898 | use dep::test_mod::TestTrait; | ||
899 | |||
900 | fn main() { | ||
901 | dep::test_mod::TestStruct::CONST | ||
902 | } | ||
903 | ", | ||
904 | ); | ||
905 | } | ||
906 | |||
907 | #[test] | ||
908 | fn assoc_fn_as_method_cross_crate() { | ||
909 | check_assist_not_applicable( | ||
910 | auto_import, | ||
911 | r" | ||
912 | //- /main.rs crate:main deps:dep | ||
913 | fn main() { | ||
914 | let test_struct = dep::test_mod::TestStruct {}; | ||
915 | test_struct.test_func<|>tion() | ||
916 | } | ||
917 | //- /dep.rs crate:dep | ||
918 | pub mod test_mod { | ||
919 | pub trait TestTrait { | ||
920 | fn test_function(); | ||
921 | } | ||
922 | pub struct TestStruct {} | ||
923 | impl TestTrait for TestStruct { | ||
924 | fn test_function() {} | ||
925 | } | ||
926 | } | ||
927 | ", | ||
928 | ); | ||
929 | } | ||
930 | |||
931 | #[test] | ||
932 | fn private_trait_cross_crate() { | ||
933 | check_assist_not_applicable( | ||
934 | auto_import, | ||
935 | r" | ||
936 | //- /main.rs crate:main deps:dep | ||
937 | fn main() { | ||
938 | let test_struct = dep::test_mod::TestStruct {}; | ||
939 | test_struct.test_meth<|>od() | ||
940 | } | ||
941 | //- /dep.rs crate:dep | ||
942 | pub mod test_mod { | ||
943 | trait TestTrait { | ||
944 | fn test_method(&self); | ||
945 | } | ||
946 | pub struct TestStruct {} | ||
947 | impl TestTrait for TestStruct { | ||
948 | fn test_method(&self) {} | ||
949 | } | ||
950 | } | ||
951 | ", | ||
952 | ); | ||
953 | } | ||
954 | |||
955 | #[test] | ||
813 | fn not_applicable_for_imported_trait_for_method() { | 956 | fn not_applicable_for_imported_trait_for_method() { |
814 | check_assist_not_applicable( | 957 | check_assist_not_applicable( |
815 | auto_import, | 958 | auto_import, |
@@ -841,4 +984,106 @@ fn main() { | |||
841 | ", | 984 | ", |
842 | ) | 985 | ) |
843 | } | 986 | } |
987 | |||
988 | #[test] | ||
989 | fn dep_import() { | ||
990 | check_assist( | ||
991 | auto_import, | ||
992 | r" | ||
993 | //- /lib.rs crate:dep | ||
994 | pub struct Struct; | ||
995 | |||
996 | //- /main.rs crate:main deps:dep | ||
997 | fn main() { | ||
998 | Struct<|> | ||
999 | } | ||
1000 | ", | ||
1001 | r"use dep::Struct; | ||
1002 | |||
1003 | fn main() { | ||
1004 | Struct | ||
1005 | } | ||
1006 | ", | ||
1007 | ); | ||
1008 | } | ||
1009 | |||
1010 | #[test] | ||
1011 | fn whole_segment() { | ||
1012 | // Tests that only imports whose last segment matches the identifier get suggested. | ||
1013 | check_assist( | ||
1014 | auto_import, | ||
1015 | r" | ||
1016 | //- /lib.rs crate:dep | ||
1017 | pub mod fmt { | ||
1018 | pub trait Display {} | ||
1019 | } | ||
1020 | |||
1021 | pub fn panic_fmt() {} | ||
1022 | |||
1023 | //- /main.rs crate:main deps:dep | ||
1024 | struct S; | ||
1025 | |||
1026 | impl f<|>mt::Display for S {} | ||
1027 | ", | ||
1028 | r"use dep::fmt; | ||
1029 | |||
1030 | struct S; | ||
1031 | |||
1032 | impl fmt::Display for S {} | ||
1033 | ", | ||
1034 | ); | ||
1035 | } | ||
1036 | |||
1037 | #[test] | ||
1038 | fn macro_generated() { | ||
1039 | // Tests that macro-generated items are suggested from external crates. | ||
1040 | check_assist( | ||
1041 | auto_import, | ||
1042 | r" | ||
1043 | //- /lib.rs crate:dep | ||
1044 | macro_rules! mac { | ||
1045 | () => { | ||
1046 | pub struct Cheese; | ||
1047 | }; | ||
1048 | } | ||
1049 | |||
1050 | mac!(); | ||
1051 | |||
1052 | //- /main.rs crate:main deps:dep | ||
1053 | fn main() { | ||
1054 | Cheese<|>; | ||
1055 | } | ||
1056 | ", | ||
1057 | r"use dep::Cheese; | ||
1058 | |||
1059 | fn main() { | ||
1060 | Cheese; | ||
1061 | } | ||
1062 | ", | ||
1063 | ); | ||
1064 | } | ||
1065 | |||
1066 | #[test] | ||
1067 | fn casing() { | ||
1068 | // Tests that differently cased names don't interfere and we only suggest the matching one. | ||
1069 | check_assist( | ||
1070 | auto_import, | ||
1071 | r" | ||
1072 | //- /lib.rs crate:dep | ||
1073 | pub struct FMT; | ||
1074 | pub struct fmt; | ||
1075 | |||
1076 | //- /main.rs crate:main deps:dep | ||
1077 | fn main() { | ||
1078 | FMT<|>; | ||
1079 | } | ||
1080 | ", | ||
1081 | r"use dep::FMT; | ||
1082 | |||
1083 | fn main() { | ||
1084 | FMT; | ||
1085 | } | ||
1086 | ", | ||
1087 | ); | ||
1088 | } | ||
844 | } | 1089 | } |
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 index c6baa0a57..24e5f6963 100644 --- a/crates/ra_assists/src/handlers/change_return_type_to_result.rs +++ b/crates/ra_assists/src/handlers/change_return_type_to_result.rs | |||
@@ -3,7 +3,8 @@ use ra_syntax::{ | |||
3 | AstNode, SyntaxNode, | 3 | AstNode, SyntaxNode, |
4 | }; | 4 | }; |
5 | 5 | ||
6 | use crate::{AssistContext, AssistId, Assists}; | 6 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
7 | use test_utils::mark; | ||
7 | 8 | ||
8 | // Assist: change_return_type_to_result | 9 | // Assist: change_return_type_to_result |
9 | // | 10 | // |
@@ -22,14 +23,19 @@ pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContex | |||
22 | let fn_def = ret_type.syntax().parent().and_then(ast::FnDef::cast)?; | 23 | let fn_def = ret_type.syntax().parent().and_then(ast::FnDef::cast)?; |
23 | 24 | ||
24 | let type_ref = &ret_type.type_ref()?; | 25 | let type_ref = &ret_type.type_ref()?; |
25 | if type_ref.syntax().text().to_string().starts_with("Result<") { | 26 | let ret_type_str = type_ref.syntax().text().to_string(); |
26 | return None; | 27 | let first_part_ret_type = ret_type_str.splitn(2, '<').next(); |
28 | if let Some(ret_type_first_part) = first_part_ret_type { | ||
29 | if ret_type_first_part.ends_with("Result") { | ||
30 | mark::hit!(change_return_type_to_result_simple_return_type_already_result); | ||
31 | return None; | ||
32 | } | ||
27 | } | 33 | } |
28 | 34 | ||
29 | let block_expr = &fn_def.body()?; | 35 | let block_expr = &fn_def.body()?; |
30 | 36 | ||
31 | acc.add( | 37 | acc.add( |
32 | AssistId("change_return_type_to_result"), | 38 | AssistId("change_return_type_to_result", AssistKind::RefactorRewrite), |
33 | "Change return type to Result", | 39 | "Change return type to Result", |
34 | type_ref.syntax().text_range(), | 40 | type_ref.syntax().text_range(), |
35 | |builder| { | 41 | |builder| { |
@@ -297,6 +303,29 @@ mod tests { | |||
297 | } | 303 | } |
298 | 304 | ||
299 | #[test] | 305 | #[test] |
306 | fn change_return_type_to_result_simple_return_type_already_result_std() { | ||
307 | check_assist_not_applicable( | ||
308 | change_return_type_to_result, | ||
309 | r#"fn foo() -> std::result::Result<i32<|>, String> { | ||
310 | let test = "test"; | ||
311 | return 42i32; | ||
312 | }"#, | ||
313 | ); | ||
314 | } | ||
315 | |||
316 | #[test] | ||
317 | fn change_return_type_to_result_simple_return_type_already_result() { | ||
318 | mark::check!(change_return_type_to_result_simple_return_type_already_result); | ||
319 | check_assist_not_applicable( | ||
320 | change_return_type_to_result, | ||
321 | r#"fn foo() -> Result<i32<|>, String> { | ||
322 | let test = "test"; | ||
323 | return 42i32; | ||
324 | }"#, | ||
325 | ); | ||
326 | } | ||
327 | |||
328 | #[test] | ||
300 | fn change_return_type_to_result_simple_with_cursor() { | 329 | fn change_return_type_to_result_simple_with_cursor() { |
301 | check_assist( | 330 | check_assist( |
302 | change_return_type_to_result, | 331 | change_return_type_to_result, |
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs index c21d75be0..4343b423c 100644 --- a/crates/ra_assists/src/handlers/change_visibility.rs +++ b/crates/ra_assists/src/handlers/change_visibility.rs | |||
@@ -2,14 +2,13 @@ use ra_syntax::{ | |||
2 | ast::{self, NameOwner, VisibilityOwner}, | 2 | ast::{self, NameOwner, VisibilityOwner}, |
3 | AstNode, | 3 | AstNode, |
4 | SyntaxKind::{ | 4 | SyntaxKind::{ |
5 | ATTR, COMMENT, CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY, | 5 | CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STATIC_DEF, STRUCT_DEF, TRAIT_DEF, VISIBILITY, |
6 | WHITESPACE, | ||
7 | }, | 6 | }, |
8 | SyntaxNode, TextSize, T, | 7 | T, |
9 | }; | 8 | }; |
10 | use test_utils::mark; | 9 | use test_utils::mark; |
11 | 10 | ||
12 | use crate::{AssistContext, AssistId, Assists}; | 11 | use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists}; |
13 | 12 | ||
14 | // Assist: change_visibility | 13 | // Assist: change_visibility |
15 | // | 14 | // |
@@ -30,14 +29,16 @@ pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Optio | |||
30 | } | 29 | } |
31 | 30 | ||
32 | fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 31 | fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
33 | let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { | 32 | let item_keyword = ctx.token_at_offset().find(|leaf| { |
34 | T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, | 33 | matches!( |
35 | _ => false, | 34 | leaf.kind(), |
35 | T![const] | T![static] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] | ||
36 | ) | ||
36 | }); | 37 | }); |
37 | 38 | ||
38 | let (offset, target) = if let Some(keyword) = item_keyword { | 39 | let (offset, target) = if let Some(keyword) = item_keyword { |
39 | let parent = keyword.parent(); | 40 | let parent = keyword.parent(); |
40 | let def_kws = vec![CONST_DEF, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; | 41 | let def_kws = vec![CONST_DEF, STATIC_DEF, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; |
41 | // Parent is not a definition, can't add visibility | 42 | // Parent is not a definition, can't add visibility |
42 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { | 43 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { |
43 | return None; | 44 | return None; |
@@ -66,27 +67,21 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
66 | return None; | 67 | return None; |
67 | }; | 68 | }; |
68 | 69 | ||
69 | acc.add(AssistId("change_visibility"), "Change visibility to pub(crate)", target, |edit| { | 70 | acc.add( |
70 | edit.insert(offset, "pub(crate) "); | 71 | AssistId("change_visibility", AssistKind::RefactorRewrite), |
71 | }) | 72 | "Change visibility to pub(crate)", |
72 | } | 73 | target, |
73 | 74 | |edit| { | |
74 | fn vis_offset(node: &SyntaxNode) -> TextSize { | 75 | edit.insert(offset, "pub(crate) "); |
75 | node.children_with_tokens() | 76 | }, |
76 | .skip_while(|it| match it.kind() { | 77 | ) |
77 | WHITESPACE | COMMENT | ATTR => true, | ||
78 | _ => false, | ||
79 | }) | ||
80 | .next() | ||
81 | .map(|it| it.text_range().start()) | ||
82 | .unwrap_or_else(|| node.text_range().start()) | ||
83 | } | 78 | } |
84 | 79 | ||
85 | fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> { | 80 | fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> { |
86 | if vis.syntax().text() == "pub" { | 81 | if vis.syntax().text() == "pub" { |
87 | let target = vis.syntax().text_range(); | 82 | let target = vis.syntax().text_range(); |
88 | return acc.add( | 83 | return acc.add( |
89 | AssistId("change_visibility"), | 84 | AssistId("change_visibility", AssistKind::RefactorRewrite), |
90 | "Change Visibility to pub(crate)", | 85 | "Change Visibility to pub(crate)", |
91 | target, | 86 | target, |
92 | |edit| { | 87 | |edit| { |
@@ -97,7 +92,7 @@ fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> { | |||
97 | if vis.syntax().text() == "pub(crate)" { | 92 | if vis.syntax().text() == "pub(crate)" { |
98 | let target = vis.syntax().text_range(); | 93 | let target = vis.syntax().text_range(); |
99 | return acc.add( | 94 | return acc.add( |
100 | AssistId("change_visibility"), | 95 | AssistId("change_visibility", AssistKind::RefactorRewrite), |
101 | "Change visibility to pub", | 96 | "Change visibility to pub", |
102 | target, | 97 | target, |
103 | |edit| { | 98 | |edit| { |
@@ -162,6 +157,11 @@ mod tests { | |||
162 | } | 157 | } |
163 | 158 | ||
164 | #[test] | 159 | #[test] |
160 | fn change_visibility_static() { | ||
161 | check_assist(change_visibility, "<|>static FOO = 3u8;", "pub(crate) static FOO = 3u8;"); | ||
162 | } | ||
163 | |||
164 | #[test] | ||
165 | fn change_visibility_handles_comment_attrs() { | 165 | fn change_visibility_handles_comment_attrs() { |
166 | check_assist( | 166 | check_assist( |
167 | change_visibility, | 167 | change_visibility, |
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs index 4cc75a7ce..330459f3c 100644 --- a/crates/ra_assists/src/handlers/early_return.rs +++ b/crates/ra_assists/src/handlers/early_return.rs | |||
@@ -15,7 +15,7 @@ use ra_syntax::{ | |||
15 | use crate::{ | 15 | use crate::{ |
16 | assist_context::{AssistContext, Assists}, | 16 | assist_context::{AssistContext, Assists}, |
17 | utils::invert_boolean_expression, | 17 | utils::invert_boolean_expression, |
18 | AssistId, | 18 | AssistId, AssistKind, |
19 | }; | 19 | }; |
20 | 20 | ||
21 | // Assist: convert_to_guarded_return | 21 | // Assist: convert_to_guarded_return |
@@ -99,86 +99,92 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) | |||
99 | 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)?; |
100 | 100 | ||
101 | let target = if_expr.syntax().text_range(); | 101 | let target = if_expr.syntax().text_range(); |
102 | acc.add(AssistId("convert_to_guarded_return"), "Convert to guarded return", target, |edit| { | 102 | acc.add( |
103 | let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); | 103 | AssistId("convert_to_guarded_return", AssistKind::RefactorRewrite), |
104 | let new_block = match if_let_pat { | 104 | "Convert to guarded return", |
105 | None => { | 105 | target, |
106 | // If. | 106 | |edit| { |
107 | let new_expr = { | 107 | let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); |
108 | let then_branch = | 108 | let new_block = match if_let_pat { |
109 | make::block_expr(once(make::expr_stmt(early_expression).into()), None); | 109 | None => { |
110 | let cond = invert_boolean_expression(cond_expr); | 110 | // If. |
111 | make::expr_if(make::condition(cond, None), then_branch).indent(if_indent_level) | 111 | let new_expr = { |
112 | }; | 112 | let then_branch = |
113 | replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) | 113 | make::block_expr(once(make::expr_stmt(early_expression).into()), None); |
114 | } | 114 | let cond = invert_boolean_expression(cond_expr); |
115 | Some((path, bound_ident)) => { | 115 | make::expr_if(make::condition(cond, None), then_branch) |
116 | // If-let. | 116 | .indent(if_indent_level) |
117 | let match_expr = { | ||
118 | let happy_arm = { | ||
119 | let pat = make::tuple_struct_pat( | ||
120 | path, | ||
121 | once(make::bind_pat(make::name("it")).into()), | ||
122 | ); | ||
123 | let expr = { | ||
124 | let name_ref = make::name_ref("it"); | ||
125 | let segment = make::path_segment(name_ref); | ||
126 | let path = make::path_unqualified(segment); | ||
127 | make::expr_path(path) | ||
128 | }; | ||
129 | make::match_arm(once(pat.into()), expr) | ||
130 | }; | 117 | }; |
118 | replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) | ||
119 | } | ||
120 | Some((path, bound_ident)) => { | ||
121 | // If-let. | ||
122 | let match_expr = { | ||
123 | let happy_arm = { | ||
124 | let pat = make::tuple_struct_pat( | ||
125 | path, | ||
126 | once(make::bind_pat(make::name("it")).into()), | ||
127 | ); | ||
128 | let expr = { | ||
129 | let name_ref = make::name_ref("it"); | ||
130 | let segment = make::path_segment(name_ref); | ||
131 | let path = make::path_unqualified(segment); | ||
132 | make::expr_path(path) | ||
133 | }; | ||
134 | make::match_arm(once(pat.into()), expr) | ||
135 | }; | ||
131 | 136 | ||
132 | let sad_arm = make::match_arm( | 137 | let sad_arm = make::match_arm( |
133 | // FIXME: would be cool to use `None` or `Err(_)` if appropriate | 138 | // FIXME: would be cool to use `None` or `Err(_)` if appropriate |
134 | once(make::placeholder_pat().into()), | 139 | once(make::placeholder_pat().into()), |
135 | early_expression, | 140 | early_expression, |
136 | ); | 141 | ); |
137 | 142 | ||
138 | make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm])) | 143 | make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm])) |
139 | }; | 144 | }; |
140 | 145 | ||
141 | let let_stmt = make::let_stmt( | 146 | let let_stmt = make::let_stmt( |
142 | make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), | 147 | make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), |
143 | Some(match_expr), | 148 | Some(match_expr), |
149 | ); | ||
150 | let let_stmt = let_stmt.indent(if_indent_level); | ||
151 | replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) | ||
152 | } | ||
153 | }; | ||
154 | edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap()); | ||
155 | |||
156 | fn replace( | ||
157 | new_expr: &SyntaxNode, | ||
158 | then_block: &ast::BlockExpr, | ||
159 | parent_block: &ast::BlockExpr, | ||
160 | if_expr: &ast::IfExpr, | ||
161 | ) -> SyntaxNode { | ||
162 | let then_block_items = then_block.dedent(IndentLevel(1)); | ||
163 | let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); | ||
164 | let end_of_then = | ||
165 | if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { | ||
166 | end_of_then.prev_sibling_or_token().unwrap() | ||
167 | } else { | ||
168 | end_of_then | ||
169 | }; | ||
170 | let mut then_statements = new_expr.children_with_tokens().chain( | ||
171 | then_block_items | ||
172 | .syntax() | ||
173 | .children_with_tokens() | ||
174 | .skip(1) | ||
175 | .take_while(|i| *i != end_of_then), | ||
144 | ); | 176 | ); |
145 | let let_stmt = let_stmt.indent(if_indent_level); | 177 | replace_children( |
146 | replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) | 178 | &parent_block.syntax(), |
179 | RangeInclusive::new( | ||
180 | if_expr.clone().syntax().clone().into(), | ||
181 | if_expr.syntax().clone().into(), | ||
182 | ), | ||
183 | &mut then_statements, | ||
184 | ) | ||
147 | } | 185 | } |
148 | }; | 186 | }, |
149 | edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap()); | 187 | ) |
150 | |||
151 | fn replace( | ||
152 | new_expr: &SyntaxNode, | ||
153 | then_block: &ast::BlockExpr, | ||
154 | parent_block: &ast::BlockExpr, | ||
155 | if_expr: &ast::IfExpr, | ||
156 | ) -> SyntaxNode { | ||
157 | let then_block_items = then_block.dedent(IndentLevel::from(1)); | ||
158 | let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); | ||
159 | let end_of_then = | ||
160 | if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { | ||
161 | end_of_then.prev_sibling_or_token().unwrap() | ||
162 | } else { | ||
163 | end_of_then | ||
164 | }; | ||
165 | let mut then_statements = new_expr.children_with_tokens().chain( | ||
166 | then_block_items | ||
167 | .syntax() | ||
168 | .children_with_tokens() | ||
169 | .skip(1) | ||
170 | .take_while(|i| *i != end_of_then), | ||
171 | ); | ||
172 | replace_children( | ||
173 | &parent_block.syntax(), | ||
174 | RangeInclusive::new( | ||
175 | if_expr.clone().syntax().clone().into(), | ||
176 | if_expr.syntax().clone().into(), | ||
177 | ), | ||
178 | &mut then_statements, | ||
179 | ) | ||
180 | } | ||
181 | }) | ||
182 | } | 188 | } |
183 | 189 | ||
184 | #[cfg(test)] | 190 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs new file mode 100644 index 000000000..2b8e273b3 --- /dev/null +++ b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs | |||
@@ -0,0 +1,321 @@ | |||
1 | use hir::{EnumVariant, Module, ModuleDef, Name}; | ||
2 | use ra_db::FileId; | ||
3 | use ra_fmt::leading_indent; | ||
4 | use ra_ide_db::{defs::Definition, search::Reference, RootDatabase}; | ||
5 | use ra_syntax::{ | ||
6 | algo::find_node_at_offset, | ||
7 | ast::{self, ArgListOwner, AstNode, NameOwner, VisibilityOwner}, | ||
8 | SourceFile, SyntaxNode, TextRange, TextSize, | ||
9 | }; | ||
10 | use rustc_hash::FxHashSet; | ||
11 | |||
12 | use crate::{ | ||
13 | assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId, | ||
14 | AssistKind, Assists, | ||
15 | }; | ||
16 | |||
17 | // Assist: extract_struct_from_enum_variant | ||
18 | // | ||
19 | // Extracts a struct from enum variant. | ||
20 | // | ||
21 | // ``` | ||
22 | // enum A { <|>One(u32, u32) } | ||
23 | // ``` | ||
24 | // -> | ||
25 | // ``` | ||
26 | // struct One(pub u32, pub u32); | ||
27 | // | ||
28 | // enum A { One(One) } | ||
29 | // ``` | ||
30 | pub(crate) fn extract_struct_from_enum_variant( | ||
31 | acc: &mut Assists, | ||
32 | ctx: &AssistContext, | ||
33 | ) -> Option<()> { | ||
34 | let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?; | ||
35 | let field_list = match variant.kind() { | ||
36 | ast::StructKind::Tuple(field_list) => field_list, | ||
37 | _ => return None, | ||
38 | }; | ||
39 | let variant_name = variant.name()?.to_string(); | ||
40 | let variant_hir = ctx.sema.to_def(&variant)?; | ||
41 | if existing_struct_def(ctx.db(), &variant_name, &variant_hir) { | ||
42 | return None; | ||
43 | } | ||
44 | let enum_ast = variant.parent_enum(); | ||
45 | let visibility = enum_ast.visibility(); | ||
46 | let enum_hir = ctx.sema.to_def(&enum_ast)?; | ||
47 | let variant_hir_name = variant_hir.name(ctx.db()); | ||
48 | let enum_module_def = ModuleDef::from(enum_hir); | ||
49 | let current_module = enum_hir.module(ctx.db()); | ||
50 | let target = variant.syntax().text_range(); | ||
51 | acc.add( | ||
52 | AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite), | ||
53 | "Extract struct from enum variant", | ||
54 | target, | ||
55 | |builder| { | ||
56 | let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir)); | ||
57 | let res = definition.find_usages(&ctx.sema, None); | ||
58 | let start_offset = variant.parent_enum().syntax().text_range().start(); | ||
59 | let mut visited_modules_set = FxHashSet::default(); | ||
60 | visited_modules_set.insert(current_module); | ||
61 | for reference in res { | ||
62 | let source_file = ctx.sema.parse(reference.file_range.file_id); | ||
63 | update_reference( | ||
64 | ctx, | ||
65 | builder, | ||
66 | reference, | ||
67 | &source_file, | ||
68 | &enum_module_def, | ||
69 | &variant_hir_name, | ||
70 | &mut visited_modules_set, | ||
71 | ); | ||
72 | } | ||
73 | extract_struct_def( | ||
74 | builder, | ||
75 | enum_ast.syntax(), | ||
76 | &variant_name, | ||
77 | &field_list.to_string(), | ||
78 | start_offset, | ||
79 | ctx.frange.file_id, | ||
80 | &visibility, | ||
81 | ); | ||
82 | let list_range = field_list.syntax().text_range(); | ||
83 | update_variant(builder, &variant_name, ctx.frange.file_id, list_range); | ||
84 | }, | ||
85 | ) | ||
86 | } | ||
87 | |||
88 | fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVariant) -> bool { | ||
89 | variant | ||
90 | .parent_enum(db) | ||
91 | .module(db) | ||
92 | .scope(db, None) | ||
93 | .into_iter() | ||
94 | .any(|(name, _)| name.to_string() == variant_name.to_string()) | ||
95 | } | ||
96 | |||
97 | fn insert_import( | ||
98 | ctx: &AssistContext, | ||
99 | builder: &mut AssistBuilder, | ||
100 | path: &ast::PathExpr, | ||
101 | module: &Module, | ||
102 | enum_module_def: &ModuleDef, | ||
103 | variant_hir_name: &Name, | ||
104 | ) -> Option<()> { | ||
105 | let db = ctx.db(); | ||
106 | let mod_path = module.find_use_path(db, enum_module_def.clone()); | ||
107 | if let Some(mut mod_path) = mod_path { | ||
108 | mod_path.segments.pop(); | ||
109 | mod_path.segments.push(variant_hir_name.clone()); | ||
110 | insert_use_statement(path.syntax(), &mod_path, ctx, builder.text_edit_builder()); | ||
111 | } | ||
112 | Some(()) | ||
113 | } | ||
114 | |||
115 | fn extract_struct_def( | ||
116 | builder: &mut AssistBuilder, | ||
117 | enum_ast: &SyntaxNode, | ||
118 | variant_name: &str, | ||
119 | variant_list: &str, | ||
120 | start_offset: TextSize, | ||
121 | file_id: FileId, | ||
122 | visibility: &Option<ast::Visibility>, | ||
123 | ) -> Option<()> { | ||
124 | let visibility_string = if let Some(visibility) = visibility { | ||
125 | format!("{} ", visibility.to_string()) | ||
126 | } else { | ||
127 | "".to_string() | ||
128 | }; | ||
129 | let indent = if let Some(indent) = leading_indent(enum_ast) { | ||
130 | indent.to_string() | ||
131 | } else { | ||
132 | "".to_string() | ||
133 | }; | ||
134 | let struct_def = format!( | ||
135 | r#"{}struct {}{}; | ||
136 | |||
137 | {}"#, | ||
138 | visibility_string, | ||
139 | variant_name, | ||
140 | list_with_visibility(variant_list), | ||
141 | indent | ||
142 | ); | ||
143 | builder.edit_file(file_id); | ||
144 | builder.insert(start_offset, struct_def); | ||
145 | Some(()) | ||
146 | } | ||
147 | |||
148 | fn update_variant( | ||
149 | builder: &mut AssistBuilder, | ||
150 | variant_name: &str, | ||
151 | file_id: FileId, | ||
152 | list_range: TextRange, | ||
153 | ) -> Option<()> { | ||
154 | let inside_variant_range = TextRange::new( | ||
155 | list_range.start().checked_add(TextSize::from(1))?, | ||
156 | list_range.end().checked_sub(TextSize::from(1))?, | ||
157 | ); | ||
158 | builder.edit_file(file_id); | ||
159 | builder.replace(inside_variant_range, variant_name); | ||
160 | Some(()) | ||
161 | } | ||
162 | |||
163 | fn update_reference( | ||
164 | ctx: &AssistContext, | ||
165 | builder: &mut AssistBuilder, | ||
166 | reference: Reference, | ||
167 | source_file: &SourceFile, | ||
168 | enum_module_def: &ModuleDef, | ||
169 | variant_hir_name: &Name, | ||
170 | visited_modules_set: &mut FxHashSet<Module>, | ||
171 | ) -> Option<()> { | ||
172 | let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>( | ||
173 | source_file.syntax(), | ||
174 | reference.file_range.range.start(), | ||
175 | )?; | ||
176 | let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; | ||
177 | let list = call.arg_list()?; | ||
178 | let segment = path_expr.path()?.segment()?; | ||
179 | let module = ctx.sema.scope(&path_expr.syntax()).module()?; | ||
180 | let list_range = list.syntax().text_range(); | ||
181 | let inside_list_range = TextRange::new( | ||
182 | list_range.start().checked_add(TextSize::from(1))?, | ||
183 | list_range.end().checked_sub(TextSize::from(1))?, | ||
184 | ); | ||
185 | builder.edit_file(reference.file_range.file_id); | ||
186 | if !visited_modules_set.contains(&module) { | ||
187 | if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name) | ||
188 | .is_some() | ||
189 | { | ||
190 | visited_modules_set.insert(module); | ||
191 | } | ||
192 | } | ||
193 | builder.replace(inside_list_range, format!("{}{}", segment, list)); | ||
194 | Some(()) | ||
195 | } | ||
196 | |||
197 | fn list_with_visibility(list: &str) -> String { | ||
198 | list.split(',') | ||
199 | .map(|part| { | ||
200 | let index = if part.chars().next().unwrap() == '(' { 1usize } else { 0 }; | ||
201 | let mut mod_part = part.trim().to_string(); | ||
202 | mod_part.insert_str(index, "pub "); | ||
203 | mod_part | ||
204 | }) | ||
205 | .collect::<Vec<String>>() | ||
206 | .join(", ") | ||
207 | } | ||
208 | |||
209 | #[cfg(test)] | ||
210 | mod tests { | ||
211 | |||
212 | use crate::{ | ||
213 | tests::{check_assist, check_assist_not_applicable}, | ||
214 | utils::FamousDefs, | ||
215 | }; | ||
216 | |||
217 | use super::*; | ||
218 | |||
219 | #[test] | ||
220 | fn test_extract_struct_several_fields() { | ||
221 | check_assist( | ||
222 | extract_struct_from_enum_variant, | ||
223 | "enum A { <|>One(u32, u32) }", | ||
224 | r#"struct One(pub u32, pub u32); | ||
225 | |||
226 | enum A { One(One) }"#, | ||
227 | ); | ||
228 | } | ||
229 | |||
230 | #[test] | ||
231 | fn test_extract_struct_one_field() { | ||
232 | check_assist( | ||
233 | extract_struct_from_enum_variant, | ||
234 | "enum A { <|>One(u32) }", | ||
235 | r#"struct One(pub u32); | ||
236 | |||
237 | enum A { One(One) }"#, | ||
238 | ); | ||
239 | } | ||
240 | |||
241 | #[test] | ||
242 | fn test_extract_struct_pub_visibility() { | ||
243 | check_assist( | ||
244 | extract_struct_from_enum_variant, | ||
245 | "pub enum A { <|>One(u32, u32) }", | ||
246 | r#"pub struct One(pub u32, pub u32); | ||
247 | |||
248 | pub enum A { One(One) }"#, | ||
249 | ); | ||
250 | } | ||
251 | |||
252 | #[test] | ||
253 | fn test_extract_struct_with_complex_imports() { | ||
254 | check_assist( | ||
255 | extract_struct_from_enum_variant, | ||
256 | r#"mod my_mod { | ||
257 | fn another_fn() { | ||
258 | let m = my_other_mod::MyEnum::MyField(1, 1); | ||
259 | } | ||
260 | |||
261 | pub mod my_other_mod { | ||
262 | fn another_fn() { | ||
263 | let m = MyEnum::MyField(1, 1); | ||
264 | } | ||
265 | |||
266 | pub enum MyEnum { | ||
267 | <|>MyField(u8, u8), | ||
268 | } | ||
269 | } | ||
270 | } | ||
271 | |||
272 | fn another_fn() { | ||
273 | let m = my_mod::my_other_mod::MyEnum::MyField(1, 1); | ||
274 | }"#, | ||
275 | r#"use my_mod::my_other_mod::MyField; | ||
276 | |||
277 | mod my_mod { | ||
278 | use my_other_mod::MyField; | ||
279 | |||
280 | fn another_fn() { | ||
281 | let m = my_other_mod::MyEnum::MyField(MyField(1, 1)); | ||
282 | } | ||
283 | |||
284 | pub mod my_other_mod { | ||
285 | fn another_fn() { | ||
286 | let m = MyEnum::MyField(MyField(1, 1)); | ||
287 | } | ||
288 | |||
289 | pub struct MyField(pub u8, pub u8); | ||
290 | |||
291 | pub enum MyEnum { | ||
292 | MyField(MyField), | ||
293 | } | ||
294 | } | ||
295 | } | ||
296 | |||
297 | fn another_fn() { | ||
298 | let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1)); | ||
299 | }"#, | ||
300 | ); | ||
301 | } | ||
302 | |||
303 | fn check_not_applicable(ra_fixture: &str) { | ||
304 | let fixture = | ||
305 | format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); | ||
306 | check_assist_not_applicable(extract_struct_from_enum_variant, &fixture) | ||
307 | } | ||
308 | |||
309 | #[test] | ||
310 | fn test_extract_enum_not_applicable_for_element_with_no_fields() { | ||
311 | check_not_applicable("enum A { <|>One }"); | ||
312 | } | ||
313 | |||
314 | #[test] | ||
315 | fn test_extract_enum_not_applicable_if_struct_exists() { | ||
316 | check_not_applicable( | ||
317 | r#"struct One; | ||
318 | enum A { <|>One(u8) }"#, | ||
319 | ); | ||
320 | } | ||
321 | } | ||
diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/extract_variable.rs index 31d6539f7..481baf1a4 100644 --- a/crates/ra_assists/src/handlers/introduce_variable.rs +++ b/crates/ra_assists/src/handlers/extract_variable.rs | |||
@@ -9,9 +9,9 @@ use ra_syntax::{ | |||
9 | use stdx::format_to; | 9 | use stdx::format_to; |
10 | use test_utils::mark; | 10 | use test_utils::mark; |
11 | 11 | ||
12 | use crate::{AssistContext, AssistId, Assists}; | 12 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
13 | 13 | ||
14 | // Assist: introduce_variable | 14 | // Assist: extract_variable |
15 | // | 15 | // |
16 | // Extracts subexpression into a variable. | 16 | // Extracts subexpression into a variable. |
17 | // | 17 | // |
@@ -27,13 +27,13 @@ use crate::{AssistContext, AssistId, Assists}; | |||
27 | // var_name * 4; | 27 | // var_name * 4; |
28 | // } | 28 | // } |
29 | // ``` | 29 | // ``` |
30 | pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 30 | pub(crate) fn extract_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 | mark::hit!(introduce_var_in_comment_is_not_applicable); | 36 | mark::hit!(extract_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)?; |
@@ -43,65 +43,85 @@ pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Opti | |||
43 | return None; | 43 | return None; |
44 | } | 44 | } |
45 | let target = expr.syntax().text_range(); | 45 | let target = expr.syntax().text_range(); |
46 | acc.add(AssistId("introduce_variable"), "Extract into variable", target, move |edit| { | 46 | acc.add( |
47 | let mut buf = String::new(); | 47 | AssistId("extract_variable", AssistKind::RefactorExtract), |
48 | 48 | "Extract into variable", | |
49 | if wrap_in_block { | 49 | target, |
50 | buf.push_str("{ let var_name = "); | 50 | move |edit| { |
51 | } else { | 51 | let field_shorthand = match expr.syntax().parent().and_then(ast::RecordField::cast) { |
52 | buf.push_str("let var_name = "); | 52 | Some(field) => field.name_ref(), |
53 | }; | 53 | None => None, |
54 | format_to!(buf, "{}", expr.syntax()); | 54 | }; |
55 | 55 | ||
56 | let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); | 56 | let mut buf = String::new(); |
57 | let is_full_stmt = if let Some(expr_stmt) = &full_stmt { | 57 | |
58 | Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) | 58 | let var_name = match &field_shorthand { |
59 | } else { | 59 | Some(it) => it.to_string(), |
60 | false | 60 | None => "var_name".to_string(), |
61 | }; | 61 | }; |
62 | if is_full_stmt { | 62 | let expr_range = match &field_shorthand { |
63 | mark::hit!(test_introduce_var_expr_stmt); | 63 | Some(it) => it.syntax().text_range().cover(expr.syntax().text_range()), |
64 | if full_stmt.unwrap().semicolon_token().is_none() { | 64 | None => expr.syntax().text_range(), |
65 | buf.push_str(";"); | 65 | }; |
66 | |||
67 | if wrap_in_block { | ||
68 | format_to!(buf, "{{ let {} = ", var_name); | ||
69 | } else { | ||
70 | format_to!(buf, "let {} = ", var_name); | ||
71 | }; | ||
72 | format_to!(buf, "{}", expr.syntax()); | ||
73 | |||
74 | let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); | ||
75 | let is_full_stmt = if let Some(expr_stmt) = &full_stmt { | ||
76 | Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) | ||
77 | } else { | ||
78 | false | ||
79 | }; | ||
80 | if is_full_stmt { | ||
81 | mark::hit!(test_extract_var_expr_stmt); | ||
82 | if full_stmt.unwrap().semicolon_token().is_none() { | ||
83 | buf.push_str(";"); | ||
84 | } | ||
85 | match ctx.config.snippet_cap { | ||
86 | Some(cap) => { | ||
87 | let snip = buf | ||
88 | .replace(&format!("let {}", var_name), &format!("let $0{}", var_name)); | ||
89 | edit.replace_snippet(cap, expr_range, snip) | ||
90 | } | ||
91 | None => edit.replace(expr_range, buf), | ||
92 | } | ||
93 | return; | ||
94 | } | ||
95 | |||
96 | buf.push_str(";"); | ||
97 | |||
98 | // We want to maintain the indent level, | ||
99 | // but we do not want to duplicate possible | ||
100 | // extra newlines in the indent block | ||
101 | let text = indent.text(); | ||
102 | if text.starts_with('\n') { | ||
103 | buf.push_str("\n"); | ||
104 | buf.push_str(text.trim_start_matches('\n')); | ||
105 | } else { | ||
106 | buf.push_str(text); | ||
66 | } | 107 | } |
67 | let offset = expr.syntax().text_range(); | 108 | |
109 | edit.replace(expr_range, var_name.clone()); | ||
110 | let offset = anchor_stmt.text_range().start(); | ||
68 | match ctx.config.snippet_cap { | 111 | match ctx.config.snippet_cap { |
69 | Some(cap) => { | 112 | Some(cap) => { |
70 | let snip = buf.replace("let var_name", "let $0var_name"); | 113 | let snip = |
71 | edit.replace_snippet(cap, offset, snip) | 114 | buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name)); |
115 | edit.insert_snippet(cap, offset, snip) | ||
72 | } | 116 | } |
73 | None => edit.replace(offset, buf), | 117 | None => edit.insert(offset, buf), |
74 | } | 118 | } |
75 | return; | ||
76 | } | ||
77 | 119 | ||
78 | buf.push_str(";"); | 120 | if wrap_in_block { |
79 | 121 | edit.insert(anchor_stmt.text_range().end(), " }"); | |
80 | // We want to maintain the indent level, | ||
81 | // but we do not want to duplicate possible | ||
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) | ||
97 | } | 122 | } |
98 | None => edit.insert(offset, buf), | 123 | }, |
99 | } | 124 | ) |
100 | |||
101 | if wrap_in_block { | ||
102 | edit.insert(anchor_stmt.text_range().end(), " }"); | ||
103 | } | ||
104 | }) | ||
105 | } | 125 | } |
106 | 126 | ||
107 | /// Check whether the node is a valid expression which can be extracted to a variable. | 127 | /// Check whether the node is a valid expression which can be extracted to a variable. |
@@ -118,7 +138,7 @@ fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> { | |||
118 | } | 138 | } |
119 | } | 139 | } |
120 | 140 | ||
121 | /// Returns the syntax node which will follow the freshly introduced var | 141 | /// Returns the syntax node which will follow the freshly extractd var |
122 | /// and a boolean indicating whether we have to wrap it within a { } block | 142 | /// and a boolean indicating whether we have to wrap it within a { } block |
123 | /// to produce correct code. | 143 | /// to produce correct code. |
124 | /// It can be a statement, the last in a block expression or a wanna be block | 144 | /// It can be a statement, the last in a block expression or a wanna be block |
@@ -127,7 +147,7 @@ fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> { | |||
127 | expr.syntax().ancestors().find_map(|node| { | 147 | expr.syntax().ancestors().find_map(|node| { |
128 | if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) { | 148 | if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) { |
129 | if expr.syntax() == &node { | 149 | if expr.syntax() == &node { |
130 | mark::hit!(test_introduce_var_last_expr); | 150 | mark::hit!(test_extract_var_last_expr); |
131 | return Some((node, false)); | 151 | return Some((node, false)); |
132 | } | 152 | } |
133 | } | 153 | } |
@@ -155,9 +175,9 @@ mod tests { | |||
155 | use super::*; | 175 | use super::*; |
156 | 176 | ||
157 | #[test] | 177 | #[test] |
158 | fn test_introduce_var_simple() { | 178 | fn test_extract_var_simple() { |
159 | check_assist( | 179 | check_assist( |
160 | introduce_variable, | 180 | extract_variable, |
161 | r#" | 181 | r#" |
162 | fn foo() { | 182 | fn foo() { |
163 | foo(<|>1 + 1<|>); | 183 | foo(<|>1 + 1<|>); |
@@ -171,16 +191,16 @@ fn foo() { | |||
171 | } | 191 | } |
172 | 192 | ||
173 | #[test] | 193 | #[test] |
174 | fn introduce_var_in_comment_is_not_applicable() { | 194 | fn extract_var_in_comment_is_not_applicable() { |
175 | mark::check!(introduce_var_in_comment_is_not_applicable); | 195 | mark::check!(extract_var_in_comment_is_not_applicable); |
176 | check_assist_not_applicable(introduce_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }"); | 196 | check_assist_not_applicable(extract_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }"); |
177 | } | 197 | } |
178 | 198 | ||
179 | #[test] | 199 | #[test] |
180 | fn test_introduce_var_expr_stmt() { | 200 | fn test_extract_var_expr_stmt() { |
181 | mark::check!(test_introduce_var_expr_stmt); | 201 | mark::check!(test_extract_var_expr_stmt); |
182 | check_assist( | 202 | check_assist( |
183 | introduce_variable, | 203 | extract_variable, |
184 | r#" | 204 | r#" |
185 | fn foo() { | 205 | fn foo() { |
186 | <|>1 + 1<|>; | 206 | <|>1 + 1<|>; |
@@ -191,7 +211,7 @@ fn foo() { | |||
191 | }"#, | 211 | }"#, |
192 | ); | 212 | ); |
193 | check_assist( | 213 | check_assist( |
194 | introduce_variable, | 214 | extract_variable, |
195 | " | 215 | " |
196 | fn foo() { | 216 | fn foo() { |
197 | <|>{ let x = 0; x }<|> | 217 | <|>{ let x = 0; x }<|> |
@@ -206,9 +226,9 @@ fn foo() { | |||
206 | } | 226 | } |
207 | 227 | ||
208 | #[test] | 228 | #[test] |
209 | fn test_introduce_var_part_of_expr_stmt() { | 229 | fn test_extract_var_part_of_expr_stmt() { |
210 | check_assist( | 230 | check_assist( |
211 | introduce_variable, | 231 | extract_variable, |
212 | " | 232 | " |
213 | fn foo() { | 233 | fn foo() { |
214 | <|>1<|> + 1; | 234 | <|>1<|> + 1; |
@@ -222,38 +242,42 @@ fn foo() { | |||
222 | } | 242 | } |
223 | 243 | ||
224 | #[test] | 244 | #[test] |
225 | fn test_introduce_var_last_expr() { | 245 | fn test_extract_var_last_expr() { |
226 | mark::check!(test_introduce_var_last_expr); | 246 | mark::check!(test_extract_var_last_expr); |
227 | check_assist( | 247 | check_assist( |
228 | introduce_variable, | 248 | extract_variable, |
229 | " | 249 | r#" |
230 | fn foo() { | 250 | fn foo() { |
231 | bar(<|>1 + 1<|>) | 251 | bar(<|>1 + 1<|>) |
232 | }", | 252 | } |
233 | " | 253 | "#, |
254 | r#" | ||
234 | fn foo() { | 255 | fn foo() { |
235 | let $0var_name = 1 + 1; | 256 | let $0var_name = 1 + 1; |
236 | bar(var_name) | 257 | bar(var_name) |
237 | }", | 258 | } |
259 | "#, | ||
238 | ); | 260 | ); |
239 | check_assist( | 261 | check_assist( |
240 | introduce_variable, | 262 | extract_variable, |
241 | " | 263 | r#" |
242 | fn foo() { | 264 | fn foo() { |
243 | <|>bar(1 + 1)<|> | 265 | <|>bar(1 + 1)<|> |
244 | }", | 266 | } |
245 | " | 267 | "#, |
268 | r#" | ||
246 | fn foo() { | 269 | fn foo() { |
247 | let $0var_name = bar(1 + 1); | 270 | let $0var_name = bar(1 + 1); |
248 | var_name | 271 | var_name |
249 | }", | 272 | } |
273 | "#, | ||
250 | ) | 274 | ) |
251 | } | 275 | } |
252 | 276 | ||
253 | #[test] | 277 | #[test] |
254 | fn test_introduce_var_in_match_arm_no_block() { | 278 | fn test_extract_var_in_match_arm_no_block() { |
255 | check_assist( | 279 | check_assist( |
256 | introduce_variable, | 280 | extract_variable, |
257 | " | 281 | " |
258 | fn main() { | 282 | fn main() { |
259 | let x = true; | 283 | let x = true; |
@@ -276,9 +300,9 @@ fn main() { | |||
276 | } | 300 | } |
277 | 301 | ||
278 | #[test] | 302 | #[test] |
279 | fn test_introduce_var_in_match_arm_with_block() { | 303 | fn test_extract_var_in_match_arm_with_block() { |
280 | check_assist( | 304 | check_assist( |
281 | introduce_variable, | 305 | extract_variable, |
282 | " | 306 | " |
283 | fn main() { | 307 | fn main() { |
284 | let x = true; | 308 | let x = true; |
@@ -308,9 +332,9 @@ fn main() { | |||
308 | } | 332 | } |
309 | 333 | ||
310 | #[test] | 334 | #[test] |
311 | fn test_introduce_var_in_closure_no_block() { | 335 | fn test_extract_var_in_closure_no_block() { |
312 | check_assist( | 336 | check_assist( |
313 | introduce_variable, | 337 | extract_variable, |
314 | " | 338 | " |
315 | fn main() { | 339 | fn main() { |
316 | let lambda = |x: u32| <|>x * 2<|>; | 340 | let lambda = |x: u32| <|>x * 2<|>; |
@@ -325,9 +349,9 @@ fn main() { | |||
325 | } | 349 | } |
326 | 350 | ||
327 | #[test] | 351 | #[test] |
328 | fn test_introduce_var_in_closure_with_block() { | 352 | fn test_extract_var_in_closure_with_block() { |
329 | check_assist( | 353 | check_assist( |
330 | introduce_variable, | 354 | extract_variable, |
331 | " | 355 | " |
332 | fn main() { | 356 | fn main() { |
333 | let lambda = |x: u32| { <|>x * 2<|> }; | 357 | let lambda = |x: u32| { <|>x * 2<|> }; |
@@ -342,9 +366,9 @@ fn main() { | |||
342 | } | 366 | } |
343 | 367 | ||
344 | #[test] | 368 | #[test] |
345 | fn test_introduce_var_path_simple() { | 369 | fn test_extract_var_path_simple() { |
346 | check_assist( | 370 | check_assist( |
347 | introduce_variable, | 371 | extract_variable, |
348 | " | 372 | " |
349 | fn main() { | 373 | fn main() { |
350 | let o = <|>Some(true)<|>; | 374 | let o = <|>Some(true)<|>; |
@@ -360,9 +384,9 @@ fn main() { | |||
360 | } | 384 | } |
361 | 385 | ||
362 | #[test] | 386 | #[test] |
363 | fn test_introduce_var_path_method() { | 387 | fn test_extract_var_path_method() { |
364 | check_assist( | 388 | check_assist( |
365 | introduce_variable, | 389 | extract_variable, |
366 | " | 390 | " |
367 | fn main() { | 391 | fn main() { |
368 | let v = <|>bar.foo()<|>; | 392 | let v = <|>bar.foo()<|>; |
@@ -378,9 +402,9 @@ fn main() { | |||
378 | } | 402 | } |
379 | 403 | ||
380 | #[test] | 404 | #[test] |
381 | fn test_introduce_var_return() { | 405 | fn test_extract_var_return() { |
382 | check_assist( | 406 | check_assist( |
383 | introduce_variable, | 407 | extract_variable, |
384 | " | 408 | " |
385 | fn foo() -> u32 { | 409 | fn foo() -> u32 { |
386 | <|>return 2 + 2<|>; | 410 | <|>return 2 + 2<|>; |
@@ -396,9 +420,9 @@ fn foo() -> u32 { | |||
396 | } | 420 | } |
397 | 421 | ||
398 | #[test] | 422 | #[test] |
399 | fn test_introduce_var_does_not_add_extra_whitespace() { | 423 | fn test_extract_var_does_not_add_extra_whitespace() { |
400 | check_assist( | 424 | check_assist( |
401 | introduce_variable, | 425 | extract_variable, |
402 | " | 426 | " |
403 | fn foo() -> u32 { | 427 | fn foo() -> u32 { |
404 | 428 | ||
@@ -417,7 +441,7 @@ fn foo() -> u32 { | |||
417 | ); | 441 | ); |
418 | 442 | ||
419 | check_assist( | 443 | check_assist( |
420 | introduce_variable, | 444 | extract_variable, |
421 | " | 445 | " |
422 | fn foo() -> u32 { | 446 | fn foo() -> u32 { |
423 | 447 | ||
@@ -434,7 +458,7 @@ fn foo() -> u32 { | |||
434 | ); | 458 | ); |
435 | 459 | ||
436 | check_assist( | 460 | check_assist( |
437 | introduce_variable, | 461 | extract_variable, |
438 | " | 462 | " |
439 | fn foo() -> u32 { | 463 | fn foo() -> u32 { |
440 | let foo = 1; | 464 | let foo = 1; |
@@ -460,9 +484,9 @@ fn foo() -> u32 { | |||
460 | } | 484 | } |
461 | 485 | ||
462 | #[test] | 486 | #[test] |
463 | fn test_introduce_var_break() { | 487 | fn test_extract_var_break() { |
464 | check_assist( | 488 | check_assist( |
465 | introduce_variable, | 489 | extract_variable, |
466 | " | 490 | " |
467 | fn main() { | 491 | fn main() { |
468 | let result = loop { | 492 | let result = loop { |
@@ -482,9 +506,9 @@ fn main() { | |||
482 | } | 506 | } |
483 | 507 | ||
484 | #[test] | 508 | #[test] |
485 | fn test_introduce_var_for_cast() { | 509 | fn test_extract_var_for_cast() { |
486 | check_assist( | 510 | check_assist( |
487 | introduce_variable, | 511 | extract_variable, |
488 | " | 512 | " |
489 | fn main() { | 513 | fn main() { |
490 | let v = <|>0f32 as u32<|>; | 514 | let v = <|>0f32 as u32<|>; |
@@ -500,22 +524,48 @@ fn main() { | |||
500 | } | 524 | } |
501 | 525 | ||
502 | #[test] | 526 | #[test] |
503 | fn test_introduce_var_for_return_not_applicable() { | 527 | fn extract_var_field_shorthand() { |
504 | check_assist_not_applicable(introduce_variable, "fn foo() { <|>return<|>; } "); | 528 | check_assist( |
529 | extract_variable, | ||
530 | r#" | ||
531 | struct S { | ||
532 | foo: i32 | ||
533 | } | ||
534 | |||
535 | fn main() { | ||
536 | S { foo: <|>1 + 1<|> } | ||
537 | } | ||
538 | "#, | ||
539 | r#" | ||
540 | struct S { | ||
541 | foo: i32 | ||
542 | } | ||
543 | |||
544 | fn main() { | ||
545 | let $0foo = 1 + 1; | ||
546 | S { foo } | ||
547 | } | ||
548 | "#, | ||
549 | ) | ||
550 | } | ||
551 | |||
552 | #[test] | ||
553 | fn test_extract_var_for_return_not_applicable() { | ||
554 | check_assist_not_applicable(extract_variable, "fn foo() { <|>return<|>; } "); | ||
505 | } | 555 | } |
506 | 556 | ||
507 | #[test] | 557 | #[test] |
508 | fn test_introduce_var_for_break_not_applicable() { | 558 | fn test_extract_var_for_break_not_applicable() { |
509 | check_assist_not_applicable(introduce_variable, "fn main() { loop { <|>break<|>; }; }"); | 559 | check_assist_not_applicable(extract_variable, "fn main() { loop { <|>break<|>; }; }"); |
510 | } | 560 | } |
511 | 561 | ||
512 | // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic | 562 | // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic |
513 | #[test] | 563 | #[test] |
514 | fn introduce_var_target() { | 564 | fn extract_var_target() { |
515 | check_assist_target(introduce_variable, "fn foo() -> u32 { <|>return 2 + 2<|>; }", "2 + 2"); | 565 | check_assist_target(extract_variable, "fn foo() -> u32 { <|>return 2 + 2<|>; }", "2 + 2"); |
516 | 566 | ||
517 | check_assist_target( | 567 | check_assist_target( |
518 | introduce_variable, | 568 | extract_variable, |
519 | " | 569 | " |
520 | fn main() { | 570 | fn main() { |
521 | let x = true; | 571 | let x = true; |
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs index cc303285b..708e1bc6c 100644 --- a/crates/ra_assists/src/handlers/fill_match_arms.rs +++ b/crates/ra_assists/src/handlers/fill_match_arms.rs | |||
@@ -8,7 +8,7 @@ use test_utils::mark; | |||
8 | 8 | ||
9 | use crate::{ | 9 | use crate::{ |
10 | utils::{render_snippet, Cursor, FamousDefs}, | 10 | utils::{render_snippet, Cursor, FamousDefs}, |
11 | AssistContext, AssistId, Assists, | 11 | AssistContext, AssistId, AssistKind, Assists, |
12 | }; | 12 | }; |
13 | 13 | ||
14 | // Assist: fill_match_arms | 14 | // Assist: fill_match_arms |
@@ -51,11 +51,11 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
51 | let module = ctx.sema.scope(expr.syntax()).module()?; | 51 | let module = ctx.sema.scope(expr.syntax()).module()?; |
52 | 52 | ||
53 | 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) { |
54 | let variants = enum_def.variants(ctx.db); | 54 | let variants = enum_def.variants(ctx.db()); |
55 | 55 | ||
56 | let mut variants = variants | 56 | let mut variants = variants |
57 | .into_iter() | 57 | .into_iter() |
58 | .filter_map(|variant| build_pat(ctx.db, module, variant)) | 58 | .filter_map(|variant| build_pat(ctx.db(), module, variant)) |
59 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) | 59 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) |
60 | .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())) |
61 | .collect::<Vec<_>>(); | 61 | .collect::<Vec<_>>(); |
@@ -84,11 +84,11 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
84 | // where each tuple represents a proposed match arm. | 84 | // where each tuple represents a proposed match arm. |
85 | enum_defs | 85 | enum_defs |
86 | .into_iter() | 86 | .into_iter() |
87 | .map(|enum_def| enum_def.variants(ctx.db)) | 87 | .map(|enum_def| enum_def.variants(ctx.db())) |
88 | .multi_cartesian_product() | 88 | .multi_cartesian_product() |
89 | .map(|variants| { | 89 | .map(|variants| { |
90 | let patterns = | 90 | let patterns = |
91 | variants.into_iter().filter_map(|variant| build_pat(ctx.db, module, variant)); | 91 | variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant)); |
92 | ast::Pat::from(make::tuple_pat(patterns)) | 92 | ast::Pat::from(make::tuple_pat(patterns)) |
93 | }) | 93 | }) |
94 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) | 94 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) |
@@ -103,24 +103,37 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
103 | } | 103 | } |
104 | 104 | ||
105 | let target = match_expr.syntax().text_range(); | 105 | let target = match_expr.syntax().text_range(); |
106 | acc.add(AssistId("fill_match_arms"), "Fill match arms", target, |builder| { | 106 | acc.add( |
107 | let new_arm_list = match_arm_list.remove_placeholder(); | 107 | AssistId("fill_match_arms", AssistKind::QuickFix), |
108 | let n_old_arms = new_arm_list.arms().count(); | 108 | "Fill match arms", |
109 | let new_arm_list = new_arm_list.append_arms(missing_arms); | 109 | target, |
110 | let first_new_arm = new_arm_list.arms().nth(n_old_arms); | 110 | |builder| { |
111 | let old_range = match_arm_list.syntax().text_range(); | 111 | let new_arm_list = match_arm_list.remove_placeholder(); |
112 | match (first_new_arm, ctx.config.snippet_cap) { | 112 | let n_old_arms = new_arm_list.arms().count(); |
113 | (Some(first_new_arm), Some(cap)) => { | 113 | let new_arm_list = new_arm_list.append_arms(missing_arms); |
114 | let snippet = render_snippet( | 114 | let first_new_arm = new_arm_list.arms().nth(n_old_arms); |
115 | cap, | 115 | let old_range = match_arm_list.syntax().text_range(); |
116 | new_arm_list.syntax(), | 116 | match (first_new_arm, ctx.config.snippet_cap) { |
117 | Cursor::Before(first_new_arm.syntax()), | 117 | (Some(first_new_arm), Some(cap)) => { |
118 | ); | 118 | let extend_lifetime; |
119 | builder.replace_snippet(cap, old_range, snippet); | 119 | let cursor = match first_new_arm |
120 | } | 120 | .syntax() |
121 | _ => builder.replace(old_range, new_arm_list.to_string()), | 121 | .descendants() |
122 | } | 122 | .find_map(ast::PlaceholderPat::cast) |
123 | }) | 123 | { |
124 | Some(it) => { | ||
125 | extend_lifetime = it.syntax().clone(); | ||
126 | Cursor::Replace(&extend_lifetime) | ||
127 | } | ||
128 | None => Cursor::Before(first_new_arm.syntax()), | ||
129 | }; | ||
130 | let snippet = render_snippet(cap, new_arm_list.syntax(), cursor); | ||
131 | builder.replace_snippet(cap, old_range, snippet); | ||
132 | } | ||
133 | _ => builder.replace(old_range, new_arm_list.to_string()), | ||
134 | } | ||
135 | }, | ||
136 | ) | ||
124 | } | 137 | } |
125 | 138 | ||
126 | fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool { | 139 | fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool { |
@@ -136,8 +149,20 @@ fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool { | |||
136 | } | 149 | } |
137 | 150 | ||
138 | fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool { | 151 | fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool { |
139 | let pat_head = pat.syntax().first_child().map(|node| node.text()); | 152 | let first_node_text = |pat: &Pat| pat.syntax().first_child().map(|node| node.text()); |
140 | let var_head = var.syntax().first_child().map(|node| node.text()); | 153 | |
154 | let pat_head = match pat { | ||
155 | Pat::BindPat(bind_pat) => { | ||
156 | if let Some(p) = bind_pat.pat() { | ||
157 | first_node_text(&p) | ||
158 | } else { | ||
159 | return false; | ||
160 | } | ||
161 | } | ||
162 | pat => first_node_text(pat), | ||
163 | }; | ||
164 | |||
165 | let var_head = first_node_text(var); | ||
141 | 166 | ||
142 | pat_head == var_head | 167 | pat_head == var_head |
143 | } | 168 | } |
@@ -274,30 +299,22 @@ mod tests { | |||
274 | check_assist( | 299 | check_assist( |
275 | fill_match_arms, | 300 | fill_match_arms, |
276 | r#" | 301 | r#" |
277 | enum A { | 302 | enum A { As, Bs, Cs(Option<i32>) } |
278 | As, | 303 | fn main() { |
279 | Bs, | 304 | match A::As<|> { |
280 | Cs(Option<i32>), | 305 | A::Cs(_) | A::Bs => {} |
281 | } | 306 | } |
282 | fn main() { | 307 | } |
283 | match A::As<|> { | 308 | "#, |
284 | A::Cs(_) | A::Bs => {} | ||
285 | } | ||
286 | } | ||
287 | "#, | ||
288 | r#" | 309 | r#" |
289 | enum A { | 310 | enum A { As, Bs, Cs(Option<i32>) } |
290 | As, | 311 | fn main() { |
291 | Bs, | 312 | match A::As { |
292 | Cs(Option<i32>), | 313 | A::Cs(_) | A::Bs => {} |
293 | } | 314 | $0A::As => {} |
294 | fn main() { | 315 | } |
295 | match A::As { | 316 | } |
296 | A::Cs(_) | A::Bs => {} | 317 | "#, |
297 | $0A::As => {} | ||
298 | } | ||
299 | } | ||
300 | "#, | ||
301 | ); | 318 | ); |
302 | } | 319 | } |
303 | 320 | ||
@@ -306,47 +323,55 @@ mod tests { | |||
306 | check_assist( | 323 | check_assist( |
307 | fill_match_arms, | 324 | fill_match_arms, |
308 | r#" | 325 | r#" |
309 | enum A { | 326 | enum A { As, Bs, Cs, Ds(String), Es(B) } |
310 | As, | 327 | enum B { Xs, Ys } |
311 | Bs, | 328 | fn main() { |
312 | Cs, | 329 | match A::As<|> { |
313 | Ds(String), | 330 | A::Bs if 0 < 1 => {} |
314 | Es(B), | 331 | A::Ds(_value) => { let x = 1; } |
315 | } | 332 | A::Es(B::Xs) => (), |
316 | enum B { | 333 | } |
317 | Xs, | 334 | } |
318 | Ys, | 335 | "#, |
319 | } | ||
320 | fn main() { | ||
321 | match A::As<|> { | ||
322 | A::Bs if 0 < 1 => {} | ||
323 | A::Ds(_value) => { let x = 1; } | ||
324 | A::Es(B::Xs) => (), | ||
325 | } | ||
326 | } | ||
327 | "#, | ||
328 | r#" | 336 | r#" |
329 | enum A { | 337 | enum A { As, Bs, Cs, Ds(String), Es(B) } |
330 | As, | 338 | enum B { Xs, Ys } |
331 | Bs, | 339 | fn main() { |
332 | Cs, | 340 | match A::As { |
333 | Ds(String), | 341 | A::Bs if 0 < 1 => {} |
334 | Es(B), | 342 | A::Ds(_value) => { let x = 1; } |
335 | } | 343 | A::Es(B::Xs) => (), |
336 | enum B { | 344 | $0A::As => {} |
337 | Xs, | 345 | A::Cs => {} |
338 | Ys, | 346 | } |
339 | } | 347 | } |
340 | fn main() { | 348 | "#, |
341 | match A::As { | 349 | ); |
342 | A::Bs if 0 < 1 => {} | 350 | } |
343 | A::Ds(_value) => { let x = 1; } | 351 | |
344 | A::Es(B::Xs) => (), | 352 | #[test] |
345 | $0A::As => {} | 353 | fn partial_fill_bind_pat() { |
346 | A::Cs => {} | 354 | check_assist( |
347 | } | 355 | fill_match_arms, |
348 | } | 356 | r#" |
349 | "#, | 357 | enum A { As, Bs, Cs(Option<i32>) } |
358 | fn main() { | ||
359 | match A::As<|> { | ||
360 | A::As(_) => {} | ||
361 | a @ A::Bs(_) => {} | ||
362 | } | ||
363 | } | ||
364 | "#, | ||
365 | r#" | ||
366 | enum A { As, Bs, Cs(Option<i32>) } | ||
367 | fn main() { | ||
368 | match A::As { | ||
369 | A::As(_) => {} | ||
370 | a @ A::Bs(_) => {} | ||
371 | A::Cs(${0:_}) => {} | ||
372 | } | ||
373 | } | ||
374 | "#, | ||
350 | ); | 375 | ); |
351 | } | 376 | } |
352 | 377 | ||
@@ -355,39 +380,27 @@ mod tests { | |||
355 | check_assist( | 380 | check_assist( |
356 | fill_match_arms, | 381 | fill_match_arms, |
357 | r#" | 382 | r#" |
358 | enum A { | 383 | enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } } |
359 | As, | ||
360 | Bs, | ||
361 | Cs(String), | ||
362 | Ds(String, String), | ||
363 | Es { x: usize, y: usize } | ||
364 | } | ||
365 | 384 | ||
366 | fn main() { | 385 | fn main() { |
367 | let a = A::As; | 386 | let a = A::As; |
368 | match a<|> {} | 387 | match a<|> {} |
369 | } | 388 | } |
370 | "#, | 389 | "#, |
371 | r#" | 390 | r#" |
372 | enum A { | 391 | enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } } |
373 | As, | ||
374 | Bs, | ||
375 | Cs(String), | ||
376 | Ds(String, String), | ||
377 | Es { x: usize, y: usize } | ||
378 | } | ||
379 | 392 | ||
380 | fn main() { | 393 | fn main() { |
381 | let a = A::As; | 394 | let a = A::As; |
382 | match a { | 395 | match a { |
383 | $0A::As => {} | 396 | $0A::As => {} |
384 | A::Bs => {} | 397 | A::Bs => {} |
385 | A::Cs(_) => {} | 398 | A::Cs(_) => {} |
386 | A::Ds(_, _) => {} | 399 | A::Ds(_, _) => {} |
387 | A::Es { x, y } => {} | 400 | A::Es { x, y } => {} |
388 | } | 401 | } |
389 | } | 402 | } |
390 | "#, | 403 | "#, |
391 | ); | 404 | ); |
392 | } | 405 | } |
393 | 406 | ||
@@ -717,9 +730,9 @@ mod tests { | |||
717 | fn foo(opt: Option<i32>) { | 730 | fn foo(opt: Option<i32>) { |
718 | match opt<|> { | 731 | match opt<|> { |
719 | } | 732 | } |
720 | }"#; | 733 | } |
721 | let before = | 734 | "#; |
722 | &format!("//- main.rs crate:main deps:core\n{}{}", before, FamousDefs::FIXTURE); | 735 | let before = &format!("//- /main.rs crate:main deps:core{}{}", before, FamousDefs::FIXTURE); |
723 | 736 | ||
724 | check_assist( | 737 | check_assist( |
725 | fill_match_arms, | 738 | fill_match_arms, |
@@ -727,7 +740,7 @@ fn foo(opt: Option<i32>) { | |||
727 | r#" | 740 | r#" |
728 | fn foo(opt: Option<i32>) { | 741 | fn foo(opt: Option<i32>) { |
729 | match opt { | 742 | match opt { |
730 | $0Some(_) => {} | 743 | Some(${0:_}) => {} |
731 | None => {} | 744 | None => {} |
732 | } | 745 | } |
733 | } | 746 | } |
diff --git a/crates/ra_assists/src/handlers/fix_visibility.rs b/crates/ra_assists/src/handlers/fix_visibility.rs index 9ec42f568..e212557c8 100644 --- a/crates/ra_assists/src/handlers/fix_visibility.rs +++ b/crates/ra_assists/src/handlers/fix_visibility.rs | |||
@@ -1,12 +1,8 @@ | |||
1 | use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; | 1 | use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; |
2 | use ra_db::FileId; | 2 | use ra_db::FileId; |
3 | use ra_syntax::{ | 3 | use ra_syntax::{ast, AstNode, TextRange, TextSize}; |
4 | ast, AstNode, | ||
5 | SyntaxKind::{ATTR, COMMENT, WHITESPACE}, | ||
6 | SyntaxNode, TextRange, TextSize, | ||
7 | }; | ||
8 | 4 | ||
9 | use crate::{AssistContext, AssistId, Assists}; | 5 | use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists}; |
10 | 6 | ||
11 | // FIXME: this really should be a fix for diagnostic, rather than an assist. | 7 | // FIXME: this really should be a fix for diagnostic, rather than an assist. |
12 | 8 | ||
@@ -45,14 +41,14 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> O | |||
45 | }; | 41 | }; |
46 | 42 | ||
47 | let current_module = ctx.sema.scope(&path.syntax()).module()?; | 43 | let current_module = ctx.sema.scope(&path.syntax()).module()?; |
48 | let target_module = def.module(ctx.db)?; | 44 | let target_module = def.module(ctx.db())?; |
49 | 45 | ||
50 | let vis = target_module.visibility_of(ctx.db, &def)?; | 46 | let vis = target_module.visibility_of(ctx.db(), &def)?; |
51 | if vis.is_visible_from(ctx.db, current_module.into()) { | 47 | if vis.is_visible_from(ctx.db(), current_module.into()) { |
52 | return None; | 48 | return None; |
53 | }; | 49 | }; |
54 | 50 | ||
55 | let (offset, target, target_file, target_name) = target_data_for_def(ctx.db, def)?; | 51 | let (offset, target, target_file, target_name) = target_data_for_def(ctx.db(), def)?; |
56 | 52 | ||
57 | let missing_visibility = | 53 | let missing_visibility = |
58 | if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; | 54 | if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; |
@@ -62,8 +58,8 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> O | |||
62 | Some(name) => format!("Change visibility of {} to {}", name, missing_visibility), | 58 | Some(name) => format!("Change visibility of {} to {}", name, missing_visibility), |
63 | }; | 59 | }; |
64 | 60 | ||
65 | acc.add(AssistId("fix_visibility"), assist_label, target, |builder| { | 61 | acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| { |
66 | builder.set_file(target_file); | 62 | builder.edit_file(target_file); |
67 | match ctx.config.snippet_cap { | 63 | match ctx.config.snippet_cap { |
68 | Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), | 64 | Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), |
69 | None => builder.insert(offset, format!("{} ", missing_visibility)), | 65 | None => builder.insert(offset, format!("{} ", missing_visibility)), |
@@ -76,16 +72,16 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> | |||
76 | let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?; | 72 | let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?; |
77 | 73 | ||
78 | let current_module = ctx.sema.scope(record_field.syntax()).module()?; | 74 | let current_module = ctx.sema.scope(record_field.syntax()).module()?; |
79 | let visibility = record_field_def.visibility(ctx.db); | 75 | let visibility = record_field_def.visibility(ctx.db()); |
80 | if visibility.is_visible_from(ctx.db, current_module.into()) { | 76 | if visibility.is_visible_from(ctx.db(), current_module.into()) { |
81 | return None; | 77 | return None; |
82 | } | 78 | } |
83 | 79 | ||
84 | let parent = record_field_def.parent_def(ctx.db); | 80 | let parent = record_field_def.parent_def(ctx.db()); |
85 | let parent_name = parent.name(ctx.db); | 81 | let parent_name = parent.name(ctx.db()); |
86 | let target_module = parent.module(ctx.db); | 82 | let target_module = parent.module(ctx.db()); |
87 | 83 | ||
88 | let in_file_source = record_field_def.source(ctx.db); | 84 | let in_file_source = record_field_def.source(ctx.db()); |
89 | let (offset, target) = match in_file_source.value { | 85 | let (offset, target) = match in_file_source.value { |
90 | hir::FieldSource::Named(it) => { | 86 | hir::FieldSource::Named(it) => { |
91 | let s = it.syntax(); | 87 | let s = it.syntax(); |
@@ -99,14 +95,14 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> | |||
99 | 95 | ||
100 | let missing_visibility = | 96 | let missing_visibility = |
101 | if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; | 97 | if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; |
102 | let target_file = in_file_source.file_id.original_file(ctx.db); | 98 | let target_file = in_file_source.file_id.original_file(ctx.db()); |
103 | 99 | ||
104 | let target_name = record_field_def.name(ctx.db); | 100 | let target_name = record_field_def.name(ctx.db()); |
105 | let assist_label = | 101 | let assist_label = |
106 | format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility); | 102 | format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility); |
107 | 103 | ||
108 | acc.add(AssistId("fix_visibility"), assist_label, target, |builder| { | 104 | acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| { |
109 | builder.set_file(target_file); | 105 | builder.edit_file(target_file); |
110 | match ctx.config.snippet_cap { | 106 | match ctx.config.snippet_cap { |
111 | Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), | 107 | Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), |
112 | None => builder.insert(offset, format!("{} ", missing_visibility)), | 108 | None => builder.insert(offset, format!("{} ", missing_visibility)), |
@@ -177,17 +173,6 @@ fn target_data_for_def( | |||
177 | Some((offset, target, target_file, target_name)) | 173 | Some((offset, target, target_file, target_name)) |
178 | } | 174 | } |
179 | 175 | ||
180 | fn vis_offset(node: &SyntaxNode) -> TextSize { | ||
181 | node.children_with_tokens() | ||
182 | .skip_while(|it| match it.kind() { | ||
183 | WHITESPACE | COMMENT | ATTR => true, | ||
184 | _ => false, | ||
185 | }) | ||
186 | .next() | ||
187 | .map(|it| it.text_range().start()) | ||
188 | .unwrap_or_else(|| node.text_range().start()) | ||
189 | } | ||
190 | |||
191 | #[cfg(test)] | 176 | #[cfg(test)] |
192 | mod tests { | 177 | mod tests { |
193 | use crate::tests::{check_assist, check_assist_not_applicable}; | 178 | use crate::tests::{check_assist, check_assist_not_applicable}; |
@@ -255,15 +240,14 @@ mod tests { | |||
255 | check_assist( | 240 | check_assist( |
256 | fix_visibility, | 241 | fix_visibility, |
257 | r" | 242 | r" |
258 | //- /main.rs | 243 | //- /main.rs |
259 | mod foo; | 244 | mod foo; |
260 | fn main() { foo::Foo<|> } | 245 | fn main() { foo::Foo<|> } |
261 | 246 | ||
262 | //- /foo.rs | 247 | //- /foo.rs |
263 | struct Foo; | 248 | struct Foo; |
264 | ", | 249 | ", |
265 | r"$0pub(crate) struct Foo; | 250 | r"$0pub(crate) struct Foo; |
266 | |||
267 | ", | 251 | ", |
268 | ); | 252 | ); |
269 | } | 253 | } |
@@ -279,14 +263,14 @@ mod tests { | |||
279 | ); | 263 | ); |
280 | check_assist( | 264 | check_assist( |
281 | fix_visibility, | 265 | fix_visibility, |
282 | r"//- /lib.rs | 266 | r" |
283 | mod foo; | 267 | //- /lib.rs |
284 | fn main() { foo::Foo { <|>bar: () }; } | 268 | mod foo; |
285 | //- /foo.rs | 269 | fn main() { foo::Foo { <|>bar: () }; } |
286 | pub struct Foo { bar: () } | 270 | //- /foo.rs |
287 | ", | 271 | pub struct Foo { bar: () } |
272 | ", | ||
288 | r"pub struct Foo { $0pub(crate) bar: () } | 273 | r"pub struct Foo { $0pub(crate) bar: () } |
289 | |||
290 | ", | 274 | ", |
291 | ); | 275 | ); |
292 | check_assist_not_applicable( | 276 | check_assist_not_applicable( |
@@ -296,12 +280,13 @@ mod tests { | |||
296 | ); | 280 | ); |
297 | check_assist_not_applicable( | 281 | check_assist_not_applicable( |
298 | fix_visibility, | 282 | fix_visibility, |
299 | r"//- /lib.rs | 283 | r" |
300 | mod foo; | 284 | //- /lib.rs |
301 | fn main() { foo::Foo { <|>bar: () }; } | 285 | mod foo; |
302 | //- /foo.rs | 286 | fn main() { foo::Foo { <|>bar: () }; } |
303 | pub struct Foo { pub bar: () } | 287 | //- /foo.rs |
304 | ", | 288 | pub struct Foo { pub bar: () } |
289 | ", | ||
305 | ); | 290 | ); |
306 | } | 291 | } |
307 | 292 | ||
@@ -316,14 +301,14 @@ mod tests { | |||
316 | ); | 301 | ); |
317 | check_assist( | 302 | check_assist( |
318 | fix_visibility, | 303 | fix_visibility, |
319 | r"//- /lib.rs | 304 | r" |
320 | mod foo; | 305 | //- /lib.rs |
321 | fn main() { foo::Foo::Bar { <|>bar: () }; } | 306 | mod foo; |
322 | //- /foo.rs | 307 | fn main() { foo::Foo::Bar { <|>bar: () }; } |
323 | pub enum Foo { Bar { bar: () } } | 308 | //- /foo.rs |
324 | ", | 309 | pub enum Foo { Bar { bar: () } } |
310 | ", | ||
325 | r"pub enum Foo { Bar { $0pub(crate) bar: () } } | 311 | r"pub enum Foo { Bar { $0pub(crate) bar: () } } |
326 | |||
327 | ", | 312 | ", |
328 | ); | 313 | ); |
329 | check_assist_not_applicable( | 314 | check_assist_not_applicable( |
@@ -333,12 +318,13 @@ mod tests { | |||
333 | ); | 318 | ); |
334 | check_assist_not_applicable( | 319 | check_assist_not_applicable( |
335 | fix_visibility, | 320 | fix_visibility, |
336 | r"//- /lib.rs | 321 | r" |
337 | mod foo; | 322 | //- /lib.rs |
338 | fn main() { foo::Foo { <|>bar: () }; } | 323 | mod foo; |
339 | //- /foo.rs | 324 | fn main() { foo::Foo { <|>bar: () }; } |
340 | pub struct Foo { pub bar: () } | 325 | //- /foo.rs |
341 | ", | 326 | pub struct Foo { pub bar: () } |
327 | ", | ||
342 | ); | 328 | ); |
343 | } | 329 | } |
344 | 330 | ||
@@ -355,14 +341,14 @@ mod tests { | |||
355 | ); | 341 | ); |
356 | check_assist( | 342 | check_assist( |
357 | fix_visibility, | 343 | fix_visibility, |
358 | r"//- /lib.rs | 344 | r" |
359 | mod foo; | 345 | //- /lib.rs |
360 | fn main() { foo::Foo { <|>bar: () }; } | 346 | mod foo; |
361 | //- /foo.rs | 347 | fn main() { foo::Foo { <|>bar: () }; } |
362 | pub union Foo { bar: () } | 348 | //- /foo.rs |
363 | ", | 349 | pub union Foo { bar: () } |
350 | ", | ||
364 | r"pub union Foo { $0pub(crate) bar: () } | 351 | r"pub union Foo { $0pub(crate) bar: () } |
365 | |||
366 | ", | 352 | ", |
367 | ); | 353 | ); |
368 | check_assist_not_applicable( | 354 | check_assist_not_applicable( |
@@ -372,12 +358,13 @@ mod tests { | |||
372 | ); | 358 | ); |
373 | check_assist_not_applicable( | 359 | check_assist_not_applicable( |
374 | fix_visibility, | 360 | fix_visibility, |
375 | r"//- /lib.rs | 361 | r" |
376 | mod foo; | 362 | //- /lib.rs |
377 | fn main() { foo::Foo { <|>bar: () }; } | 363 | mod foo; |
378 | //- /foo.rs | 364 | fn main() { foo::Foo { <|>bar: () }; } |
379 | pub union Foo { pub bar: () } | 365 | //- /foo.rs |
380 | ", | 366 | pub union Foo { pub bar: () } |
367 | ", | ||
381 | ); | 368 | ); |
382 | } | 369 | } |
383 | 370 | ||
@@ -458,19 +445,18 @@ mod tests { | |||
458 | check_assist( | 445 | check_assist( |
459 | fix_visibility, | 446 | fix_visibility, |
460 | r" | 447 | r" |
461 | //- /main.rs | 448 | //- /main.rs |
462 | mod foo; | 449 | mod foo; |
463 | fn main() { foo::bar<|>::baz(); } | 450 | fn main() { foo::bar<|>::baz(); } |
464 | 451 | ||
465 | //- /foo.rs | 452 | //- /foo.rs |
466 | mod bar { | 453 | mod bar { |
467 | pub fn baz() {} | 454 | pub fn baz() {} |
468 | } | 455 | } |
469 | ", | 456 | ", |
470 | r"$0pub(crate) mod bar { | 457 | r"$0pub(crate) mod bar { |
471 | pub fn baz() {} | 458 | pub fn baz() {} |
472 | } | 459 | } |
473 | |||
474 | ", | 460 | ", |
475 | ); | 461 | ); |
476 | 462 | ||
@@ -486,17 +472,15 @@ mod tests { | |||
486 | check_assist( | 472 | check_assist( |
487 | fix_visibility, | 473 | fix_visibility, |
488 | r" | 474 | r" |
489 | //- /main.rs | 475 | //- /main.rs |
490 | mod foo; | 476 | mod foo; |
491 | fn main() { foo::bar<|>::baz(); } | 477 | fn main() { foo::bar<|>::baz(); } |
492 | 478 | ||
493 | //- /foo.rs | 479 | //- /foo.rs |
494 | mod bar; | 480 | mod bar; |
495 | 481 | //- /foo/bar.rs | |
496 | //- /foo/bar.rs | 482 | pub fn baz() {} |
497 | pub fn baz() {} | 483 | ", |
498 | } | ||
499 | ", | ||
500 | r"$0pub(crate) mod bar; | 484 | r"$0pub(crate) mod bar; |
501 | ", | 485 | ", |
502 | ); | 486 | ); |
@@ -506,14 +490,16 @@ mod tests { | |||
506 | fn fix_visibility_of_module_declaration_in_other_file() { | 490 | fn fix_visibility_of_module_declaration_in_other_file() { |
507 | check_assist( | 491 | check_assist( |
508 | fix_visibility, | 492 | fix_visibility, |
509 | r"//- /main.rs | 493 | r" |
510 | mod foo; | 494 | //- /main.rs |
511 | fn main() { foo::bar<|>>::baz(); } | 495 | mod foo; |
496 | fn main() { foo::bar<|>>::baz(); } | ||
512 | 497 | ||
513 | //- /foo.rs | 498 | //- /foo.rs |
514 | mod bar { | 499 | mod bar { |
515 | pub fn baz() {} | 500 | pub fn baz() {} |
516 | }", | 501 | } |
502 | ", | ||
517 | r"$0pub(crate) mod bar { | 503 | r"$0pub(crate) mod bar { |
518 | pub fn baz() {} | 504 | pub fn baz() {} |
519 | } | 505 | } |
@@ -525,10 +511,12 @@ mod tests { | |||
525 | fn adds_pub_when_target_is_in_another_crate() { | 511 | fn adds_pub_when_target_is_in_another_crate() { |
526 | check_assist( | 512 | check_assist( |
527 | fix_visibility, | 513 | fix_visibility, |
528 | r"//- /main.rs crate:a deps:foo | 514 | r" |
529 | foo::Bar<|> | 515 | //- /main.rs crate:a deps:foo |
530 | //- /lib.rs crate:foo | 516 | foo::Bar<|> |
531 | struct Bar;", | 517 | //- /lib.rs crate:foo |
518 | struct Bar; | ||
519 | ", | ||
532 | r"$0pub struct Bar; | 520 | r"$0pub struct Bar; |
533 | ", | 521 | ", |
534 | ) | 522 | ) |
diff --git a/crates/ra_assists/src/handlers/flip_binexpr.rs b/crates/ra_assists/src/handlers/flip_binexpr.rs index 573196576..3cd532650 100644 --- a/crates/ra_assists/src/handlers/flip_binexpr.rs +++ b/crates/ra_assists/src/handlers/flip_binexpr.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use ra_syntax::ast::{AstNode, BinExpr, BinOp}; | 1 | use ra_syntax::ast::{AstNode, BinExpr, BinOp}; |
2 | 2 | ||
3 | use crate::{AssistContext, AssistId, Assists}; | 3 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
4 | 4 | ||
5 | // Assist: flip_binexpr | 5 | // Assist: flip_binexpr |
6 | // | 6 | // |
@@ -33,13 +33,18 @@ pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
33 | return None; | 33 | return None; |
34 | } | 34 | } |
35 | 35 | ||
36 | acc.add(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| { | 36 | acc.add( |
37 | if let FlipAction::FlipAndReplaceOp(new_op) = action { | 37 | AssistId("flip_binexpr", AssistKind::RefactorRewrite), |
38 | edit.replace(op_range, new_op); | 38 | "Flip binary expression", |
39 | } | 39 | op_range, |
40 | edit.replace(lhs.text_range(), rhs.text()); | 40 | |edit| { |
41 | edit.replace(rhs.text_range(), lhs.text()); | 41 | if let FlipAction::FlipAndReplaceOp(new_op) = action { |
42 | }) | 42 | edit.replace(op_range, new_op); |
43 | } | ||
44 | edit.replace(lhs.text_range(), rhs.text()); | ||
45 | edit.replace(rhs.text_range(), lhs.text()); | ||
46 | }, | ||
47 | ) | ||
43 | } | 48 | } |
44 | 49 | ||
45 | enum FlipAction { | 50 | enum FlipAction { |
diff --git a/crates/ra_assists/src/handlers/flip_comma.rs b/crates/ra_assists/src/handlers/flip_comma.rs index a57a1c463..55a971dc7 100644 --- a/crates/ra_assists/src/handlers/flip_comma.rs +++ b/crates/ra_assists/src/handlers/flip_comma.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use ra_syntax::{algo::non_trivia_sibling, Direction, T}; | 1 | use ra_syntax::{algo::non_trivia_sibling, Direction, T}; |
2 | 2 | ||
3 | use crate::{AssistContext, AssistId, Assists}; | 3 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
4 | 4 | ||
5 | // Assist: flip_comma | 5 | // Assist: flip_comma |
6 | // | 6 | // |
@@ -28,10 +28,15 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
28 | return None; | 28 | return None; |
29 | } | 29 | } |
30 | 30 | ||
31 | acc.add(AssistId("flip_comma"), "Flip comma", comma.text_range(), |edit| { | 31 | acc.add( |
32 | edit.replace(prev.text_range(), next.to_string()); | 32 | AssistId("flip_comma", AssistKind::RefactorRewrite), |
33 | edit.replace(next.text_range(), prev.to_string()); | 33 | "Flip comma", |
34 | }) | 34 | comma.text_range(), |
35 | |edit| { | ||
36 | edit.replace(prev.text_range(), next.to_string()); | ||
37 | edit.replace(next.text_range(), prev.to_string()); | ||
38 | }, | ||
39 | ) | ||
35 | } | 40 | } |
36 | 41 | ||
37 | #[cfg(test)] | 42 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/handlers/flip_trait_bound.rs b/crates/ra_assists/src/handlers/flip_trait_bound.rs index 0115adc8b..1234f4d29 100644 --- a/crates/ra_assists/src/handlers/flip_trait_bound.rs +++ b/crates/ra_assists/src/handlers/flip_trait_bound.rs | |||
@@ -4,7 +4,7 @@ use ra_syntax::{ | |||
4 | Direction, T, | 4 | Direction, T, |
5 | }; | 5 | }; |
6 | 6 | ||
7 | use crate::{AssistContext, AssistId, Assists}; | 7 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
8 | 8 | ||
9 | // Assist: flip_trait_bound | 9 | // Assist: flip_trait_bound |
10 | // | 10 | // |
@@ -33,10 +33,15 @@ pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option | |||
33 | ); | 33 | ); |
34 | 34 | ||
35 | let target = plus.text_range(); | 35 | let target = plus.text_range(); |
36 | acc.add(AssistId("flip_trait_bound"), "Flip trait bounds", target, |edit| { | 36 | acc.add( |
37 | edit.replace(before.text_range(), after.to_string()); | 37 | AssistId("flip_trait_bound", AssistKind::RefactorRewrite), |
38 | edit.replace(after.text_range(), before.to_string()); | 38 | "Flip trait bounds", |
39 | }) | 39 | target, |
40 | |edit| { | ||
41 | edit.replace(before.text_range(), after.to_string()); | ||
42 | edit.replace(after.text_range(), before.to_string()); | ||
43 | }, | ||
44 | ) | ||
40 | } | 45 | } |
41 | 46 | ||
42 | #[cfg(test)] | 47 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/generate_derive.rs index b123b8498..6ccf39900 100644 --- a/crates/ra_assists/src/handlers/add_derive.rs +++ b/crates/ra_assists/src/handlers/generate_derive.rs | |||
@@ -4,9 +4,9 @@ use ra_syntax::{ | |||
4 | TextSize, | 4 | TextSize, |
5 | }; | 5 | }; |
6 | 6 | ||
7 | use crate::{AssistContext, AssistId, Assists}; | 7 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
8 | 8 | ||
9 | // Assist: add_derive | 9 | // Assist: generate_derive |
10 | // | 10 | // |
11 | // Adds a new `#[derive()]` clause to a struct or enum. | 11 | // Adds a new `#[derive()]` clause to a struct or enum. |
12 | // | 12 | // |
@@ -24,32 +24,37 @@ use crate::{AssistContext, AssistId, Assists}; | |||
24 | // y: u32, | 24 | // y: u32, |
25 | // } | 25 | // } |
26 | // ``` | 26 | // ``` |
27 | pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 27 | pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
28 | let cap = ctx.config.snippet_cap?; | 28 | let cap = ctx.config.snippet_cap?; |
29 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; | 29 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; |
30 | let node_start = derive_insertion_offset(&nominal)?; | 30 | let node_start = derive_insertion_offset(&nominal)?; |
31 | let target = nominal.syntax().text_range(); | 31 | let target = nominal.syntax().text_range(); |
32 | acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |builder| { | 32 | acc.add( |
33 | let derive_attr = nominal | 33 | AssistId("generate_derive", AssistKind::Generate), |
34 | .attrs() | 34 | "Add `#[derive]`", |
35 | .filter_map(|x| x.as_simple_call()) | 35 | target, |
36 | .filter(|(name, _arg)| name == "derive") | 36 | |builder| { |
37 | .map(|(_name, arg)| arg) | 37 | let derive_attr = nominal |
38 | .next(); | 38 | .attrs() |
39 | match derive_attr { | 39 | .filter_map(|x| x.as_simple_call()) |
40 | None => { | 40 | .filter(|(name, _arg)| name == "derive") |
41 | builder.insert_snippet(cap, node_start, "#[derive($0)]\n"); | 41 | .map(|(_name, arg)| arg) |
42 | } | 42 | .next(); |
43 | Some(tt) => { | 43 | match derive_attr { |
44 | // Just move the cursor. | 44 | None => { |
45 | builder.insert_snippet( | 45 | builder.insert_snippet(cap, node_start, "#[derive($0)]\n"); |
46 | cap, | 46 | } |
47 | tt.syntax().text_range().end() - TextSize::of(')'), | 47 | Some(tt) => { |
48 | "$0", | 48 | // Just move the cursor. |
49 | ) | 49 | builder.insert_snippet( |
50 | } | 50 | cap, |
51 | }; | 51 | tt.syntax().text_range().end() - TextSize::of(')'), |
52 | }) | 52 | "$0", |
53 | ) | ||
54 | } | ||
55 | }; | ||
56 | }, | ||
57 | ) | ||
53 | } | 58 | } |
54 | 59 | ||
55 | // Insert `derive` after doc comments. | 60 | // Insert `derive` after doc comments. |
@@ -70,12 +75,12 @@ mod tests { | |||
70 | #[test] | 75 | #[test] |
71 | fn add_derive_new() { | 76 | fn add_derive_new() { |
72 | check_assist( | 77 | check_assist( |
73 | add_derive, | 78 | generate_derive, |
74 | "struct Foo { a: i32, <|>}", | 79 | "struct Foo { a: i32, <|>}", |
75 | "#[derive($0)]\nstruct Foo { a: i32, }", | 80 | "#[derive($0)]\nstruct Foo { a: i32, }", |
76 | ); | 81 | ); |
77 | check_assist( | 82 | check_assist( |
78 | add_derive, | 83 | generate_derive, |
79 | "struct Foo { <|> a: i32, }", | 84 | "struct Foo { <|> a: i32, }", |
80 | "#[derive($0)]\nstruct Foo { a: i32, }", | 85 | "#[derive($0)]\nstruct Foo { a: i32, }", |
81 | ); | 86 | ); |
@@ -84,7 +89,7 @@ mod tests { | |||
84 | #[test] | 89 | #[test] |
85 | fn add_derive_existing() { | 90 | fn add_derive_existing() { |
86 | check_assist( | 91 | check_assist( |
87 | add_derive, | 92 | generate_derive, |
88 | "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", | 93 | "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", |
89 | "#[derive(Clone$0)]\nstruct Foo { a: i32, }", | 94 | "#[derive(Clone$0)]\nstruct Foo { a: i32, }", |
90 | ); | 95 | ); |
@@ -93,7 +98,7 @@ mod tests { | |||
93 | #[test] | 98 | #[test] |
94 | fn add_derive_new_with_doc_comment() { | 99 | fn add_derive_new_with_doc_comment() { |
95 | check_assist( | 100 | check_assist( |
96 | add_derive, | 101 | generate_derive, |
97 | " | 102 | " |
98 | /// `Foo` is a pretty important struct. | 103 | /// `Foo` is a pretty important struct. |
99 | /// It does stuff. | 104 | /// It does stuff. |
@@ -111,7 +116,7 @@ struct Foo { a: i32, } | |||
111 | #[test] | 116 | #[test] |
112 | fn add_derive_target() { | 117 | fn add_derive_target() { |
113 | check_assist_target( | 118 | check_assist_target( |
114 | add_derive, | 119 | generate_derive, |
115 | " | 120 | " |
116 | struct SomeThingIrrelevant; | 121 | struct SomeThingIrrelevant; |
117 | /// `Foo` is a pretty important struct. | 122 | /// `Foo` is a pretty important struct. |
diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs index 776bddf91..a347e3c2e 100644 --- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs +++ b/crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs | |||
@@ -2,9 +2,9 @@ use ra_ide_db::RootDatabase; | |||
2 | use ra_syntax::ast::{self, AstNode, NameOwner}; | 2 | use ra_syntax::ast::{self, AstNode, NameOwner}; |
3 | use test_utils::mark; | 3 | use test_utils::mark; |
4 | 4 | ||
5 | use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; | 5 | use crate::{utils::FamousDefs, AssistContext, AssistId, AssistKind, Assists}; |
6 | 6 | ||
7 | // Assist: add_from_impl_for_enum | 7 | // Assist: generate_from_impl_for_enum |
8 | // | 8 | // |
9 | // Adds a From impl for an enum variant with one tuple field. | 9 | // Adds a From impl for an enum variant with one tuple field. |
10 | // | 10 | // |
@@ -21,7 +21,7 @@ use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; | |||
21 | // } | 21 | // } |
22 | // } | 22 | // } |
23 | // ``` | 23 | // ``` |
24 | pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 24 | pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
25 | let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?; | 25 | let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?; |
26 | let variant_name = variant.name()?; | 26 | let variant_name = variant.name()?; |
27 | let enum_name = variant.parent_enum().name()?; | 27 | let enum_name = variant.parent_enum().name()?; |
@@ -45,8 +45,8 @@ pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> | |||
45 | 45 | ||
46 | let target = variant.syntax().text_range(); | 46 | let target = variant.syntax().text_range(); |
47 | acc.add( | 47 | acc.add( |
48 | AssistId("add_from_impl_for_enum"), | 48 | AssistId("generate_from_impl_for_enum", AssistKind::Generate), |
49 | "Add From impl for this enum variant", | 49 | "Generate `From` impl for this enum variant", |
50 | target, | 50 | target, |
51 | |edit| { | 51 | |edit| { |
52 | let start_offset = variant.parent_enum().syntax().text_range().end(); | 52 | let start_offset = variant.parent_enum().syntax().text_range().end(); |
@@ -97,9 +97,9 @@ mod tests { | |||
97 | use super::*; | 97 | use super::*; |
98 | 98 | ||
99 | #[test] | 99 | #[test] |
100 | fn test_add_from_impl_for_enum() { | 100 | fn test_generate_from_impl_for_enum() { |
101 | check_assist( | 101 | check_assist( |
102 | add_from_impl_for_enum, | 102 | generate_from_impl_for_enum, |
103 | "enum A { <|>One(u32) }", | 103 | "enum A { <|>One(u32) }", |
104 | r#"enum A { One(u32) } | 104 | r#"enum A { One(u32) } |
105 | 105 | ||
@@ -112,9 +112,9 @@ impl From<u32> for A { | |||
112 | } | 112 | } |
113 | 113 | ||
114 | #[test] | 114 | #[test] |
115 | fn test_add_from_impl_for_enum_complicated_path() { | 115 | fn test_generate_from_impl_for_enum_complicated_path() { |
116 | check_assist( | 116 | check_assist( |
117 | add_from_impl_for_enum, | 117 | generate_from_impl_for_enum, |
118 | r#"enum A { <|>One(foo::bar::baz::Boo) }"#, | 118 | r#"enum A { <|>One(foo::bar::baz::Boo) }"#, |
119 | r#"enum A { One(foo::bar::baz::Boo) } | 119 | r#"enum A { One(foo::bar::baz::Boo) } |
120 | 120 | ||
@@ -128,8 +128,8 @@ impl From<foo::bar::baz::Boo> for A { | |||
128 | 128 | ||
129 | fn check_not_applicable(ra_fixture: &str) { | 129 | fn check_not_applicable(ra_fixture: &str) { |
130 | let fixture = | 130 | let fixture = |
131 | format!("//- main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); | 131 | format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); |
132 | check_assist_not_applicable(add_from_impl_for_enum, &fixture) | 132 | check_assist_not_applicable(generate_from_impl_for_enum, &fixture) |
133 | } | 133 | } |
134 | 134 | ||
135 | #[test] | 135 | #[test] |
@@ -166,7 +166,7 @@ impl From<u32> for A { | |||
166 | #[test] | 166 | #[test] |
167 | fn test_add_from_impl_different_variant_impl_exists() { | 167 | fn test_add_from_impl_different_variant_impl_exists() { |
168 | check_assist( | 168 | check_assist( |
169 | add_from_impl_for_enum, | 169 | generate_from_impl_for_enum, |
170 | r#"enum A { <|>One(u32), Two(String), } | 170 | r#"enum A { <|>One(u32), Two(String), } |
171 | 171 | ||
172 | impl From<String> for A { | 172 | impl From<String> for A { |
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/generate_function.rs index 24f931a85..b721b96bb 100644 --- a/crates/ra_assists/src/handlers/add_function.rs +++ b/crates/ra_assists/src/handlers/generate_function.rs | |||
@@ -13,10 +13,10 @@ use rustc_hash::{FxHashMap, FxHashSet}; | |||
13 | use crate::{ | 13 | use crate::{ |
14 | assist_config::SnippetCap, | 14 | assist_config::SnippetCap, |
15 | utils::{render_snippet, Cursor}, | 15 | utils::{render_snippet, Cursor}, |
16 | AssistContext, AssistId, Assists, | 16 | AssistContext, AssistId, AssistKind, Assists, |
17 | }; | 17 | }; |
18 | 18 | ||
19 | // Assist: add_function | 19 | // Assist: generate_function |
20 | // | 20 | // |
21 | // 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. |
22 | // | 22 | // |
@@ -41,7 +41,7 @@ use crate::{ | |||
41 | // } | 41 | // } |
42 | // | 42 | // |
43 | // ``` | 43 | // ``` |
44 | pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 44 | pub(crate) fn generate_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
45 | let path_expr: ast::PathExpr = ctx.find_node_at_offset()?; | 45 | let path_expr: ast::PathExpr = ctx.find_node_at_offset()?; |
46 | let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; | 46 | let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; |
47 | let path = path_expr.path()?; | 47 | let path = path_expr.path()?; |
@@ -62,15 +62,20 @@ pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
62 | let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; | 62 | let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; |
63 | 63 | ||
64 | let target = call.syntax().text_range(); | 64 | let target = call.syntax().text_range(); |
65 | acc.add(AssistId("add_function"), "Add function", target, |builder| { | 65 | acc.add( |
66 | let function_template = function_builder.render(); | 66 | AssistId("generate_function", AssistKind::Generate), |
67 | builder.set_file(function_template.file); | 67 | format!("Generate `{}` function", function_builder.fn_name), |
68 | let new_fn = function_template.to_string(ctx.config.snippet_cap); | 68 | target, |
69 | match ctx.config.snippet_cap { | 69 | |builder| { |
70 | Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn), | 70 | let function_template = function_builder.render(); |
71 | None => builder.insert(function_template.insert_offset, new_fn), | 71 | builder.edit_file(function_template.file); |
72 | } | 72 | let new_fn = function_template.to_string(ctx.config.snippet_cap); |
73 | }) | 73 | match ctx.config.snippet_cap { |
74 | Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn), | ||
75 | None => builder.insert(function_template.insert_offset, new_fn), | ||
76 | } | ||
77 | }, | ||
78 | ) | ||
74 | } | 79 | } |
75 | 80 | ||
76 | struct FunctionTemplate { | 81 | struct FunctionTemplate { |
@@ -117,7 +122,7 @@ impl FunctionBuilder { | |||
117 | let mut file = ctx.frange.file_id; | 122 | let mut file = ctx.frange.file_id; |
118 | let target = match &target_module { | 123 | let target = match &target_module { |
119 | Some(target_module) => { | 124 | Some(target_module) => { |
120 | let module_source = target_module.definition_source(ctx.db); | 125 | let module_source = target_module.definition_source(ctx.db()); |
121 | let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?; | 126 | let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?; |
122 | file = in_file; | 127 | file = in_file; |
123 | target | 128 | target |
@@ -269,7 +274,7 @@ fn fn_arg_type( | |||
269 | return None; | 274 | return None; |
270 | } | 275 | } |
271 | 276 | ||
272 | if let Ok(rendered) = ty.display_source_code(ctx.db, target_module.into()) { | 277 | if let Ok(rendered) = ty.display_source_code(ctx.db(), target_module.into()) { |
273 | Some(rendered) | 278 | Some(rendered) |
274 | } else { | 279 | } else { |
275 | None | 280 | None |
@@ -333,7 +338,7 @@ mod tests { | |||
333 | #[test] | 338 | #[test] |
334 | fn add_function_with_no_args() { | 339 | fn add_function_with_no_args() { |
335 | check_assist( | 340 | check_assist( |
336 | add_function, | 341 | generate_function, |
337 | r" | 342 | r" |
338 | fn foo() { | 343 | fn foo() { |
339 | bar<|>(); | 344 | bar<|>(); |
@@ -356,7 +361,7 @@ fn bar() { | |||
356 | // This ensures that the function is correctly generated | 361 | // This ensures that the function is correctly generated |
357 | // in the next outer mod or file | 362 | // in the next outer mod or file |
358 | check_assist( | 363 | check_assist( |
359 | add_function, | 364 | generate_function, |
360 | r" | 365 | r" |
361 | impl Foo { | 366 | impl Foo { |
362 | fn foo() { | 367 | fn foo() { |
@@ -382,7 +387,7 @@ fn bar() { | |||
382 | fn add_function_directly_after_current_block() { | 387 | fn add_function_directly_after_current_block() { |
383 | // The new fn should not be created at the end of the file or module | 388 | // The new fn should not be created at the end of the file or module |
384 | check_assist( | 389 | check_assist( |
385 | add_function, | 390 | generate_function, |
386 | r" | 391 | r" |
387 | fn foo1() { | 392 | fn foo1() { |
388 | bar<|>(); | 393 | bar<|>(); |
@@ -407,7 +412,7 @@ fn foo2() {} | |||
407 | #[test] | 412 | #[test] |
408 | fn add_function_with_no_args_in_same_module() { | 413 | fn add_function_with_no_args_in_same_module() { |
409 | check_assist( | 414 | check_assist( |
410 | add_function, | 415 | generate_function, |
411 | r" | 416 | r" |
412 | mod baz { | 417 | mod baz { |
413 | fn foo() { | 418 | fn foo() { |
@@ -432,7 +437,7 @@ mod baz { | |||
432 | #[test] | 437 | #[test] |
433 | fn add_function_with_function_call_arg() { | 438 | fn add_function_with_function_call_arg() { |
434 | check_assist( | 439 | check_assist( |
435 | add_function, | 440 | generate_function, |
436 | r" | 441 | r" |
437 | struct Baz; | 442 | struct Baz; |
438 | fn baz() -> Baz { todo!() } | 443 | fn baz() -> Baz { todo!() } |
@@ -457,7 +462,7 @@ fn bar(baz: Baz) { | |||
457 | #[test] | 462 | #[test] |
458 | fn add_function_with_method_call_arg() { | 463 | fn add_function_with_method_call_arg() { |
459 | check_assist( | 464 | check_assist( |
460 | add_function, | 465 | generate_function, |
461 | r" | 466 | r" |
462 | struct Baz; | 467 | struct Baz; |
463 | impl Baz { | 468 | impl Baz { |
@@ -490,7 +495,7 @@ fn bar(baz: Baz) { | |||
490 | #[test] | 495 | #[test] |
491 | fn add_function_with_string_literal_arg() { | 496 | fn add_function_with_string_literal_arg() { |
492 | check_assist( | 497 | check_assist( |
493 | add_function, | 498 | generate_function, |
494 | r#" | 499 | r#" |
495 | fn foo() { | 500 | fn foo() { |
496 | <|>bar("bar") | 501 | <|>bar("bar") |
@@ -511,7 +516,7 @@ fn bar(arg: &str) { | |||
511 | #[test] | 516 | #[test] |
512 | fn add_function_with_char_literal_arg() { | 517 | fn add_function_with_char_literal_arg() { |
513 | check_assist( | 518 | check_assist( |
514 | add_function, | 519 | generate_function, |
515 | r#" | 520 | r#" |
516 | fn foo() { | 521 | fn foo() { |
517 | <|>bar('x') | 522 | <|>bar('x') |
@@ -532,7 +537,7 @@ fn bar(arg: char) { | |||
532 | #[test] | 537 | #[test] |
533 | fn add_function_with_int_literal_arg() { | 538 | fn add_function_with_int_literal_arg() { |
534 | check_assist( | 539 | check_assist( |
535 | add_function, | 540 | generate_function, |
536 | r" | 541 | r" |
537 | fn foo() { | 542 | fn foo() { |
538 | <|>bar(42) | 543 | <|>bar(42) |
@@ -553,7 +558,7 @@ fn bar(arg: i32) { | |||
553 | #[test] | 558 | #[test] |
554 | fn add_function_with_cast_int_literal_arg() { | 559 | fn add_function_with_cast_int_literal_arg() { |
555 | check_assist( | 560 | check_assist( |
556 | add_function, | 561 | generate_function, |
557 | r" | 562 | r" |
558 | fn foo() { | 563 | fn foo() { |
559 | <|>bar(42 as u8) | 564 | <|>bar(42 as u8) |
@@ -576,7 +581,7 @@ fn bar(arg: u8) { | |||
576 | // Ensures that the name of the cast type isn't used | 581 | // Ensures that the name of the cast type isn't used |
577 | // in the generated function signature. | 582 | // in the generated function signature. |
578 | check_assist( | 583 | check_assist( |
579 | add_function, | 584 | generate_function, |
580 | r" | 585 | r" |
581 | fn foo() { | 586 | fn foo() { |
582 | let x = 42; | 587 | let x = 42; |
@@ -599,7 +604,7 @@ fn bar(x: u8) { | |||
599 | #[test] | 604 | #[test] |
600 | fn add_function_with_variable_arg() { | 605 | fn add_function_with_variable_arg() { |
601 | check_assist( | 606 | check_assist( |
602 | add_function, | 607 | generate_function, |
603 | r" | 608 | r" |
604 | fn foo() { | 609 | fn foo() { |
605 | let worble = (); | 610 | let worble = (); |
@@ -622,7 +627,7 @@ fn bar(worble: ()) { | |||
622 | #[test] | 627 | #[test] |
623 | fn add_function_with_impl_trait_arg() { | 628 | fn add_function_with_impl_trait_arg() { |
624 | check_assist( | 629 | check_assist( |
625 | add_function, | 630 | generate_function, |
626 | r" | 631 | r" |
627 | trait Foo {} | 632 | trait Foo {} |
628 | fn foo() -> impl Foo { | 633 | fn foo() -> impl Foo { |
@@ -651,7 +656,7 @@ fn bar(foo: impl Foo) { | |||
651 | #[test] | 656 | #[test] |
652 | fn borrowed_arg() { | 657 | fn borrowed_arg() { |
653 | check_assist( | 658 | check_assist( |
654 | add_function, | 659 | generate_function, |
655 | r" | 660 | r" |
656 | struct Baz; | 661 | struct Baz; |
657 | fn baz() -> Baz { todo!() } | 662 | fn baz() -> Baz { todo!() } |
@@ -678,7 +683,7 @@ fn bar(baz: &Baz) { | |||
678 | #[test] | 683 | #[test] |
679 | fn add_function_with_qualified_path_arg() { | 684 | fn add_function_with_qualified_path_arg() { |
680 | check_assist( | 685 | check_assist( |
681 | add_function, | 686 | generate_function, |
682 | r" | 687 | r" |
683 | mod Baz { | 688 | mod Baz { |
684 | pub struct Bof; | 689 | pub struct Bof; |
@@ -709,7 +714,7 @@ fn bar(baz: Baz::Bof) { | |||
709 | // FIXME fix printing the generics of a `Ty` to make this test pass | 714 | // FIXME fix printing the generics of a `Ty` to make this test pass |
710 | fn add_function_with_generic_arg() { | 715 | fn add_function_with_generic_arg() { |
711 | check_assist( | 716 | check_assist( |
712 | add_function, | 717 | generate_function, |
713 | r" | 718 | r" |
714 | fn foo<T>(t: T) { | 719 | fn foo<T>(t: T) { |
715 | <|>bar(t) | 720 | <|>bar(t) |
@@ -732,7 +737,7 @@ fn bar<T>(t: T) { | |||
732 | // FIXME Fix function type printing to make this test pass | 737 | // FIXME Fix function type printing to make this test pass |
733 | fn add_function_with_fn_arg() { | 738 | fn add_function_with_fn_arg() { |
734 | check_assist( | 739 | check_assist( |
735 | add_function, | 740 | generate_function, |
736 | r" | 741 | r" |
737 | struct Baz; | 742 | struct Baz; |
738 | impl Baz { | 743 | impl Baz { |
@@ -763,7 +768,7 @@ fn bar(arg: fn() -> Baz) { | |||
763 | // FIXME Fix closure type printing to make this test pass | 768 | // FIXME Fix closure type printing to make this test pass |
764 | fn add_function_with_closure_arg() { | 769 | fn add_function_with_closure_arg() { |
765 | check_assist( | 770 | check_assist( |
766 | add_function, | 771 | generate_function, |
767 | r" | 772 | r" |
768 | fn foo() { | 773 | fn foo() { |
769 | let closure = |x: i64| x - 1; | 774 | let closure = |x: i64| x - 1; |
@@ -786,7 +791,7 @@ fn bar(closure: impl Fn(i64) -> i64) { | |||
786 | #[test] | 791 | #[test] |
787 | fn unresolveable_types_default_to_unit() { | 792 | fn unresolveable_types_default_to_unit() { |
788 | check_assist( | 793 | check_assist( |
789 | add_function, | 794 | generate_function, |
790 | r" | 795 | r" |
791 | fn foo() { | 796 | fn foo() { |
792 | <|>bar(baz) | 797 | <|>bar(baz) |
@@ -807,7 +812,7 @@ fn bar(baz: ()) { | |||
807 | #[test] | 812 | #[test] |
808 | fn arg_names_dont_overlap() { | 813 | fn arg_names_dont_overlap() { |
809 | check_assist( | 814 | check_assist( |
810 | add_function, | 815 | generate_function, |
811 | r" | 816 | r" |
812 | struct Baz; | 817 | struct Baz; |
813 | fn baz() -> Baz { Baz } | 818 | fn baz() -> Baz { Baz } |
@@ -832,7 +837,7 @@ fn bar(baz_1: Baz, baz_2: Baz) { | |||
832 | #[test] | 837 | #[test] |
833 | fn arg_name_counters_start_at_1_per_name() { | 838 | fn arg_name_counters_start_at_1_per_name() { |
834 | check_assist( | 839 | check_assist( |
835 | add_function, | 840 | generate_function, |
836 | r#" | 841 | r#" |
837 | struct Baz; | 842 | struct Baz; |
838 | fn baz() -> Baz { Baz } | 843 | fn baz() -> Baz { Baz } |
@@ -857,7 +862,7 @@ fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) { | |||
857 | #[test] | 862 | #[test] |
858 | fn add_function_in_module() { | 863 | fn add_function_in_module() { |
859 | check_assist( | 864 | check_assist( |
860 | add_function, | 865 | generate_function, |
861 | r" | 866 | r" |
862 | mod bar {} | 867 | mod bar {} |
863 | 868 | ||
@@ -885,7 +890,7 @@ fn foo() { | |||
885 | // See https://github.com/rust-analyzer/rust-analyzer/issues/1165 | 890 | // See https://github.com/rust-analyzer/rust-analyzer/issues/1165 |
886 | fn qualified_path_uses_correct_scope() { | 891 | fn qualified_path_uses_correct_scope() { |
887 | check_assist( | 892 | check_assist( |
888 | add_function, | 893 | generate_function, |
889 | " | 894 | " |
890 | mod foo { | 895 | mod foo { |
891 | pub struct Foo; | 896 | pub struct Foo; |
@@ -916,7 +921,7 @@ fn baz(foo: foo::Foo) { | |||
916 | #[test] | 921 | #[test] |
917 | fn add_function_in_module_containing_other_items() { | 922 | fn add_function_in_module_containing_other_items() { |
918 | check_assist( | 923 | check_assist( |
919 | add_function, | 924 | generate_function, |
920 | r" | 925 | r" |
921 | mod bar { | 926 | mod bar { |
922 | fn something_else() {} | 927 | fn something_else() {} |
@@ -945,7 +950,7 @@ fn foo() { | |||
945 | #[test] | 950 | #[test] |
946 | fn add_function_in_nested_module() { | 951 | fn add_function_in_nested_module() { |
947 | check_assist( | 952 | check_assist( |
948 | add_function, | 953 | generate_function, |
949 | r" | 954 | r" |
950 | mod bar { | 955 | mod bar { |
951 | mod baz {} | 956 | mod baz {} |
@@ -974,7 +979,7 @@ fn foo() { | |||
974 | #[test] | 979 | #[test] |
975 | fn add_function_in_another_file() { | 980 | fn add_function_in_another_file() { |
976 | check_assist( | 981 | check_assist( |
977 | add_function, | 982 | generate_function, |
978 | r" | 983 | r" |
979 | //- /main.rs | 984 | //- /main.rs |
980 | mod foo; | 985 | mod foo; |
@@ -996,7 +1001,7 @@ pub(crate) fn bar() { | |||
996 | #[test] | 1001 | #[test] |
997 | fn add_function_not_applicable_if_function_already_exists() { | 1002 | fn add_function_not_applicable_if_function_already_exists() { |
998 | check_assist_not_applicable( | 1003 | check_assist_not_applicable( |
999 | add_function, | 1004 | generate_function, |
1000 | r" | 1005 | r" |
1001 | fn foo() { | 1006 | fn foo() { |
1002 | bar<|>(); | 1007 | bar<|>(); |
@@ -1013,7 +1018,7 @@ fn bar() {} | |||
1013 | // bar is resolved, but baz isn't. | 1018 | // bar is resolved, but baz isn't. |
1014 | // The assist is only active if the cursor is on an unresolved path, | 1019 | // The assist is only active if the cursor is on an unresolved path, |
1015 | // but the assist should only be offered if the path is a function call. | 1020 | // but the assist should only be offered if the path is a function call. |
1016 | add_function, | 1021 | generate_function, |
1017 | r" | 1022 | r" |
1018 | fn foo() { | 1023 | fn foo() { |
1019 | bar(b<|>az); | 1024 | bar(b<|>az); |
@@ -1028,7 +1033,7 @@ fn bar(baz: ()) {} | |||
1028 | #[ignore] | 1033 | #[ignore] |
1029 | fn create_method_with_no_args() { | 1034 | fn create_method_with_no_args() { |
1030 | check_assist( | 1035 | check_assist( |
1031 | add_function, | 1036 | generate_function, |
1032 | r" | 1037 | r" |
1033 | struct Foo; | 1038 | struct Foo; |
1034 | impl Foo { | 1039 | impl Foo { |
diff --git a/crates/ra_assists/src/handlers/generate_impl.rs b/crates/ra_assists/src/handlers/generate_impl.rs new file mode 100644 index 000000000..cbbac1d7f --- /dev/null +++ b/crates/ra_assists/src/handlers/generate_impl.rs | |||
@@ -0,0 +1,109 @@ | |||
1 | use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner}; | ||
2 | use stdx::{format_to, SepBy}; | ||
3 | |||
4 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | ||
5 | |||
6 | // Assist: generate_impl | ||
7 | // | ||
8 | // Adds a new inherent impl for a type. | ||
9 | // | ||
10 | // ``` | ||
11 | // struct Ctx<T: Clone> { | ||
12 | // data: T,<|> | ||
13 | // } | ||
14 | // ``` | ||
15 | // -> | ||
16 | // ``` | ||
17 | // struct Ctx<T: Clone> { | ||
18 | // data: T, | ||
19 | // } | ||
20 | // | ||
21 | // impl<T: Clone> Ctx<T> { | ||
22 | // $0 | ||
23 | // } | ||
24 | // ``` | ||
25 | pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
26 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; | ||
27 | let name = nominal.name()?; | ||
28 | let target = nominal.syntax().text_range(); | ||
29 | acc.add( | ||
30 | AssistId("generate_impl", AssistKind::Generate), | ||
31 | format!("Generate impl for `{}`", name), | ||
32 | target, | ||
33 | |edit| { | ||
34 | let type_params = nominal.type_param_list(); | ||
35 | let start_offset = nominal.syntax().text_range().end(); | ||
36 | let mut buf = String::new(); | ||
37 | buf.push_str("\n\nimpl"); | ||
38 | if let Some(type_params) = &type_params { | ||
39 | format_to!(buf, "{}", type_params.syntax()); | ||
40 | } | ||
41 | buf.push_str(" "); | ||
42 | buf.push_str(name.text().as_str()); | ||
43 | if let Some(type_params) = type_params { | ||
44 | let lifetime_params = type_params | ||
45 | .lifetime_params() | ||
46 | .filter_map(|it| it.lifetime_token()) | ||
47 | .map(|it| it.text().clone()); | ||
48 | let type_params = type_params | ||
49 | .type_params() | ||
50 | .filter_map(|it| it.name()) | ||
51 | .map(|it| it.text().clone()); | ||
52 | |||
53 | let generic_params = lifetime_params.chain(type_params).sep_by(", "); | ||
54 | format_to!(buf, "<{}>", generic_params) | ||
55 | } | ||
56 | match ctx.config.snippet_cap { | ||
57 | Some(cap) => { | ||
58 | buf.push_str(" {\n $0\n}"); | ||
59 | edit.insert_snippet(cap, start_offset, buf); | ||
60 | } | ||
61 | None => { | ||
62 | buf.push_str(" {\n}"); | ||
63 | edit.insert(start_offset, buf); | ||
64 | } | ||
65 | } | ||
66 | }, | ||
67 | ) | ||
68 | } | ||
69 | |||
70 | #[cfg(test)] | ||
71 | mod tests { | ||
72 | use crate::tests::{check_assist, check_assist_target}; | ||
73 | |||
74 | use super::*; | ||
75 | |||
76 | #[test] | ||
77 | fn test_add_impl() { | ||
78 | check_assist( | ||
79 | generate_impl, | ||
80 | "struct Foo {<|>}\n", | ||
81 | "struct Foo {}\n\nimpl Foo {\n $0\n}\n", | ||
82 | ); | ||
83 | check_assist( | ||
84 | generate_impl, | ||
85 | "struct Foo<T: Clone> {<|>}", | ||
86 | "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}", | ||
87 | ); | ||
88 | check_assist( | ||
89 | generate_impl, | ||
90 | "struct Foo<'a, T: Foo<'a>> {<|>}", | ||
91 | "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}", | ||
92 | ); | ||
93 | } | ||
94 | |||
95 | #[test] | ||
96 | fn add_impl_target() { | ||
97 | check_assist_target( | ||
98 | generate_impl, | ||
99 | " | ||
100 | struct SomeThingIrrelevant; | ||
101 | /// Has a lifetime parameter | ||
102 | struct Foo<'a, T: Foo<'a>> {<|>} | ||
103 | struct EvenMoreIrrelevant; | ||
104 | ", | ||
105 | "/// Has a lifetime parameter | ||
106 | struct Foo<'a, T: Foo<'a>> {}", | ||
107 | ); | ||
108 | } | ||
109 | } | ||
diff --git a/crates/ra_assists/src/handlers/add_new.rs b/crates/ra_assists/src/handlers/generate_new.rs index 837aa8377..e27def1d8 100644 --- a/crates/ra_assists/src/handlers/add_new.rs +++ b/crates/ra_assists/src/handlers/generate_new.rs | |||
@@ -7,9 +7,9 @@ use ra_syntax::{ | |||
7 | }; | 7 | }; |
8 | use stdx::{format_to, SepBy}; | 8 | use stdx::{format_to, SepBy}; |
9 | 9 | ||
10 | use crate::{AssistContext, AssistId, Assists}; | 10 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
11 | 11 | ||
12 | // Assist: add_new | 12 | // Assist: generate_new |
13 | // | 13 | // |
14 | // Adds a new inherent impl for a type. | 14 | // Adds a new inherent impl for a type. |
15 | // | 15 | // |
@@ -29,7 +29,7 @@ use crate::{AssistContext, AssistId, Assists}; | |||
29 | // } | 29 | // } |
30 | // | 30 | // |
31 | // ``` | 31 | // ``` |
32 | pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 32 | pub(crate) fn generate_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 |
@@ -42,7 +42,7 @@ pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
42 | let impl_def = find_struct_impl(&ctx, &strukt)?; | 42 | let impl_def = find_struct_impl(&ctx, &strukt)?; |
43 | 43 | ||
44 | let target = strukt.syntax().text_range(); | 44 | let target = strukt.syntax().text_range(); |
45 | acc.add(AssistId("add_new"), "Add default constructor", target, |builder| { | 45 | acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| { |
46 | let mut buf = String::with_capacity(512); | 46 | let mut buf = String::with_capacity(512); |
47 | 47 | ||
48 | if impl_def.is_some() { | 48 | if impl_def.is_some() { |
@@ -122,7 +122,7 @@ fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String { | |||
122 | // 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 |
123 | // viable (e.g. we process proc macros, etc) | 123 | // viable (e.g. we process proc macros, etc) |
124 | fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> { | 124 | fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> { |
125 | let db = ctx.db; | 125 | let db = ctx.db(); |
126 | let module = strukt.syntax().ancestors().find(|node| { | 126 | let module = strukt.syntax().ancestors().find(|node| { |
127 | 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()) |
128 | })?; | 128 | })?; |
@@ -181,10 +181,10 @@ mod tests { | |||
181 | 181 | ||
182 | #[test] | 182 | #[test] |
183 | #[rustfmt::skip] | 183 | #[rustfmt::skip] |
184 | fn test_add_new() { | 184 | fn test_generate_new() { |
185 | // Check output of generation | 185 | // Check output of generation |
186 | check_assist( | 186 | check_assist( |
187 | add_new, | 187 | generate_new, |
188 | "struct Foo {<|>}", | 188 | "struct Foo {<|>}", |
189 | "struct Foo {} | 189 | "struct Foo {} |
190 | 190 | ||
@@ -194,7 +194,7 @@ impl Foo { | |||
194 | ", | 194 | ", |
195 | ); | 195 | ); |
196 | check_assist( | 196 | check_assist( |
197 | add_new, | 197 | generate_new, |
198 | "struct Foo<T: Clone> {<|>}", | 198 | "struct Foo<T: Clone> {<|>}", |
199 | "struct Foo<T: Clone> {} | 199 | "struct Foo<T: Clone> {} |
200 | 200 | ||
@@ -204,7 +204,7 @@ impl<T: Clone> Foo<T> { | |||
204 | ", | 204 | ", |
205 | ); | 205 | ); |
206 | check_assist( | 206 | check_assist( |
207 | add_new, | 207 | generate_new, |
208 | "struct Foo<'a, T: Foo<'a>> {<|>}", | 208 | "struct Foo<'a, T: Foo<'a>> {<|>}", |
209 | "struct Foo<'a, T: Foo<'a>> {} | 209 | "struct Foo<'a, T: Foo<'a>> {} |
210 | 210 | ||
@@ -214,7 +214,7 @@ impl<'a, T: Foo<'a>> Foo<'a, T> { | |||
214 | ", | 214 | ", |
215 | ); | 215 | ); |
216 | check_assist( | 216 | check_assist( |
217 | add_new, | 217 | generate_new, |
218 | "struct Foo { baz: String <|>}", | 218 | "struct Foo { baz: String <|>}", |
219 | "struct Foo { baz: String } | 219 | "struct Foo { baz: String } |
220 | 220 | ||
@@ -224,7 +224,7 @@ impl Foo { | |||
224 | ", | 224 | ", |
225 | ); | 225 | ); |
226 | check_assist( | 226 | check_assist( |
227 | add_new, | 227 | generate_new, |
228 | "struct Foo { baz: String, qux: Vec<i32> <|>}", | 228 | "struct Foo { baz: String, qux: Vec<i32> <|>}", |
229 | "struct Foo { baz: String, qux: Vec<i32> } | 229 | "struct Foo { baz: String, qux: Vec<i32> } |
230 | 230 | ||
@@ -236,7 +236,7 @@ impl Foo { | |||
236 | 236 | ||
237 | // Check that visibility modifiers don't get brought in for fields | 237 | // Check that visibility modifiers don't get brought in for fields |
238 | check_assist( | 238 | check_assist( |
239 | add_new, | 239 | generate_new, |
240 | "struct Foo { pub baz: String, pub qux: Vec<i32> <|>}", | 240 | "struct Foo { pub baz: String, pub qux: Vec<i32> <|>}", |
241 | "struct Foo { pub baz: String, pub qux: Vec<i32> } | 241 | "struct Foo { pub baz: String, pub qux: Vec<i32> } |
242 | 242 | ||
@@ -248,7 +248,7 @@ impl Foo { | |||
248 | 248 | ||
249 | // Check that it reuses existing impls | 249 | // Check that it reuses existing impls |
250 | check_assist( | 250 | check_assist( |
251 | add_new, | 251 | generate_new, |
252 | "struct Foo {<|>} | 252 | "struct Foo {<|>} |
253 | 253 | ||
254 | impl Foo {} | 254 | impl Foo {} |
@@ -261,7 +261,7 @@ impl Foo { | |||
261 | ", | 261 | ", |
262 | ); | 262 | ); |
263 | check_assist( | 263 | check_assist( |
264 | add_new, | 264 | generate_new, |
265 | "struct Foo {<|>} | 265 | "struct Foo {<|>} |
266 | 266 | ||
267 | impl Foo { | 267 | impl Foo { |
@@ -279,7 +279,7 @@ impl Foo { | |||
279 | ); | 279 | ); |
280 | 280 | ||
281 | check_assist( | 281 | check_assist( |
282 | add_new, | 282 | generate_new, |
283 | "struct Foo {<|>} | 283 | "struct Foo {<|>} |
284 | 284 | ||
285 | impl Foo { | 285 | impl Foo { |
@@ -304,7 +304,7 @@ impl Foo { | |||
304 | 304 | ||
305 | // Check visibility of new fn based on struct | 305 | // Check visibility of new fn based on struct |
306 | check_assist( | 306 | check_assist( |
307 | add_new, | 307 | generate_new, |
308 | "pub struct Foo {<|>}", | 308 | "pub struct Foo {<|>}", |
309 | "pub struct Foo {} | 309 | "pub struct Foo {} |
310 | 310 | ||
@@ -314,7 +314,7 @@ impl Foo { | |||
314 | ", | 314 | ", |
315 | ); | 315 | ); |
316 | check_assist( | 316 | check_assist( |
317 | add_new, | 317 | generate_new, |
318 | "pub(crate) struct Foo {<|>}", | 318 | "pub(crate) struct Foo {<|>}", |
319 | "pub(crate) struct Foo {} | 319 | "pub(crate) struct Foo {} |
320 | 320 | ||
@@ -326,9 +326,9 @@ impl Foo { | |||
326 | } | 326 | } |
327 | 327 | ||
328 | #[test] | 328 | #[test] |
329 | fn add_new_not_applicable_if_fn_exists() { | 329 | fn generate_new_not_applicable_if_fn_exists() { |
330 | check_assist_not_applicable( | 330 | check_assist_not_applicable( |
331 | add_new, | 331 | generate_new, |
332 | " | 332 | " |
333 | struct Foo {<|>} | 333 | struct Foo {<|>} |
334 | 334 | ||
@@ -340,7 +340,7 @@ impl Foo { | |||
340 | ); | 340 | ); |
341 | 341 | ||
342 | check_assist_not_applicable( | 342 | check_assist_not_applicable( |
343 | add_new, | 343 | generate_new, |
344 | " | 344 | " |
345 | struct Foo {<|>} | 345 | struct Foo {<|>} |
346 | 346 | ||
@@ -353,9 +353,9 @@ impl Foo { | |||
353 | } | 353 | } |
354 | 354 | ||
355 | #[test] | 355 | #[test] |
356 | fn add_new_target() { | 356 | fn generate_new_target() { |
357 | check_assist_target( | 357 | check_assist_target( |
358 | add_new, | 358 | generate_new, |
359 | " | 359 | " |
360 | struct SomeThingIrrelevant; | 360 | struct SomeThingIrrelevant; |
361 | /// Has a lifetime parameter | 361 | /// Has a lifetime parameter |
@@ -370,7 +370,7 @@ struct Foo<'a, T: Foo<'a>> {}", | |||
370 | #[test] | 370 | #[test] |
371 | fn test_unrelated_new() { | 371 | fn test_unrelated_new() { |
372 | check_assist( | 372 | check_assist( |
373 | add_new, | 373 | generate_new, |
374 | r##" | 374 | r##" |
375 | pub struct AstId<N: AstNode> { | 375 | pub struct AstId<N: AstNode> { |
376 | file_id: HirFileId, | 376 | file_id: HirFileId, |
diff --git a/crates/ra_assists/src/handlers/inline_local_variable.rs b/crates/ra_assists/src/handlers/inline_local_variable.rs index d26e68847..2fdfabaf5 100644 --- a/crates/ra_assists/src/handlers/inline_local_variable.rs +++ b/crates/ra_assists/src/handlers/inline_local_variable.rs | |||
@@ -7,7 +7,7 @@ use test_utils::mark; | |||
7 | 7 | ||
8 | use crate::{ | 8 | use crate::{ |
9 | assist_context::{AssistContext, Assists}, | 9 | assist_context::{AssistContext, Assists}, |
10 | AssistId, | 10 | AssistId, AssistKind, |
11 | }; | 11 | }; |
12 | 12 | ||
13 | // Assist: inline_local_variable | 13 | // Assist: inline_local_variable |
@@ -44,7 +44,7 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O | |||
44 | 44 | ||
45 | let def = ctx.sema.to_def(&bind_pat)?; | 45 | let def = ctx.sema.to_def(&bind_pat)?; |
46 | let def = Definition::Local(def); | 46 | let def = Definition::Local(def); |
47 | let refs = def.find_usages(ctx.db, None); | 47 | let refs = def.find_usages(&ctx.sema, None); |
48 | if refs.is_empty() { | 48 | if refs.is_empty() { |
49 | mark::hit!(test_not_applicable_if_variable_unused); | 49 | mark::hit!(test_not_applicable_if_variable_unused); |
50 | return None; | 50 | return None; |
@@ -110,13 +110,19 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O | |||
110 | let init_in_paren = format!("({})", &init_str); | 110 | let init_in_paren = format!("({})", &init_str); |
111 | 111 | ||
112 | let target = bind_pat.syntax().text_range(); | 112 | let target = bind_pat.syntax().text_range(); |
113 | acc.add(AssistId("inline_local_variable"), "Inline variable", target, move |builder| { | 113 | acc.add( |
114 | builder.delete(delete_range); | 114 | AssistId("inline_local_variable", AssistKind::RefactorInline), |
115 | for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { | 115 | "Inline variable", |
116 | let replacement = if should_wrap { init_in_paren.clone() } else { init_str.clone() }; | 116 | target, |
117 | builder.replace(desc.file_range.range, replacement) | 117 | move |builder| { |
118 | } | 118 | builder.delete(delete_range); |
119 | }) | 119 | for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { |
120 | let replacement = | ||
121 | if should_wrap { init_in_paren.clone() } else { init_str.clone() }; | ||
122 | builder.replace(desc.file_range.range, replacement) | ||
123 | } | ||
124 | }, | ||
125 | ) | ||
120 | } | 126 | } |
121 | 127 | ||
122 | #[cfg(test)] | 128 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs index 999aec421..967593031 100644 --- a/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs +++ b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs | |||
@@ -1,12 +1,15 @@ | |||
1 | use crate::{assist_context::AssistBuilder, AssistContext, AssistId, Assists}; | 1 | use ra_syntax::{ |
2 | use ast::{NameOwner, ParamList, TypeAscriptionOwner, TypeParamList, TypeRef}; | 2 | ast::{self, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, |
3 | use ra_syntax::{ast, ast::TypeParamsOwner, AstNode, SyntaxKind, TextRange, TextSize}; | 3 | AstNode, SyntaxKind, TextRange, TextSize, |
4 | }; | ||
4 | use rustc_hash::FxHashSet; | 5 | use rustc_hash::FxHashSet; |
5 | 6 | ||
6 | static ASSIST_NAME: &str = "change_lifetime_anon_to_named"; | 7 | use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists}; |
7 | static ASSIST_LABEL: &str = "Give anonymous lifetime a name"; | ||
8 | 8 | ||
9 | // Assist: change_lifetime_anon_to_named | 9 | static ASSIST_NAME: &str = "introduce_named_lifetime"; |
10 | static ASSIST_LABEL: &str = "Introduce named lifetime"; | ||
11 | |||
12 | // Assist: introduce_named_lifetime | ||
10 | // | 13 | // |
11 | // Change an anonymous lifetime to a named lifetime. | 14 | // Change an anonymous lifetime to a named lifetime. |
12 | // | 15 | // |
@@ -31,15 +34,13 @@ static ASSIST_LABEL: &str = "Give anonymous lifetime a name"; | |||
31 | // ``` | 34 | // ``` |
32 | // FIXME: How can we handle renaming any one of multiple anonymous lifetimes? | 35 | // FIXME: How can we handle renaming any one of multiple anonymous lifetimes? |
33 | // FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo | 36 | // FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo |
34 | pub(crate) fn change_lifetime_anon_to_named(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 37 | pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
35 | let lifetime_token = ctx | 38 | let lifetime_token = ctx |
36 | .find_token_at_offset(SyntaxKind::LIFETIME) | 39 | .find_token_at_offset(SyntaxKind::LIFETIME) |
37 | .filter(|lifetime| lifetime.text() == "'_")?; | 40 | .filter(|lifetime| lifetime.text() == "'_")?; |
38 | if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) { | 41 | if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) { |
39 | generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range()) | 42 | generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range()) |
40 | } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) { | 43 | } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) { |
41 | // only allow naming the last anonymous lifetime | ||
42 | lifetime_token.next_token().filter(|tok| tok.kind() == SyntaxKind::R_ANGLE)?; | ||
43 | generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range()) | 44 | generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range()) |
44 | } else { | 45 | } else { |
45 | None | 46 | None |
@@ -52,7 +53,7 @@ fn generate_fn_def_assist( | |||
52 | fn_def: &ast::FnDef, | 53 | fn_def: &ast::FnDef, |
53 | lifetime_loc: TextRange, | 54 | lifetime_loc: TextRange, |
54 | ) -> Option<()> { | 55 | ) -> Option<()> { |
55 | let param_list: ParamList = fn_def.param_list()?; | 56 | let param_list: ast::ParamList = fn_def.param_list()?; |
56 | let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.type_param_list())?; | 57 | let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.type_param_list())?; |
57 | let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end(); | 58 | let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end(); |
58 | let self_param = | 59 | let self_param = |
@@ -67,7 +68,7 @@ fn generate_fn_def_assist( | |||
67 | let fn_params_without_lifetime: Vec<_> = param_list | 68 | let fn_params_without_lifetime: Vec<_> = param_list |
68 | .params() | 69 | .params() |
69 | .filter_map(|param| match param.ascribed_type() { | 70 | .filter_map(|param| match param.ascribed_type() { |
70 | Some(TypeRef::ReferenceType(ascribed_type)) | 71 | Some(ast::TypeRef::ReferenceType(ascribed_type)) |
71 | if ascribed_type.lifetime_token() == None => | 72 | if ascribed_type.lifetime_token() == None => |
72 | { | 73 | { |
73 | Some(ascribed_type.amp_token()?.text_range().end()) | 74 | Some(ascribed_type.amp_token()?.text_range().end()) |
@@ -82,7 +83,7 @@ fn generate_fn_def_assist( | |||
82 | _ => return None, | 83 | _ => return None, |
83 | } | 84 | } |
84 | }; | 85 | }; |
85 | acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { | 86 | acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| { |
86 | add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param); | 87 | add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param); |
87 | builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); | 88 | builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); |
88 | loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param))); | 89 | loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param))); |
@@ -97,7 +98,7 @@ fn generate_impl_def_assist( | |||
97 | ) -> Option<()> { | 98 | ) -> Option<()> { |
98 | let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.type_param_list())?; | 99 | let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.type_param_list())?; |
99 | let end_of_impl_kw = impl_def.impl_token()?.text_range().end(); | 100 | let end_of_impl_kw = impl_def.impl_token()?.text_range().end(); |
100 | acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { | 101 | acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| { |
101 | add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param); | 102 | add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param); |
102 | builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); | 103 | builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); |
103 | }) | 104 | }) |
@@ -106,7 +107,7 @@ fn generate_impl_def_assist( | |||
106 | /// Given a type parameter list, generate a unique lifetime parameter name | 107 | /// Given a type parameter list, generate a unique lifetime parameter name |
107 | /// which is not in the list | 108 | /// which is not in the list |
108 | fn generate_unique_lifetime_param_name( | 109 | fn generate_unique_lifetime_param_name( |
109 | existing_type_param_list: &Option<TypeParamList>, | 110 | existing_type_param_list: &Option<ast::TypeParamList>, |
110 | ) -> Option<char> { | 111 | ) -> Option<char> { |
111 | match existing_type_param_list { | 112 | match existing_type_param_list { |
112 | Some(type_params) => { | 113 | Some(type_params) => { |
@@ -151,7 +152,7 @@ mod tests { | |||
151 | #[test] | 152 | #[test] |
152 | fn test_example_case() { | 153 | fn test_example_case() { |
153 | check_assist( | 154 | check_assist( |
154 | change_lifetime_anon_to_named, | 155 | introduce_named_lifetime, |
155 | r#"impl Cursor<'_<|>> { | 156 | r#"impl Cursor<'_<|>> { |
156 | fn node(self) -> &SyntaxNode { | 157 | fn node(self) -> &SyntaxNode { |
157 | match self { | 158 | match self { |
@@ -172,7 +173,7 @@ mod tests { | |||
172 | #[test] | 173 | #[test] |
173 | fn test_example_case_simplified() { | 174 | fn test_example_case_simplified() { |
174 | check_assist( | 175 | check_assist( |
175 | change_lifetime_anon_to_named, | 176 | introduce_named_lifetime, |
176 | r#"impl Cursor<'_<|>> {"#, | 177 | r#"impl Cursor<'_<|>> {"#, |
177 | r#"impl<'a> Cursor<'a> {"#, | 178 | r#"impl<'a> Cursor<'a> {"#, |
178 | ); | 179 | ); |
@@ -181,16 +182,33 @@ mod tests { | |||
181 | #[test] | 182 | #[test] |
182 | fn test_example_case_cursor_after_tick() { | 183 | fn test_example_case_cursor_after_tick() { |
183 | check_assist( | 184 | check_assist( |
184 | change_lifetime_anon_to_named, | 185 | introduce_named_lifetime, |
185 | r#"impl Cursor<'<|>_> {"#, | 186 | r#"impl Cursor<'<|>_> {"#, |
186 | r#"impl<'a> Cursor<'a> {"#, | 187 | r#"impl<'a> Cursor<'a> {"#, |
187 | ); | 188 | ); |
188 | } | 189 | } |
189 | 190 | ||
190 | #[test] | 191 | #[test] |
192 | fn test_impl_with_other_type_param() { | ||
193 | check_assist( | ||
194 | introduce_named_lifetime, | ||
195 | "impl<I> fmt::Display for SepByBuilder<'_<|>, I> | ||
196 | where | ||
197 | I: Iterator, | ||
198 | I::Item: fmt::Display, | ||
199 | {", | ||
200 | "impl<I, 'a> fmt::Display for SepByBuilder<'a, I> | ||
201 | where | ||
202 | I: Iterator, | ||
203 | I::Item: fmt::Display, | ||
204 | {", | ||
205 | ) | ||
206 | } | ||
207 | |||
208 | #[test] | ||
191 | fn test_example_case_cursor_before_tick() { | 209 | fn test_example_case_cursor_before_tick() { |
192 | check_assist( | 210 | check_assist( |
193 | change_lifetime_anon_to_named, | 211 | introduce_named_lifetime, |
194 | r#"impl Cursor<<|>'_> {"#, | 212 | r#"impl Cursor<<|>'_> {"#, |
195 | r#"impl<'a> Cursor<'a> {"#, | 213 | r#"impl<'a> Cursor<'a> {"#, |
196 | ); | 214 | ); |
@@ -198,23 +216,20 @@ mod tests { | |||
198 | 216 | ||
199 | #[test] | 217 | #[test] |
200 | fn test_not_applicable_cursor_position() { | 218 | fn test_not_applicable_cursor_position() { |
201 | check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'_><|> {"#); | 219 | check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'_><|> {"#); |
202 | check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<|><'_> {"#); | 220 | check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<|><'_> {"#); |
203 | } | 221 | } |
204 | 222 | ||
205 | #[test] | 223 | #[test] |
206 | fn test_not_applicable_lifetime_already_name() { | 224 | fn test_not_applicable_lifetime_already_name() { |
207 | check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'a<|>> {"#); | 225 | check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'a<|>> {"#); |
208 | check_assist_not_applicable( | 226 | check_assist_not_applicable(introduce_named_lifetime, r#"fn my_fun<'a>() -> X<'a<|>>"#); |
209 | change_lifetime_anon_to_named, | ||
210 | r#"fn my_fun<'a>() -> X<'a<|>>"#, | ||
211 | ); | ||
212 | } | 227 | } |
213 | 228 | ||
214 | #[test] | 229 | #[test] |
215 | fn test_with_type_parameter() { | 230 | fn test_with_type_parameter() { |
216 | check_assist( | 231 | check_assist( |
217 | change_lifetime_anon_to_named, | 232 | introduce_named_lifetime, |
218 | r#"impl<T> Cursor<T, '_<|>>"#, | 233 | r#"impl<T> Cursor<T, '_<|>>"#, |
219 | r#"impl<T, 'a> Cursor<T, 'a>"#, | 234 | r#"impl<T, 'a> Cursor<T, 'a>"#, |
220 | ); | 235 | ); |
@@ -223,7 +238,7 @@ mod tests { | |||
223 | #[test] | 238 | #[test] |
224 | fn test_with_existing_lifetime_name_conflict() { | 239 | fn test_with_existing_lifetime_name_conflict() { |
225 | check_assist( | 240 | check_assist( |
226 | change_lifetime_anon_to_named, | 241 | introduce_named_lifetime, |
227 | r#"impl<'a, 'b> Cursor<'a, 'b, '_<|>>"#, | 242 | r#"impl<'a, 'b> Cursor<'a, 'b, '_<|>>"#, |
228 | r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#, | 243 | r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#, |
229 | ); | 244 | ); |
@@ -232,7 +247,7 @@ mod tests { | |||
232 | #[test] | 247 | #[test] |
233 | fn test_function_return_value_anon_lifetime_param() { | 248 | fn test_function_return_value_anon_lifetime_param() { |
234 | check_assist( | 249 | check_assist( |
235 | change_lifetime_anon_to_named, | 250 | introduce_named_lifetime, |
236 | r#"fn my_fun() -> X<'_<|>>"#, | 251 | r#"fn my_fun() -> X<'_<|>>"#, |
237 | r#"fn my_fun<'a>() -> X<'a>"#, | 252 | r#"fn my_fun<'a>() -> X<'a>"#, |
238 | ); | 253 | ); |
@@ -241,7 +256,7 @@ mod tests { | |||
241 | #[test] | 256 | #[test] |
242 | fn test_function_return_value_anon_reference_lifetime() { | 257 | fn test_function_return_value_anon_reference_lifetime() { |
243 | check_assist( | 258 | check_assist( |
244 | change_lifetime_anon_to_named, | 259 | introduce_named_lifetime, |
245 | r#"fn my_fun() -> &'_<|> X"#, | 260 | r#"fn my_fun() -> &'_<|> X"#, |
246 | r#"fn my_fun<'a>() -> &'a X"#, | 261 | r#"fn my_fun<'a>() -> &'a X"#, |
247 | ); | 262 | ); |
@@ -250,7 +265,7 @@ mod tests { | |||
250 | #[test] | 265 | #[test] |
251 | fn test_function_param_anon_lifetime() { | 266 | fn test_function_param_anon_lifetime() { |
252 | check_assist( | 267 | check_assist( |
253 | change_lifetime_anon_to_named, | 268 | introduce_named_lifetime, |
254 | r#"fn my_fun(x: X<'_<|>>)"#, | 269 | r#"fn my_fun(x: X<'_<|>>)"#, |
255 | r#"fn my_fun<'a>(x: X<'a>)"#, | 270 | r#"fn my_fun<'a>(x: X<'a>)"#, |
256 | ); | 271 | ); |
@@ -259,7 +274,7 @@ mod tests { | |||
259 | #[test] | 274 | #[test] |
260 | fn test_function_add_lifetime_to_params() { | 275 | fn test_function_add_lifetime_to_params() { |
261 | check_assist( | 276 | check_assist( |
262 | change_lifetime_anon_to_named, | 277 | introduce_named_lifetime, |
263 | r#"fn my_fun(f: &Foo) -> X<'_<|>>"#, | 278 | r#"fn my_fun(f: &Foo) -> X<'_<|>>"#, |
264 | r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#, | 279 | r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#, |
265 | ); | 280 | ); |
@@ -268,7 +283,7 @@ mod tests { | |||
268 | #[test] | 283 | #[test] |
269 | fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() { | 284 | fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() { |
270 | check_assist( | 285 | check_assist( |
271 | change_lifetime_anon_to_named, | 286 | introduce_named_lifetime, |
272 | r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_<|>>"#, | 287 | r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_<|>>"#, |
273 | r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#, | 288 | r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#, |
274 | ); | 289 | ); |
@@ -278,7 +293,7 @@ mod tests { | |||
278 | fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() { | 293 | fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() { |
279 | // this is not permitted under lifetime elision rules | 294 | // this is not permitted under lifetime elision rules |
280 | check_assist_not_applicable( | 295 | check_assist_not_applicable( |
281 | change_lifetime_anon_to_named, | 296 | introduce_named_lifetime, |
282 | r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_<|>>"#, | 297 | r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_<|>>"#, |
283 | ); | 298 | ); |
284 | } | 299 | } |
@@ -286,7 +301,7 @@ mod tests { | |||
286 | #[test] | 301 | #[test] |
287 | fn test_function_add_lifetime_to_self_ref_param() { | 302 | fn test_function_add_lifetime_to_self_ref_param() { |
288 | check_assist( | 303 | check_assist( |
289 | change_lifetime_anon_to_named, | 304 | introduce_named_lifetime, |
290 | r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, | 305 | r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, |
291 | r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#, | 306 | r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#, |
292 | ); | 307 | ); |
@@ -295,7 +310,7 @@ mod tests { | |||
295 | #[test] | 310 | #[test] |
296 | fn test_function_add_lifetime_to_param_with_non_ref_self() { | 311 | fn test_function_add_lifetime_to_param_with_non_ref_self() { |
297 | check_assist( | 312 | check_assist( |
298 | change_lifetime_anon_to_named, | 313 | introduce_named_lifetime, |
299 | r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, | 314 | r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, |
300 | r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#, | 315 | r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#, |
301 | ); | 316 | ); |
diff --git a/crates/ra_assists/src/handlers/invert_if.rs b/crates/ra_assists/src/handlers/invert_if.rs index 59d278eb9..bbe3f3643 100644 --- a/crates/ra_assists/src/handlers/invert_if.rs +++ b/crates/ra_assists/src/handlers/invert_if.rs | |||
@@ -6,7 +6,7 @@ use ra_syntax::{ | |||
6 | use crate::{ | 6 | use crate::{ |
7 | assist_context::{AssistContext, Assists}, | 7 | assist_context::{AssistContext, Assists}, |
8 | utils::invert_boolean_expression, | 8 | utils::invert_boolean_expression, |
9 | AssistId, | 9 | AssistId, AssistKind, |
10 | }; | 10 | }; |
11 | 11 | ||
12 | // Assist: invert_if | 12 | // Assist: invert_if |
@@ -54,7 +54,7 @@ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
54 | let else_node = else_block.syntax(); | 54 | let else_node = else_block.syntax(); |
55 | let else_range = else_node.text_range(); | 55 | let else_range = else_node.text_range(); |
56 | let then_range = then_node.text_range(); | 56 | let then_range = then_node.text_range(); |
57 | acc.add(AssistId("invert_if"), "Invert if", if_range, |edit| { | 57 | acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| { |
58 | edit.replace(cond_range, flip_cond.syntax().text()); | 58 | edit.replace(cond_range, flip_cond.syntax().text()); |
59 | edit.replace(else_range, then_node.text()); | 59 | edit.replace(else_range, then_node.text()); |
60 | edit.replace(then_range, else_node.text()); | 60 | edit.replace(then_range, else_node.text()); |
diff --git a/crates/ra_assists/src/handlers/merge_imports.rs b/crates/ra_assists/src/handlers/merge_imports.rs index 972d16241..1beccb61c 100644 --- a/crates/ra_assists/src/handlers/merge_imports.rs +++ b/crates/ra_assists/src/handlers/merge_imports.rs | |||
@@ -8,7 +8,7 @@ use ra_syntax::{ | |||
8 | 8 | ||
9 | use crate::{ | 9 | use crate::{ |
10 | assist_context::{AssistContext, Assists}, | 10 | assist_context::{AssistContext, Assists}, |
11 | AssistId, | 11 | AssistId, AssistKind, |
12 | }; | 12 | }; |
13 | 13 | ||
14 | // Assist: merge_imports | 14 | // Assist: merge_imports |
@@ -56,9 +56,14 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<() | |||
56 | }; | 56 | }; |
57 | 57 | ||
58 | let target = tree.syntax().text_range(); | 58 | let target = tree.syntax().text_range(); |
59 | acc.add(AssistId("merge_imports"), "Merge imports", target, |builder| { | 59 | acc.add( |
60 | builder.rewrite(rewriter); | 60 | AssistId("merge_imports", AssistKind::RefactorRewrite), |
61 | }) | 61 | "Merge imports", |
62 | target, | ||
63 | |builder| { | ||
64 | builder.rewrite(rewriter); | ||
65 | }, | ||
66 | ) | ||
62 | } | 67 | } |
63 | 68 | ||
64 | fn next_prev() -> impl Iterator<Item = Direction> { | 69 | fn next_prev() -> impl Iterator<Item = Direction> { |
@@ -127,7 +132,7 @@ fn first_path(path: &ast::Path) -> ast::Path { | |||
127 | 132 | ||
128 | #[cfg(test)] | 133 | #[cfg(test)] |
129 | mod tests { | 134 | mod tests { |
130 | use crate::tests::check_assist; | 135 | use crate::tests::{check_assist, check_assist_not_applicable}; |
131 | 136 | ||
132 | use super::*; | 137 | use super::*; |
133 | 138 | ||
@@ -276,4 +281,14 @@ bar::baz}; | |||
276 | ", | 281 | ", |
277 | ) | 282 | ) |
278 | } | 283 | } |
284 | |||
285 | #[test] | ||
286 | fn test_empty_use() { | ||
287 | check_assist_not_applicable( | ||
288 | merge_imports, | ||
289 | r" | ||
290 | use std::<|> | ||
291 | fn main() {}", | ||
292 | ); | ||
293 | } | ||
279 | } | 294 | } |
diff --git a/crates/ra_assists/src/handlers/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs index ca04ec671..186a1f618 100644 --- a/crates/ra_assists/src/handlers/merge_match_arms.rs +++ b/crates/ra_assists/src/handlers/merge_match_arms.rs | |||
@@ -6,7 +6,7 @@ use ra_syntax::{ | |||
6 | Direction, | 6 | Direction, |
7 | }; | 7 | }; |
8 | 8 | ||
9 | use crate::{AssistContext, AssistId, Assists, TextRange}; | 9 | use crate::{AssistContext, AssistId, AssistKind, Assists, TextRange}; |
10 | 10 | ||
11 | // Assist: merge_match_arms | 11 | // Assist: merge_match_arms |
12 | // | 12 | // |
@@ -59,32 +59,34 @@ pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option | |||
59 | return None; | 59 | return None; |
60 | } | 60 | } |
61 | 61 | ||
62 | acc.add(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| { | 62 | acc.add( |
63 | let pats = if arms_to_merge.iter().any(contains_placeholder) { | 63 | AssistId("merge_match_arms", AssistKind::RefactorRewrite), |
64 | "_".into() | 64 | "Merge match arms", |
65 | } else { | 65 | current_text_range, |
66 | arms_to_merge | 66 | |edit| { |
67 | .iter() | 67 | let pats = if arms_to_merge.iter().any(contains_placeholder) { |
68 | .filter_map(ast::MatchArm::pat) | 68 | "_".into() |
69 | .map(|x| x.syntax().to_string()) | 69 | } else { |
70 | .collect::<Vec<String>>() | 70 | arms_to_merge |
71 | .join(" | ") | 71 | .iter() |
72 | }; | 72 | .filter_map(ast::MatchArm::pat) |
73 | 73 | .map(|x| x.syntax().to_string()) | |
74 | let arm = format!("{} => {}", pats, current_expr.syntax().text()); | 74 | .collect::<Vec<String>>() |
75 | 75 | .join(" | ") | |
76 | let start = arms_to_merge.first().unwrap().syntax().text_range().start(); | 76 | }; |
77 | let end = arms_to_merge.last().unwrap().syntax().text_range().end(); | 77 | |
78 | 78 | let arm = format!("{} => {}", pats, current_expr.syntax().text()); | |
79 | edit.replace(TextRange::new(start, end), arm); | 79 | |
80 | }) | 80 | let start = arms_to_merge.first().unwrap().syntax().text_range().start(); |
81 | let end = arms_to_merge.last().unwrap().syntax().text_range().end(); | ||
82 | |||
83 | edit.replace(TextRange::new(start, end), arm); | ||
84 | }, | ||
85 | ) | ||
81 | } | 86 | } |
82 | 87 | ||
83 | fn contains_placeholder(a: &ast::MatchArm) -> bool { | 88 | fn contains_placeholder(a: &ast::MatchArm) -> bool { |
84 | match a.pat() { | 89 | matches!(a.pat(), Some(ast::Pat::PlaceholderPat(..))) |
85 | Some(ra_syntax::ast::Pat::PlaceholderPat(..)) => true, | ||
86 | _ => false, | ||
87 | } | ||
88 | } | 90 | } |
89 | 91 | ||
90 | #[cfg(test)] | 92 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/handlers/move_bounds.rs b/crates/ra_assists/src/handlers/move_bounds.rs index be2a7eddc..ba3dafb99 100644 --- a/crates/ra_assists/src/handlers/move_bounds.rs +++ b/crates/ra_assists/src/handlers/move_bounds.rs | |||
@@ -5,7 +5,7 @@ use ra_syntax::{ | |||
5 | T, | 5 | T, |
6 | }; | 6 | }; |
7 | 7 | ||
8 | use crate::{AssistContext, AssistId, Assists}; | 8 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
9 | 9 | ||
10 | // Assist: move_bounds_to_where_clause | 10 | // Assist: move_bounds_to_where_clause |
11 | // | 11 | // |
@@ -50,29 +50,36 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext | |||
50 | }; | 50 | }; |
51 | 51 | ||
52 | let target = type_param_list.syntax().text_range(); | 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 | acc.add( |
54 | let new_params = type_param_list | 54 | AssistId("move_bounds_to_where_clause", AssistKind::RefactorRewrite), |
55 | .type_params() | 55 | "Move to where clause", |
56 | .filter(|it| it.type_bound_list().is_some()) | 56 | target, |
57 | .map(|type_param| { | 57 | |edit| { |
58 | let without_bounds = type_param.remove_bounds(); | 58 | let new_params = type_param_list |
59 | (type_param, without_bounds) | 59 | .type_params() |
60 | }); | 60 | .filter(|it| it.type_bound_list().is_some()) |
61 | 61 | .map(|type_param| { | |
62 | let new_type_param_list = type_param_list.replace_descendants(new_params); | 62 | let without_bounds = type_param.remove_bounds(); |
63 | edit.replace_ast(type_param_list.clone(), new_type_param_list); | 63 | (type_param, without_bounds) |
64 | 64 | }); | |
65 | let where_clause = { | 65 | |
66 | let predicates = type_param_list.type_params().filter_map(build_predicate); | 66 | let new_type_param_list = type_param_list.replace_descendants(new_params); |
67 | make::where_clause(predicates) | 67 | edit.replace_ast(type_param_list.clone(), new_type_param_list); |
68 | }; | 68 | |
69 | 69 | let where_clause = { | |
70 | let to_insert = match anchor.prev_sibling_or_token() { | 70 | let predicates = type_param_list.type_params().filter_map(build_predicate); |
71 | Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()), | 71 | make::where_clause(predicates) |
72 | _ => format!(" {}", where_clause.syntax()), | 72 | }; |
73 | }; | 73 | |
74 | edit.insert(anchor.text_range().start(), to_insert); | 74 | let to_insert = match anchor.prev_sibling_or_token() { |
75 | }) | 75 | Some(ref elem) if elem.kind() == WHITESPACE => { |
76 | format!("{} ", where_clause.syntax()) | ||
77 | } | ||
78 | _ => format!(" {}", where_clause.syntax()), | ||
79 | }; | ||
80 | edit.insert(anchor.text_range().start(), to_insert); | ||
81 | }, | ||
82 | ) | ||
76 | } | 83 | } |
77 | 84 | ||
78 | fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { | 85 | fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { |
diff --git a/crates/ra_assists/src/handlers/move_guard.rs b/crates/ra_assists/src/handlers/move_guard.rs index 7edcf0748..4060d34c6 100644 --- a/crates/ra_assists/src/handlers/move_guard.rs +++ b/crates/ra_assists/src/handlers/move_guard.rs | |||
@@ -3,7 +3,7 @@ use ra_syntax::{ | |||
3 | SyntaxKind::WHITESPACE, | 3 | SyntaxKind::WHITESPACE, |
4 | }; | 4 | }; |
5 | 5 | ||
6 | use crate::{AssistContext, AssistId, Assists}; | 6 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
7 | 7 | ||
8 | // Assist: move_guard_to_arm_body | 8 | // Assist: move_guard_to_arm_body |
9 | // | 9 | // |
@@ -40,17 +40,22 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> | |||
40 | 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()); |
41 | 41 | ||
42 | let target = guard.syntax().text_range(); | 42 | let target = guard.syntax().text_range(); |
43 | acc.add(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| { | 43 | acc.add( |
44 | match space_before_guard { | 44 | AssistId("move_guard_to_arm_body", AssistKind::RefactorRewrite), |
45 | Some(element) if element.kind() == WHITESPACE => { | 45 | "Move guard to arm body", |
46 | edit.delete(element.text_range()); | 46 | target, |
47 | } | 47 | |edit| { |
48 | _ => (), | 48 | match space_before_guard { |
49 | }; | 49 | Some(element) if element.kind() == WHITESPACE => { |
50 | edit.delete(element.text_range()); | ||
51 | } | ||
52 | _ => (), | ||
53 | }; | ||
50 | 54 | ||
51 | edit.delete(guard.syntax().text_range()); | 55 | edit.delete(guard.syntax().text_range()); |
52 | edit.replace_node_and_indent(arm_expr.syntax(), buf); | 56 | edit.replace_node_and_indent(arm_expr.syntax(), buf); |
53 | }) | 57 | }, |
58 | ) | ||
54 | } | 59 | } |
55 | 60 | ||
56 | // Assist: move_arm_cond_to_match_guard | 61 | // Assist: move_arm_cond_to_match_guard |
@@ -100,7 +105,7 @@ pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContex | |||
100 | 105 | ||
101 | let target = if_expr.syntax().text_range(); | 106 | let target = if_expr.syntax().text_range(); |
102 | acc.add( | 107 | acc.add( |
103 | AssistId("move_arm_cond_to_match_guard"), | 108 | AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite), |
104 | "Move condition to match guard", | 109 | "Move condition to match guard", |
105 | target, | 110 | target, |
106 | |edit| { | 111 | |edit| { |
diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs index 16002d2ac..6d77dff13 100644 --- a/crates/ra_assists/src/handlers/raw_string.rs +++ b/crates/ra_assists/src/handlers/raw_string.rs | |||
@@ -1,11 +1,13 @@ | |||
1 | use std::borrow::Cow; | ||
2 | |||
1 | use ra_syntax::{ | 3 | use ra_syntax::{ |
2 | ast::{self, HasStringValue}, | 4 | ast::{self, HasQuotes, HasStringValue}, |
3 | AstToken, | 5 | AstToken, |
4 | SyntaxKind::{RAW_STRING, STRING}, | 6 | SyntaxKind::{RAW_STRING, STRING}, |
5 | TextSize, | 7 | TextSize, |
6 | }; | 8 | }; |
7 | 9 | ||
8 | use crate::{AssistContext, AssistId, Assists}; | 10 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
9 | 11 | ||
10 | // Assist: make_raw_string | 12 | // Assist: make_raw_string |
11 | // | 13 | // |
@@ -26,14 +28,25 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
26 | let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; | 28 | let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; |
27 | let value = token.value()?; | 29 | let value = token.value()?; |
28 | let target = token.syntax().text_range(); | 30 | let target = token.syntax().text_range(); |
29 | acc.add(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| { | 31 | acc.add( |
30 | let max_hash_streak = count_hashes(&value); | 32 | AssistId("make_raw_string", AssistKind::RefactorRewrite), |
31 | let mut hashes = String::with_capacity(max_hash_streak + 1); | 33 | "Rewrite as raw string", |
32 | for _ in 0..hashes.capacity() { | 34 | target, |
33 | hashes.push('#'); | 35 | |edit| { |
34 | } | 36 | let max_hash_streak = count_hashes(&value); |
35 | edit.replace(token.syntax().text_range(), format!("r{}\"{}\"{}", hashes, value, hashes)); | 37 | let hashes = "#".repeat(max_hash_streak + 1); |
36 | }) | 38 | if matches!(value, Cow::Borrowed(_)) { |
39 | // Avoid replacing the whole string to better position the cursor. | ||
40 | edit.insert(token.syntax().text_range().start(), format!("r{}", hashes)); | ||
41 | edit.insert(token.syntax().text_range().end(), format!("{}", hashes)); | ||
42 | } else { | ||
43 | edit.replace( | ||
44 | token.syntax().text_range(), | ||
45 | format!("r{}\"{}\"{}", hashes, value, hashes), | ||
46 | ); | ||
47 | } | ||
48 | }, | ||
49 | ) | ||
37 | } | 50 | } |
38 | 51 | ||
39 | // Assist: make_usual_string | 52 | // Assist: make_usual_string |
@@ -55,11 +68,24 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio | |||
55 | let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; | 68 | let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; |
56 | let value = token.value()?; | 69 | let value = token.value()?; |
57 | let target = token.syntax().text_range(); | 70 | let target = token.syntax().text_range(); |
58 | acc.add(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| { | 71 | acc.add( |
59 | // parse inside string to escape `"` | 72 | AssistId("make_usual_string", AssistKind::RefactorRewrite), |
60 | let escaped = value.escape_default().to_string(); | 73 | "Rewrite as regular string", |
61 | edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); | 74 | target, |
62 | }) | 75 | |edit| { |
76 | // parse inside string to escape `"` | ||
77 | let escaped = value.escape_default().to_string(); | ||
78 | if let Some(offsets) = token.quote_offsets() { | ||
79 | if token.text()[offsets.contents - token.syntax().text_range().start()] == escaped { | ||
80 | edit.replace(offsets.quotes.0, "\""); | ||
81 | edit.replace(offsets.quotes.1, "\""); | ||
82 | return; | ||
83 | } | ||
84 | } | ||
85 | |||
86 | edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); | ||
87 | }, | ||
88 | ) | ||
63 | } | 89 | } |
64 | 90 | ||
65 | // Assist: add_hash | 91 | // Assist: add_hash |
@@ -80,7 +106,7 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio | |||
80 | pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 106 | pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
81 | let token = ctx.find_token_at_offset(RAW_STRING)?; | 107 | let token = ctx.find_token_at_offset(RAW_STRING)?; |
82 | let target = token.text_range(); | 108 | let target = token.text_range(); |
83 | acc.add(AssistId("add_hash"), "Add # to raw string", target, |edit| { | 109 | acc.add(AssistId("add_hash", AssistKind::Refactor), "Add # to raw string", target, |edit| { |
84 | edit.insert(token.text_range().start() + TextSize::of('r'), "#"); | 110 | edit.insert(token.text_range().start() + TextSize::of('r'), "#"); |
85 | edit.insert(token.text_range().end(), "#"); | 111 | edit.insert(token.text_range().end(), "#"); |
86 | }) | 112 | }) |
@@ -109,18 +135,23 @@ pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
109 | return None; | 135 | return None; |
110 | } | 136 | } |
111 | let target = token.text_range(); | 137 | let target = token.text_range(); |
112 | acc.add(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| { | 138 | acc.add( |
113 | let result = &text[2..text.len() - 1]; | 139 | AssistId("remove_hash", AssistKind::RefactorRewrite), |
114 | let result = if result.starts_with('\"') { | 140 | "Remove hash from raw string", |
115 | // FIXME: this logic is wrong, not only the last has has to handled specially | 141 | target, |
116 | // no more hash, escape | 142 | |edit| { |
117 | let internal_str = &result[1..result.len() - 1]; | 143 | let result = &text[2..text.len() - 1]; |
118 | format!("\"{}\"", internal_str.escape_default().to_string()) | 144 | let result = if result.starts_with('\"') { |
119 | } else { | 145 | // FIXME: this logic is wrong, not only the last has has to handled specially |
120 | result.to_owned() | 146 | // no more hash, escape |
121 | }; | 147 | let internal_str = &result[1..result.len() - 1]; |
122 | edit.replace(token.text_range(), format!("r{}", result)); | 148 | format!("\"{}\"", internal_str.escape_default().to_string()) |
123 | }) | 149 | } else { |
150 | result.to_owned() | ||
151 | }; | ||
152 | edit.replace(token.text_range(), format!("r{}", result)); | ||
153 | }, | ||
154 | ) | ||
124 | } | 155 | } |
125 | 156 | ||
126 | fn count_hashes(s: &str) -> usize { | 157 | fn count_hashes(s: &str) -> usize { |
@@ -158,16 +189,16 @@ mod test { | |||
158 | check_assist( | 189 | check_assist( |
159 | make_raw_string, | 190 | make_raw_string, |
160 | r#" | 191 | r#" |
161 | fn f() { | 192 | fn f() { |
162 | let s = <|>"random\nstring"; | 193 | let s = <|>"random\nstring"; |
163 | } | 194 | } |
164 | "#, | 195 | "#, |
165 | r##" | 196 | r##" |
166 | fn f() { | 197 | fn f() { |
167 | let s = r#"random | 198 | let s = r#"random |
168 | string"#; | 199 | string"#; |
169 | } | 200 | } |
170 | "##, | 201 | "##, |
171 | ) | 202 | ) |
172 | } | 203 | } |
173 | 204 | ||
@@ -193,16 +224,16 @@ string"#; | |||
193 | check_assist( | 224 | check_assist( |
194 | make_raw_string, | 225 | make_raw_string, |
195 | r###" | 226 | r###" |
196 | fn f() { | 227 | fn f() { |
197 | let s = <|>"#random##\nstring"; | 228 | let s = <|>"#random##\nstring"; |
198 | } | 229 | } |
199 | "###, | 230 | "###, |
200 | r####" | 231 | r####" |
201 | fn f() { | 232 | fn f() { |
202 | let s = r#"#random## | 233 | let s = r#"#random## |
203 | string"#; | 234 | string"#; |
204 | } | 235 | } |
205 | "####, | 236 | "####, |
206 | ) | 237 | ) |
207 | } | 238 | } |
208 | 239 | ||
@@ -211,16 +242,16 @@ string"#; | |||
211 | check_assist( | 242 | check_assist( |
212 | make_raw_string, | 243 | make_raw_string, |
213 | r###" | 244 | r###" |
214 | fn f() { | 245 | fn f() { |
215 | let s = <|>"#random\"##\nstring"; | 246 | let s = <|>"#random\"##\nstring"; |
216 | } | 247 | } |
217 | "###, | 248 | "###, |
218 | r####" | 249 | r####" |
219 | fn f() { | 250 | fn f() { |
220 | let s = r###"#random"## | 251 | let s = r###"#random"## |
221 | string"###; | 252 | string"###; |
222 | } | 253 | } |
223 | "####, | 254 | "####, |
224 | ) | 255 | ) |
225 | } | 256 | } |
226 | 257 | ||
diff --git a/crates/ra_assists/src/handlers/remove_dbg.rs b/crates/ra_assists/src/handlers/remove_dbg.rs index 961ee1731..a616cca57 100644 --- a/crates/ra_assists/src/handlers/remove_dbg.rs +++ b/crates/ra_assists/src/handlers/remove_dbg.rs | |||
@@ -3,7 +3,7 @@ use ra_syntax::{ | |||
3 | TextSize, T, | 3 | TextSize, T, |
4 | }; | 4 | }; |
5 | 5 | ||
6 | use crate::{AssistContext, AssistId, Assists}; | 6 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
7 | 7 | ||
8 | // Assist: remove_dbg | 8 | // Assist: remove_dbg |
9 | // | 9 | // |
@@ -38,7 +38,7 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
38 | }; | 38 | }; |
39 | 39 | ||
40 | let target = macro_call.syntax().text_range(); | 40 | let target = macro_call.syntax().text_range(); |
41 | acc.add(AssistId("remove_dbg"), "Remove dbg!()", target, |builder| { | 41 | acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", target, |builder| { |
42 | builder.replace(macro_range, macro_content); | 42 | builder.replace(macro_range, macro_content); |
43 | }) | 43 | }) |
44 | } | 44 | } |
diff --git a/crates/ra_assists/src/handlers/remove_mut.rs b/crates/ra_assists/src/handlers/remove_mut.rs index fe4eada03..ef55c354e 100644 --- a/crates/ra_assists/src/handlers/remove_mut.rs +++ b/crates/ra_assists/src/handlers/remove_mut.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use ra_syntax::{SyntaxKind, TextRange, T}; | 1 | use ra_syntax::{SyntaxKind, TextRange, T}; |
2 | 2 | ||
3 | use crate::{AssistContext, AssistId, Assists}; | 3 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
4 | 4 | ||
5 | // Assist: remove_mut | 5 | // Assist: remove_mut |
6 | // | 6 | // |
@@ -26,7 +26,12 @@ pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
26 | }; | 26 | }; |
27 | 27 | ||
28 | let target = mut_token.text_range(); | 28 | let target = mut_token.text_range(); |
29 | acc.add(AssistId("remove_mut"), "Remove `mut` keyword", target, |builder| { | 29 | acc.add( |
30 | builder.delete(TextRange::new(delete_from, delete_to)); | 30 | AssistId("remove_mut", AssistKind::Refactor), |
31 | }) | 31 | "Remove `mut` keyword", |
32 | target, | ||
33 | |builder| { | ||
34 | builder.delete(TextRange::new(delete_from, delete_to)); | ||
35 | }, | ||
36 | ) | ||
32 | } | 37 | } |
diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs index 897da2832..2ac1c56cf 100644 --- a/crates/ra_assists/src/handlers/reorder_fields.rs +++ b/crates/ra_assists/src/handlers/reorder_fields.rs | |||
@@ -1,11 +1,11 @@ | |||
1 | use std::collections::HashMap; | 1 | use itertools::Itertools; |
2 | use rustc_hash::FxHashMap; | ||
2 | 3 | ||
3 | use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct}; | 4 | use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct}; |
4 | use itertools::Itertools; | ||
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode}; | 6 | use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode}; |
7 | 7 | ||
8 | use crate::{AssistContext, AssistId, Assists}; | 8 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
9 | 9 | ||
10 | // Assist: reorder_fields | 10 | // Assist: reorder_fields |
11 | // | 11 | // |
@@ -42,11 +42,16 @@ fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
42 | } | 42 | } |
43 | 43 | ||
44 | let target = record.syntax().text_range(); | 44 | let target = record.syntax().text_range(); |
45 | acc.add(AssistId("reorder_fields"), "Reorder record fields", target, |edit| { | 45 | acc.add( |
46 | for (old, new) in fields.iter().zip(&sorted_fields) { | 46 | AssistId("reorder_fields", AssistKind::RefactorRewrite), |
47 | algo::diff(old, new).into_text_edit(edit.text_edit_builder()); | 47 | "Reorder record fields", |
48 | } | 48 | target, |
49 | }) | 49 | |edit| { |
50 | for (old, new) in fields.iter().zip(&sorted_fields) { | ||
51 | algo::diff(old, new).into_text_edit(edit.text_edit_builder()); | ||
52 | } | ||
53 | }, | ||
54 | ) | ||
50 | } | 55 | } |
51 | 56 | ||
52 | fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> { | 57 | fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> { |
@@ -87,13 +92,13 @@ fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option | |||
87 | } | 92 | } |
88 | } | 93 | } |
89 | 94 | ||
90 | fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<HashMap<String, usize>> { | 95 | fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { |
91 | Some( | 96 | Some( |
92 | struct_definition(path, &ctx.sema)? | 97 | struct_definition(path, &ctx.sema)? |
93 | .fields(ctx.db) | 98 | .fields(ctx.db()) |
94 | .iter() | 99 | .iter() |
95 | .enumerate() | 100 | .enumerate() |
96 | .map(|(idx, field)| (field.name(ctx.db).to_string(), idx)) | 101 | .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx)) |
97 | .collect(), | 102 | .collect(), |
98 | ) | 103 | ) |
99 | } | 104 | } |
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 e016f51c3..b7e30a7f2 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 | |||
@@ -8,7 +8,7 @@ use ra_syntax::{ | |||
8 | AstNode, | 8 | AstNode, |
9 | }; | 9 | }; |
10 | 10 | ||
11 | use crate::{utils::TryEnum, AssistContext, AssistId, Assists}; | 11 | use crate::{utils::TryEnum, AssistContext, AssistId, AssistKind, Assists}; |
12 | 12 | ||
13 | // Assist: replace_if_let_with_match | 13 | // Assist: replace_if_let_with_match |
14 | // | 14 | // |
@@ -48,28 +48,35 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) | |||
48 | }; | 48 | }; |
49 | 49 | ||
50 | let target = if_expr.syntax().text_range(); | 50 | let target = if_expr.syntax().text_range(); |
51 | acc.add(AssistId("replace_if_let_with_match"), "Replace with match", target, move |edit| { | 51 | acc.add( |
52 | let match_expr = { | 52 | AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite), |
53 | let then_arm = { | 53 | "Replace with match", |
54 | let then_expr = unwrap_trivial_block(then_block); | 54 | target, |
55 | make::match_arm(vec![pat.clone()], then_expr) | 55 | move |edit| { |
56 | let match_expr = { | ||
57 | let then_arm = { | ||
58 | let then_block = then_block.reset_indent().indent(IndentLevel(1)); | ||
59 | let then_expr = unwrap_trivial_block(then_block); | ||
60 | make::match_arm(vec![pat.clone()], then_expr) | ||
61 | }; | ||
62 | let else_arm = { | ||
63 | let pattern = ctx | ||
64 | .sema | ||
65 | .type_of_pat(&pat) | ||
66 | .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty)) | ||
67 | .map(|it| it.sad_pattern()) | ||
68 | .unwrap_or_else(|| make::placeholder_pat().into()); | ||
69 | let else_expr = unwrap_trivial_block(else_block); | ||
70 | make::match_arm(vec![pattern], else_expr) | ||
71 | }; | ||
72 | let match_expr = | ||
73 | make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])); | ||
74 | match_expr.indent(IndentLevel::from_node(if_expr.syntax())) | ||
56 | }; | 75 | }; |
57 | let else_arm = { | ||
58 | let pattern = ctx | ||
59 | .sema | ||
60 | .type_of_pat(&pat) | ||
61 | .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty)) | ||
62 | .map(|it| it.sad_pattern()) | ||
63 | .unwrap_or_else(|| make::placeholder_pat().into()); | ||
64 | let else_expr = unwrap_trivial_block(else_block); | ||
65 | make::match_arm(vec![pattern], else_expr) | ||
66 | }; | ||
67 | make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])) | ||
68 | .indent(IndentLevel::from_node(if_expr.syntax())) | ||
69 | }; | ||
70 | 76 | ||
71 | edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr); | 77 | edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr); |
72 | }) | 78 | }, |
79 | ) | ||
73 | } | 80 | } |
74 | 81 | ||
75 | #[cfg(test)] | 82 | #[cfg(test)] |
@@ -213,4 +220,36 @@ fn foo(x: Result<i32, ()>) { | |||
213 | "#, | 220 | "#, |
214 | ); | 221 | ); |
215 | } | 222 | } |
223 | |||
224 | #[test] | ||
225 | fn nested_indent() { | ||
226 | check_assist( | ||
227 | replace_if_let_with_match, | ||
228 | r#" | ||
229 | fn main() { | ||
230 | if true { | ||
231 | <|>if let Ok(rel_path) = path.strip_prefix(root_path) { | ||
232 | let rel_path = RelativePathBuf::from_path(rel_path).ok()?; | ||
233 | Some((*id, rel_path)) | ||
234 | } else { | ||
235 | None | ||
236 | } | ||
237 | } | ||
238 | } | ||
239 | "#, | ||
240 | r#" | ||
241 | fn main() { | ||
242 | if true { | ||
243 | match path.strip_prefix(root_path) { | ||
244 | Ok(rel_path) => { | ||
245 | let rel_path = RelativePathBuf::from_path(rel_path).ok()?; | ||
246 | Some((*id, rel_path)) | ||
247 | } | ||
248 | _ => None, | ||
249 | } | ||
250 | } | ||
251 | } | ||
252 | "#, | ||
253 | ) | ||
254 | } | ||
216 | } | 255 | } |
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 761557ac0..a49292c97 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,7 +9,7 @@ use ra_syntax::{ | |||
9 | AstNode, T, | 9 | AstNode, T, |
10 | }; | 10 | }; |
11 | 11 | ||
12 | use crate::{utils::TryEnum, AssistContext, AssistId, Assists}; | 12 | use crate::{utils::TryEnum, AssistContext, AssistId, AssistKind, Assists}; |
13 | 13 | ||
14 | // Assist: replace_let_with_if_let | 14 | // Assist: replace_let_with_if_let |
15 | // | 15 | // |
@@ -44,24 +44,31 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> | |||
44 | 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()); |
45 | 45 | ||
46 | let target = let_kw.text_range(); | 46 | let target = let_kw.text_range(); |
47 | acc.add(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| { | 47 | acc.add( |
48 | let with_placeholder: ast::Pat = match happy_variant { | 48 | AssistId("replace_let_with_if_let", AssistKind::RefactorRewrite), |
49 | None => make::placeholder_pat().into(), | 49 | "Replace with if-let", |
50 | Some(var_name) => make::tuple_struct_pat( | 50 | target, |
51 | make::path_unqualified(make::path_segment(make::name_ref(var_name))), | 51 | |edit| { |
52 | once(make::placeholder_pat().into()), | 52 | let with_placeholder: ast::Pat = match happy_variant { |
53 | ) | 53 | None => make::placeholder_pat().into(), |
54 | .into(), | 54 | Some(var_name) => make::tuple_struct_pat( |
55 | }; | 55 | make::path_unqualified(make::path_segment(make::name_ref(var_name))), |
56 | let block = make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax())); | 56 | once(make::placeholder_pat().into()), |
57 | let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block); | 57 | ) |
58 | let stmt = make::expr_stmt(if_); | 58 | .into(), |
59 | }; | ||
60 | let block = | ||
61 | make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax())); | ||
62 | let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block); | ||
63 | let stmt = make::expr_stmt(if_); | ||
59 | 64 | ||
60 | let placeholder = stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap(); | 65 | let placeholder = |
61 | let stmt = stmt.replace_descendant(placeholder.into(), original_pat); | 66 | stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap(); |
67 | let stmt = stmt.replace_descendant(placeholder.into(), original_pat); | ||
62 | 68 | ||
63 | edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt)); | 69 | edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt)); |
64 | }) | 70 | }, |
71 | ) | ||
65 | } | 72 | } |
66 | 73 | ||
67 | #[cfg(test)] | 74 | #[cfg(test)] |
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 0197a8cf0..dfd314abf 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,7 +1,10 @@ | |||
1 | use hir; | 1 | use hir; |
2 | use ra_syntax::{ast, AstNode, SmolStr, TextRange}; | 2 | use ra_syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SmolStr, SyntaxNode}; |
3 | 3 | ||
4 | use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists}; | 4 | use crate::{ |
5 | utils::{find_insert_use_container, insert_use_statement}, | ||
6 | AssistContext, AssistId, AssistKind, Assists, | ||
7 | }; | ||
5 | 8 | ||
6 | // Assist: replace_qualified_name_with_use | 9 | // Assist: replace_qualified_name_with_use |
7 | // | 10 | // |
@@ -34,21 +37,23 @@ pub(crate) fn replace_qualified_name_with_use( | |||
34 | 37 | ||
35 | let target = path.syntax().text_range(); | 38 | let target = path.syntax().text_range(); |
36 | acc.add( | 39 | acc.add( |
37 | AssistId("replace_qualified_name_with_use"), | 40 | AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite), |
38 | "Replace qualified path with use", | 41 | "Replace qualified path with use", |
39 | target, | 42 | target, |
40 | |builder| { | 43 | |builder| { |
41 | let path_to_import = hir_path.mod_path().clone(); | 44 | let path_to_import = hir_path.mod_path().clone(); |
45 | let container = match find_insert_use_container(path.syntax(), ctx) { | ||
46 | Some(c) => c, | ||
47 | None => return, | ||
48 | }; | ||
42 | insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_builder()); | 49 | insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_builder()); |
43 | 50 | ||
44 | if let Some(last) = path.segment() { | 51 | // Now that we've brought the name into scope, re-qualify all paths that could be |
45 | // Here we are assuming the assist will provide a correct use statement | 52 | // affected (that is, all paths inside the node we added the `use` to). |
46 | // so we can delete the path qualifier | 53 | let mut rewriter = SyntaxRewriter::default(); |
47 | builder.delete(TextRange::new( | 54 | let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone()); |
48 | path.syntax().text_range().start(), | 55 | shorten_paths(&mut rewriter, syntax, path); |
49 | last.syntax().text_range().start(), | 56 | builder.rewrite(rewriter); |
50 | )); | ||
51 | } | ||
52 | }, | 57 | }, |
53 | ) | 58 | ) |
54 | } | 59 | } |
@@ -73,6 +78,69 @@ fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> { | |||
73 | Some(ps) | 78 | Some(ps) |
74 | } | 79 | } |
75 | 80 | ||
81 | /// Adds replacements to `re` that shorten `path` in all descendants of `node`. | ||
82 | fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: ast::Path) { | ||
83 | for child in node.children() { | ||
84 | match_ast! { | ||
85 | match child { | ||
86 | // Don't modify `use` items, as this can break the `use` item when injecting a new | ||
87 | // import into the use tree. | ||
88 | ast::UseItem(_it) => continue, | ||
89 | // Don't descend into submodules, they don't have the same `use` items in scope. | ||
90 | ast::Module(_it) => continue, | ||
91 | |||
92 | ast::Path(p) => { | ||
93 | match maybe_replace_path(rewriter, p.clone(), path.clone()) { | ||
94 | Some(()) => {}, | ||
95 | None => shorten_paths(rewriter, p.syntax().clone(), path.clone()), | ||
96 | } | ||
97 | }, | ||
98 | _ => shorten_paths(rewriter, child, path.clone()), | ||
99 | } | ||
100 | } | ||
101 | } | ||
102 | } | ||
103 | |||
104 | fn maybe_replace_path( | ||
105 | rewriter: &mut SyntaxRewriter<'static>, | ||
106 | path: ast::Path, | ||
107 | target: ast::Path, | ||
108 | ) -> Option<()> { | ||
109 | if !path_eq(path.clone(), target.clone()) { | ||
110 | return None; | ||
111 | } | ||
112 | |||
113 | // Shorten `path`, leaving only its last segment. | ||
114 | if let Some(parent) = path.qualifier() { | ||
115 | rewriter.delete(parent.syntax()); | ||
116 | } | ||
117 | if let Some(double_colon) = path.coloncolon_token() { | ||
118 | rewriter.delete(&double_colon); | ||
119 | } | ||
120 | |||
121 | Some(()) | ||
122 | } | ||
123 | |||
124 | fn path_eq(lhs: ast::Path, rhs: ast::Path) -> bool { | ||
125 | let mut lhs_curr = lhs; | ||
126 | let mut rhs_curr = rhs; | ||
127 | loop { | ||
128 | match (lhs_curr.segment(), rhs_curr.segment()) { | ||
129 | (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (), | ||
130 | _ => return false, | ||
131 | } | ||
132 | |||
133 | match (lhs_curr.qualifier(), rhs_curr.qualifier()) { | ||
134 | (Some(lhs), Some(rhs)) => { | ||
135 | lhs_curr = lhs; | ||
136 | rhs_curr = rhs; | ||
137 | } | ||
138 | (None, None) => return true, | ||
139 | _ => return false, | ||
140 | } | ||
141 | } | ||
142 | } | ||
143 | |||
76 | #[cfg(test)] | 144 | #[cfg(test)] |
77 | mod tests { | 145 | mod tests { |
78 | use crate::tests::{check_assist, check_assist_not_applicable}; | 146 | use crate::tests::{check_assist, check_assist_not_applicable}; |
@@ -83,10 +151,10 @@ mod tests { | |||
83 | fn test_replace_add_use_no_anchor() { | 151 | fn test_replace_add_use_no_anchor() { |
84 | check_assist( | 152 | check_assist( |
85 | replace_qualified_name_with_use, | 153 | replace_qualified_name_with_use, |
86 | " | 154 | r" |
87 | std::fmt::Debug<|> | 155 | std::fmt::Debug<|> |
88 | ", | 156 | ", |
89 | " | 157 | r" |
90 | use std::fmt::Debug; | 158 | use std::fmt::Debug; |
91 | 159 | ||
92 | Debug | 160 | Debug |
@@ -97,13 +165,13 @@ Debug | |||
97 | fn test_replace_add_use_no_anchor_with_item_below() { | 165 | fn test_replace_add_use_no_anchor_with_item_below() { |
98 | check_assist( | 166 | check_assist( |
99 | replace_qualified_name_with_use, | 167 | replace_qualified_name_with_use, |
100 | " | 168 | r" |
101 | std::fmt::Debug<|> | 169 | std::fmt::Debug<|> |
102 | 170 | ||
103 | fn main() { | 171 | fn main() { |
104 | } | 172 | } |
105 | ", | 173 | ", |
106 | " | 174 | r" |
107 | use std::fmt::Debug; | 175 | use std::fmt::Debug; |
108 | 176 | ||
109 | Debug | 177 | Debug |
@@ -118,13 +186,13 @@ fn main() { | |||
118 | fn test_replace_add_use_no_anchor_with_item_above() { | 186 | fn test_replace_add_use_no_anchor_with_item_above() { |
119 | check_assist( | 187 | check_assist( |
120 | replace_qualified_name_with_use, | 188 | replace_qualified_name_with_use, |
121 | " | 189 | r" |
122 | fn main() { | 190 | fn main() { |
123 | } | 191 | } |
124 | 192 | ||
125 | std::fmt::Debug<|> | 193 | std::fmt::Debug<|> |
126 | ", | 194 | ", |
127 | " | 195 | r" |
128 | use std::fmt::Debug; | 196 | use std::fmt::Debug; |
129 | 197 | ||
130 | fn main() { | 198 | fn main() { |
@@ -139,10 +207,10 @@ Debug | |||
139 | fn test_replace_add_use_no_anchor_2seg() { | 207 | fn test_replace_add_use_no_anchor_2seg() { |
140 | check_assist( | 208 | check_assist( |
141 | replace_qualified_name_with_use, | 209 | replace_qualified_name_with_use, |
142 | " | 210 | r" |
143 | std::fmt<|>::Debug | 211 | std::fmt<|>::Debug |
144 | ", | 212 | ", |
145 | " | 213 | r" |
146 | use std::fmt; | 214 | use std::fmt; |
147 | 215 | ||
148 | fmt::Debug | 216 | fmt::Debug |
@@ -154,13 +222,13 @@ fmt::Debug | |||
154 | fn test_replace_add_use() { | 222 | fn test_replace_add_use() { |
155 | check_assist( | 223 | check_assist( |
156 | replace_qualified_name_with_use, | 224 | replace_qualified_name_with_use, |
157 | " | 225 | r" |
158 | use stdx; | 226 | use stdx; |
159 | 227 | ||
160 | impl std::fmt::Debug<|> for Foo { | 228 | impl std::fmt::Debug<|> for Foo { |
161 | } | 229 | } |
162 | ", | 230 | ", |
163 | " | 231 | r" |
164 | use stdx; | 232 | use stdx; |
165 | use std::fmt::Debug; | 233 | use std::fmt::Debug; |
166 | 234 | ||
@@ -174,11 +242,11 @@ impl Debug for Foo { | |||
174 | fn test_replace_file_use_other_anchor() { | 242 | fn test_replace_file_use_other_anchor() { |
175 | check_assist( | 243 | check_assist( |
176 | replace_qualified_name_with_use, | 244 | replace_qualified_name_with_use, |
177 | " | 245 | r" |
178 | impl std::fmt::Debug<|> for Foo { | 246 | impl std::fmt::Debug<|> for Foo { |
179 | } | 247 | } |
180 | ", | 248 | ", |
181 | " | 249 | r" |
182 | use std::fmt::Debug; | 250 | use std::fmt::Debug; |
183 | 251 | ||
184 | impl Debug for Foo { | 252 | impl Debug for Foo { |
@@ -191,11 +259,11 @@ impl Debug for Foo { | |||
191 | fn test_replace_add_use_other_anchor_indent() { | 259 | fn test_replace_add_use_other_anchor_indent() { |
192 | check_assist( | 260 | check_assist( |
193 | replace_qualified_name_with_use, | 261 | replace_qualified_name_with_use, |
194 | " | 262 | r" |
195 | impl std::fmt::Debug<|> for Foo { | 263 | impl std::fmt::Debug<|> for Foo { |
196 | } | 264 | } |
197 | ", | 265 | ", |
198 | " | 266 | r" |
199 | use std::fmt::Debug; | 267 | use std::fmt::Debug; |
200 | 268 | ||
201 | impl Debug for Foo { | 269 | impl Debug for Foo { |
@@ -208,13 +276,13 @@ impl Debug for Foo { | |||
208 | fn test_replace_split_different() { | 276 | fn test_replace_split_different() { |
209 | check_assist( | 277 | check_assist( |
210 | replace_qualified_name_with_use, | 278 | replace_qualified_name_with_use, |
211 | " | 279 | r" |
212 | use std::fmt; | 280 | use std::fmt; |
213 | 281 | ||
214 | impl std::io<|> for Foo { | 282 | impl std::io<|> for Foo { |
215 | } | 283 | } |
216 | ", | 284 | ", |
217 | " | 285 | r" |
218 | use std::{io, fmt}; | 286 | use std::{io, fmt}; |
219 | 287 | ||
220 | impl io for Foo { | 288 | impl io for Foo { |
@@ -227,13 +295,13 @@ impl io for Foo { | |||
227 | fn test_replace_split_self_for_use() { | 295 | fn test_replace_split_self_for_use() { |
228 | check_assist( | 296 | check_assist( |
229 | replace_qualified_name_with_use, | 297 | replace_qualified_name_with_use, |
230 | " | 298 | r" |
231 | use std::fmt; | 299 | use std::fmt; |
232 | 300 | ||
233 | impl std::fmt::Debug<|> for Foo { | 301 | impl std::fmt::Debug<|> for Foo { |
234 | } | 302 | } |
235 | ", | 303 | ", |
236 | " | 304 | r" |
237 | use std::fmt::{self, Debug, }; | 305 | use std::fmt::{self, Debug, }; |
238 | 306 | ||
239 | impl Debug for Foo { | 307 | impl Debug for Foo { |
@@ -246,13 +314,13 @@ impl Debug for Foo { | |||
246 | fn test_replace_split_self_for_target() { | 314 | fn test_replace_split_self_for_target() { |
247 | check_assist( | 315 | check_assist( |
248 | replace_qualified_name_with_use, | 316 | replace_qualified_name_with_use, |
249 | " | 317 | r" |
250 | use std::fmt::Debug; | 318 | use std::fmt::Debug; |
251 | 319 | ||
252 | impl std::fmt<|> for Foo { | 320 | impl std::fmt<|> for Foo { |
253 | } | 321 | } |
254 | ", | 322 | ", |
255 | " | 323 | r" |
256 | use std::fmt::{self, Debug}; | 324 | use std::fmt::{self, Debug}; |
257 | 325 | ||
258 | impl fmt for Foo { | 326 | impl fmt for Foo { |
@@ -265,13 +333,13 @@ impl fmt for Foo { | |||
265 | fn test_replace_add_to_nested_self_nested() { | 333 | fn test_replace_add_to_nested_self_nested() { |
266 | check_assist( | 334 | check_assist( |
267 | replace_qualified_name_with_use, | 335 | replace_qualified_name_with_use, |
268 | " | 336 | r" |
269 | use std::fmt::{Debug, nested::{Display}}; | 337 | use std::fmt::{Debug, nested::{Display}}; |
270 | 338 | ||
271 | impl std::fmt::nested<|> for Foo { | 339 | impl std::fmt::nested<|> for Foo { |
272 | } | 340 | } |
273 | ", | 341 | ", |
274 | " | 342 | r" |
275 | use std::fmt::{Debug, nested::{Display, self}}; | 343 | use std::fmt::{Debug, nested::{Display, self}}; |
276 | 344 | ||
277 | impl nested for Foo { | 345 | impl nested for Foo { |
@@ -284,13 +352,13 @@ impl nested for Foo { | |||
284 | fn test_replace_add_to_nested_self_already_included() { | 352 | fn test_replace_add_to_nested_self_already_included() { |
285 | check_assist( | 353 | check_assist( |
286 | replace_qualified_name_with_use, | 354 | replace_qualified_name_with_use, |
287 | " | 355 | r" |
288 | use std::fmt::{Debug, nested::{self, Display}}; | 356 | use std::fmt::{Debug, nested::{self, Display}}; |
289 | 357 | ||
290 | impl std::fmt::nested<|> for Foo { | 358 | impl std::fmt::nested<|> for Foo { |
291 | } | 359 | } |
292 | ", | 360 | ", |
293 | " | 361 | r" |
294 | use std::fmt::{Debug, nested::{self, Display}}; | 362 | use std::fmt::{Debug, nested::{self, Display}}; |
295 | 363 | ||
296 | impl nested for Foo { | 364 | impl nested for Foo { |
@@ -303,13 +371,13 @@ impl nested for Foo { | |||
303 | fn test_replace_add_to_nested_nested() { | 371 | fn test_replace_add_to_nested_nested() { |
304 | check_assist( | 372 | check_assist( |
305 | replace_qualified_name_with_use, | 373 | replace_qualified_name_with_use, |
306 | " | 374 | r" |
307 | use std::fmt::{Debug, nested::{Display}}; | 375 | use std::fmt::{Debug, nested::{Display}}; |
308 | 376 | ||
309 | impl std::fmt::nested::Debug<|> for Foo { | 377 | impl std::fmt::nested::Debug<|> for Foo { |
310 | } | 378 | } |
311 | ", | 379 | ", |
312 | " | 380 | r" |
313 | use std::fmt::{Debug, nested::{Display, Debug}}; | 381 | use std::fmt::{Debug, nested::{Display, Debug}}; |
314 | 382 | ||
315 | impl Debug for Foo { | 383 | impl Debug for Foo { |
@@ -322,13 +390,13 @@ impl Debug for Foo { | |||
322 | fn test_replace_split_common_target_longer() { | 390 | fn test_replace_split_common_target_longer() { |
323 | check_assist( | 391 | check_assist( |
324 | replace_qualified_name_with_use, | 392 | replace_qualified_name_with_use, |
325 | " | 393 | r" |
326 | use std::fmt::Debug; | 394 | use std::fmt::Debug; |
327 | 395 | ||
328 | impl std::fmt::nested::Display<|> for Foo { | 396 | impl std::fmt::nested::Display<|> for Foo { |
329 | } | 397 | } |
330 | ", | 398 | ", |
331 | " | 399 | r" |
332 | use std::fmt::{nested::Display, Debug}; | 400 | use std::fmt::{nested::Display, Debug}; |
333 | 401 | ||
334 | impl Display for Foo { | 402 | impl Display for Foo { |
@@ -341,13 +409,13 @@ impl Display for Foo { | |||
341 | fn test_replace_split_common_use_longer() { | 409 | fn test_replace_split_common_use_longer() { |
342 | check_assist( | 410 | check_assist( |
343 | replace_qualified_name_with_use, | 411 | replace_qualified_name_with_use, |
344 | " | 412 | r" |
345 | use std::fmt::nested::Debug; | 413 | use std::fmt::nested::Debug; |
346 | 414 | ||
347 | impl std::fmt::Display<|> for Foo { | 415 | impl std::fmt::Display<|> for Foo { |
348 | } | 416 | } |
349 | ", | 417 | ", |
350 | " | 418 | r" |
351 | use std::fmt::{Display, nested::Debug}; | 419 | use std::fmt::{Display, nested::Debug}; |
352 | 420 | ||
353 | impl Display for Foo { | 421 | impl Display for Foo { |
@@ -360,7 +428,7 @@ impl Display for Foo { | |||
360 | fn test_replace_use_nested_import() { | 428 | fn test_replace_use_nested_import() { |
361 | check_assist( | 429 | check_assist( |
362 | replace_qualified_name_with_use, | 430 | replace_qualified_name_with_use, |
363 | " | 431 | r" |
364 | use crate::{ | 432 | use crate::{ |
365 | ty::{Substs, Ty}, | 433 | ty::{Substs, Ty}, |
366 | AssocItem, | 434 | AssocItem, |
@@ -368,7 +436,7 @@ use crate::{ | |||
368 | 436 | ||
369 | fn foo() { crate::ty::lower<|>::trait_env() } | 437 | fn foo() { crate::ty::lower<|>::trait_env() } |
370 | ", | 438 | ", |
371 | " | 439 | r" |
372 | use crate::{ | 440 | use crate::{ |
373 | ty::{Substs, Ty, lower}, | 441 | ty::{Substs, Ty, lower}, |
374 | AssocItem, | 442 | AssocItem, |
@@ -383,13 +451,13 @@ fn foo() { lower::trait_env() } | |||
383 | fn test_replace_alias() { | 451 | fn test_replace_alias() { |
384 | check_assist( | 452 | check_assist( |
385 | replace_qualified_name_with_use, | 453 | replace_qualified_name_with_use, |
386 | " | 454 | r" |
387 | use std::fmt as foo; | 455 | use std::fmt as foo; |
388 | 456 | ||
389 | impl foo::Debug<|> for Foo { | 457 | impl foo::Debug<|> for Foo { |
390 | } | 458 | } |
391 | ", | 459 | ", |
392 | " | 460 | r" |
393 | use std::fmt as foo; | 461 | use std::fmt as foo; |
394 | 462 | ||
395 | impl Debug for Foo { | 463 | impl Debug for Foo { |
@@ -402,7 +470,7 @@ impl Debug for Foo { | |||
402 | fn test_replace_not_applicable_one_segment() { | 470 | fn test_replace_not_applicable_one_segment() { |
403 | check_assist_not_applicable( | 471 | check_assist_not_applicable( |
404 | replace_qualified_name_with_use, | 472 | replace_qualified_name_with_use, |
405 | " | 473 | r" |
406 | impl foo<|> for Foo { | 474 | impl foo<|> for Foo { |
407 | } | 475 | } |
408 | ", | 476 | ", |
@@ -413,7 +481,7 @@ impl foo<|> for Foo { | |||
413 | fn test_replace_not_applicable_in_use() { | 481 | fn test_replace_not_applicable_in_use() { |
414 | check_assist_not_applicable( | 482 | check_assist_not_applicable( |
415 | replace_qualified_name_with_use, | 483 | replace_qualified_name_with_use, |
416 | " | 484 | r" |
417 | use std::fmt<|>; | 485 | use std::fmt<|>; |
418 | ", | 486 | ", |
419 | ); | 487 | ); |
@@ -423,14 +491,14 @@ use std::fmt<|>; | |||
423 | fn test_replace_add_use_no_anchor_in_mod_mod() { | 491 | fn test_replace_add_use_no_anchor_in_mod_mod() { |
424 | check_assist( | 492 | check_assist( |
425 | replace_qualified_name_with_use, | 493 | replace_qualified_name_with_use, |
426 | " | 494 | r" |
427 | mod foo { | 495 | mod foo { |
428 | mod bar { | 496 | mod bar { |
429 | std::fmt::Debug<|> | 497 | std::fmt::Debug<|> |
430 | } | 498 | } |
431 | } | 499 | } |
432 | ", | 500 | ", |
433 | " | 501 | r" |
434 | mod foo { | 502 | mod foo { |
435 | mod bar { | 503 | mod bar { |
436 | use std::fmt::Debug; | 504 | use std::fmt::Debug; |
@@ -446,14 +514,14 @@ mod foo { | |||
446 | fn inserts_imports_after_inner_attributes() { | 514 | fn inserts_imports_after_inner_attributes() { |
447 | check_assist( | 515 | check_assist( |
448 | replace_qualified_name_with_use, | 516 | replace_qualified_name_with_use, |
449 | " | 517 | r" |
450 | #![allow(dead_code)] | 518 | #![allow(dead_code)] |
451 | 519 | ||
452 | fn main() { | 520 | fn main() { |
453 | std::fmt::Debug<|> | 521 | std::fmt::Debug<|> |
454 | } | 522 | } |
455 | ", | 523 | ", |
456 | " | 524 | r" |
457 | #![allow(dead_code)] | 525 | #![allow(dead_code)] |
458 | use std::fmt::Debug; | 526 | use std::fmt::Debug; |
459 | 527 | ||
@@ -463,4 +531,116 @@ fn main() { | |||
463 | ", | 531 | ", |
464 | ); | 532 | ); |
465 | } | 533 | } |
534 | |||
535 | #[test] | ||
536 | fn replaces_all_affected_paths() { | ||
537 | check_assist( | ||
538 | replace_qualified_name_with_use, | ||
539 | r" | ||
540 | fn main() { | ||
541 | std::fmt::Debug<|>; | ||
542 | let x: std::fmt::Debug = std::fmt::Debug; | ||
543 | } | ||
544 | ", | ||
545 | r" | ||
546 | use std::fmt::Debug; | ||
547 | |||
548 | fn main() { | ||
549 | Debug; | ||
550 | let x: Debug = Debug; | ||
551 | } | ||
552 | ", | ||
553 | ); | ||
554 | } | ||
555 | |||
556 | #[test] | ||
557 | fn replaces_all_affected_paths_mod() { | ||
558 | check_assist( | ||
559 | replace_qualified_name_with_use, | ||
560 | r" | ||
561 | mod m { | ||
562 | fn f() { | ||
563 | std::fmt::Debug<|>; | ||
564 | let x: std::fmt::Debug = std::fmt::Debug; | ||
565 | } | ||
566 | fn g() { | ||
567 | std::fmt::Debug; | ||
568 | } | ||
569 | } | ||
570 | |||
571 | fn f() { | ||
572 | std::fmt::Debug; | ||
573 | } | ||
574 | ", | ||
575 | r" | ||
576 | mod m { | ||
577 | use std::fmt::Debug; | ||
578 | |||
579 | fn f() { | ||
580 | Debug; | ||
581 | let x: Debug = Debug; | ||
582 | } | ||
583 | fn g() { | ||
584 | Debug; | ||
585 | } | ||
586 | } | ||
587 | |||
588 | fn f() { | ||
589 | std::fmt::Debug; | ||
590 | } | ||
591 | ", | ||
592 | ); | ||
593 | } | ||
594 | |||
595 | #[test] | ||
596 | fn does_not_replace_in_submodules() { | ||
597 | check_assist( | ||
598 | replace_qualified_name_with_use, | ||
599 | r" | ||
600 | fn main() { | ||
601 | std::fmt::Debug<|>; | ||
602 | } | ||
603 | |||
604 | mod sub { | ||
605 | fn f() { | ||
606 | std::fmt::Debug; | ||
607 | } | ||
608 | } | ||
609 | ", | ||
610 | r" | ||
611 | use std::fmt::Debug; | ||
612 | |||
613 | fn main() { | ||
614 | Debug; | ||
615 | } | ||
616 | |||
617 | mod sub { | ||
618 | fn f() { | ||
619 | std::fmt::Debug; | ||
620 | } | ||
621 | } | ||
622 | ", | ||
623 | ); | ||
624 | } | ||
625 | |||
626 | #[test] | ||
627 | fn does_not_replace_in_use() { | ||
628 | check_assist( | ||
629 | replace_qualified_name_with_use, | ||
630 | r" | ||
631 | use std::fmt::Display; | ||
632 | |||
633 | fn main() { | ||
634 | std::fmt<|>; | ||
635 | } | ||
636 | ", | ||
637 | r" | ||
638 | use std::fmt::{self, Display}; | ||
639 | |||
640 | fn main() { | ||
641 | fmt; | ||
642 | } | ||
643 | ", | ||
644 | ); | ||
645 | } | ||
466 | } | 646 | } |
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 cff7dfb81..e5a4bb23c 100644 --- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs +++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs | |||
@@ -11,7 +11,7 @@ use ra_syntax::{ | |||
11 | 11 | ||
12 | use crate::{ | 12 | use crate::{ |
13 | utils::{render_snippet, Cursor, TryEnum}, | 13 | utils::{render_snippet, Cursor, TryEnum}, |
14 | AssistContext, AssistId, Assists, | 14 | AssistContext, AssistId, AssistKind, Assists, |
15 | }; | 15 | }; |
16 | 16 | ||
17 | // Assist: replace_unwrap_with_match | 17 | // Assist: replace_unwrap_with_match |
@@ -46,37 +46,43 @@ pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext) | |||
46 | let ty = ctx.sema.type_of_expr(&caller)?; | 46 | let ty = ctx.sema.type_of_expr(&caller)?; |
47 | let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case(); | 47 | let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case(); |
48 | let target = method_call.syntax().text_range(); | 48 | let target = method_call.syntax().text_range(); |
49 | acc.add(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", target, |builder| { | 49 | acc.add( |
50 | let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); | 50 | AssistId("replace_unwrap_with_match", AssistKind::RefactorRewrite), |
51 | let it = make::bind_pat(make::name("a")).into(); | 51 | "Replace unwrap with match", |
52 | let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); | 52 | target, |
53 | |builder| { | ||
54 | let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); | ||
55 | let it = make::bind_pat(make::name("a")).into(); | ||
56 | let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); | ||
53 | 57 | ||
54 | let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); | 58 | let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); |
55 | let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); | 59 | let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); |
56 | 60 | ||
57 | let unreachable_call = make::expr_unreachable(); | 61 | let unreachable_call = make::expr_unreachable(); |
58 | let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); | 62 | let err_arm = |
63 | make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); | ||
59 | 64 | ||
60 | let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); | 65 | let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); |
61 | let match_expr = make::expr_match(caller.clone(), match_arm_list) | 66 | let match_expr = make::expr_match(caller.clone(), match_arm_list) |
62 | .indent(IndentLevel::from_node(method_call.syntax())); | 67 | .indent(IndentLevel::from_node(method_call.syntax())); |
63 | 68 | ||
64 | let range = method_call.syntax().text_range(); | 69 | let range = method_call.syntax().text_range(); |
65 | match ctx.config.snippet_cap { | 70 | match ctx.config.snippet_cap { |
66 | Some(cap) => { | 71 | Some(cap) => { |
67 | let err_arm = match_expr | 72 | let err_arm = match_expr |
68 | .syntax() | 73 | .syntax() |
69 | .descendants() | 74 | .descendants() |
70 | .filter_map(ast::MatchArm::cast) | 75 | .filter_map(ast::MatchArm::cast) |
71 | .last() | 76 | .last() |
72 | .unwrap(); | 77 | .unwrap(); |
73 | let snippet = | 78 | let snippet = |
74 | render_snippet(cap, match_expr.syntax(), Cursor::Before(err_arm.syntax())); | 79 | render_snippet(cap, match_expr.syntax(), Cursor::Before(err_arm.syntax())); |
75 | builder.replace_snippet(cap, range, snippet) | 80 | builder.replace_snippet(cap, range, snippet) |
81 | } | ||
82 | None => builder.replace(range, match_expr.to_string()), | ||
76 | } | 83 | } |
77 | None => builder.replace(range, match_expr.to_string()), | 84 | }, |
78 | } | 85 | ) |
79 | }) | ||
80 | } | 86 | } |
81 | 87 | ||
82 | #[cfg(test)] | 88 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs index c7a874480..4ca5c3ca1 100644 --- a/crates/ra_assists/src/handlers/split_import.rs +++ b/crates/ra_assists/src/handlers/split_import.rs | |||
@@ -2,7 +2,7 @@ use std::iter::successors; | |||
2 | 2 | ||
3 | use ra_syntax::{ast, AstNode, T}; | 3 | use ra_syntax::{ast, AstNode, T}; |
4 | 4 | ||
5 | use crate::{AssistContext, AssistId, Assists}; | 5 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
6 | 6 | ||
7 | // Assist: split_import | 7 | // Assist: split_import |
8 | // | 8 | // |
@@ -28,7 +28,7 @@ pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
28 | } | 28 | } |
29 | 29 | ||
30 | let target = colon_colon.text_range(); | 30 | let target = colon_colon.text_range(); |
31 | acc.add(AssistId("split_import"), "Split import", target, |edit| { | 31 | acc.add(AssistId("split_import", AssistKind::RefactorRewrite), "Split import", target, |edit| { |
32 | edit.replace_ast(use_tree, new_tree); | 32 | edit.replace_ast(use_tree, new_tree); |
33 | }) | 33 | }) |
34 | } | 34 | } |
@@ -66,4 +66,14 @@ mod tests { | |||
66 | fn issue4044() { | 66 | fn issue4044() { |
67 | check_assist_not_applicable(split_import, "use crate::<|>:::self;") | 67 | check_assist_not_applicable(split_import, "use crate::<|>:::self;") |
68 | } | 68 | } |
69 | |||
70 | #[test] | ||
71 | fn test_empty_use() { | ||
72 | check_assist_not_applicable( | ||
73 | split_import, | ||
74 | r" | ||
75 | use std::<|> | ||
76 | fn main() {}", | ||
77 | ); | ||
78 | } | ||
69 | } | 79 | } |
diff --git a/crates/ra_assists/src/handlers/unwrap_block.rs b/crates/ra_assists/src/handlers/unwrap_block.rs index 8440c7d0f..8b38695a9 100644 --- a/crates/ra_assists/src/handlers/unwrap_block.rs +++ b/crates/ra_assists/src/handlers/unwrap_block.rs | |||
@@ -1,10 +1,13 @@ | |||
1 | use ra_fmt::unwrap_trivial_block; | 1 | use ra_fmt::unwrap_trivial_block; |
2 | use ra_syntax::{ | 2 | use ra_syntax::{ |
3 | ast::{self, ElseBranch, Expr, LoopBodyOwner}, | 3 | ast::{ |
4 | match_ast, AstNode, TextRange, T, | 4 | self, |
5 | edit::{AstNodeEdit, IndentLevel}, | ||
6 | }, | ||
7 | AstNode, TextRange, T, | ||
5 | }; | 8 | }; |
6 | 9 | ||
7 | use crate::{AssistContext, AssistId, Assists}; | 10 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
8 | 11 | ||
9 | // Assist: unwrap_block | 12 | // Assist: unwrap_block |
10 | // | 13 | // |
@@ -24,94 +27,73 @@ use crate::{AssistContext, AssistId, Assists}; | |||
24 | // } | 27 | // } |
25 | // ``` | 28 | // ``` |
26 | pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 29 | pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
27 | let l_curly_token = ctx.find_token_at_offset(T!['{'])?; | 30 | let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite); |
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"; | 31 | let assist_label = "Unwrap block"; |
32 | 32 | ||
33 | let (expr, expr_to_unwrap) = match_ast! { | 33 | let l_curly_token = ctx.find_token_at_offset(T!['{'])?; |
34 | match parent { | 34 | let mut block = ast::BlockExpr::cast(l_curly_token.parent())?; |
35 | ast::ForExpr(for_expr) => { | 35 | let mut parent = block.syntax().parent()?; |
36 | let block_expr = for_expr.loop_body()?; | 36 | if ast::MatchArm::can_cast(parent.kind()) { |
37 | let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?; | 37 | parent = parent.ancestors().find(|it| ast::MatchExpr::can_cast(it.kind()))? |
38 | (ast::Expr::ForExpr(for_expr), expr_to_unwrap) | 38 | } |
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 | 39 | ||
90 | resp? | 40 | let parent = ast::Expr::cast(parent)?; |
91 | }, | 41 | |
92 | _ => return None, | 42 | match parent.clone() { |
43 | ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::LoopExpr(_) => (), | ||
44 | ast::Expr::MatchExpr(_) => block = block.dedent(IndentLevel(1)), | ||
45 | ast::Expr::IfExpr(if_expr) => { | ||
46 | let then_branch = if_expr.then_branch()?; | ||
47 | if then_branch == block { | ||
48 | if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) { | ||
49 | // For `else if` blocks | ||
50 | let ancestor_then_branch = ancestor.then_branch()?; | ||
51 | |||
52 | let target = then_branch.syntax().text_range(); | ||
53 | return acc.add(assist_id, assist_label, target, |edit| { | ||
54 | let range_to_del_else_if = TextRange::new( | ||
55 | ancestor_then_branch.syntax().text_range().end(), | ||
56 | l_curly_token.text_range().start(), | ||
57 | ); | ||
58 | let range_to_del_rest = TextRange::new( | ||
59 | then_branch.syntax().text_range().end(), | ||
60 | if_expr.syntax().text_range().end(), | ||
61 | ); | ||
62 | |||
63 | edit.delete(range_to_del_rest); | ||
64 | edit.delete(range_to_del_else_if); | ||
65 | edit.replace( | ||
66 | target, | ||
67 | update_expr_string(then_branch.to_string(), &[' ', '{']), | ||
68 | ); | ||
69 | }); | ||
70 | } | ||
71 | } else { | ||
72 | let target = block.syntax().text_range(); | ||
73 | return acc.add(assist_id, assist_label, target, |edit| { | ||
74 | let range_to_del = TextRange::new( | ||
75 | then_branch.syntax().text_range().end(), | ||
76 | l_curly_token.text_range().start(), | ||
77 | ); | ||
78 | |||
79 | edit.delete(range_to_del); | ||
80 | edit.replace(target, update_expr_string(block.to_string(), &[' ', '{'])); | ||
81 | }); | ||
82 | } | ||
93 | } | 83 | } |
84 | _ => return None, | ||
94 | }; | 85 | }; |
95 | 86 | ||
96 | let target = expr_to_unwrap.syntax().text_range(); | 87 | let unwrapped = unwrap_trivial_block(block); |
97 | acc.add(assist_id, assist_label, target, |edit| { | 88 | let target = unwrapped.syntax().text_range(); |
98 | edit.replace( | 89 | acc.add(assist_id, assist_label, target, |builder| { |
99 | expr.syntax().text_range(), | 90 | builder.replace( |
100 | update_expr_string(expr_to_unwrap.to_string(), &[' ', '{', '\n']), | 91 | parent.syntax().text_range(), |
92 | update_expr_string(unwrapped.to_string(), &[' ', '{', '\n']), | ||
101 | ); | 93 | ); |
102 | }) | 94 | }) |
103 | } | 95 | } |
104 | 96 | ||
105 | fn extract_expr(cursor_range: TextRange, block: ast::BlockExpr) -> Option<ast::Expr> { | ||
106 | let cursor_in_range = block.l_curly_token()?.text_range().contains_range(cursor_range); | ||
107 | |||
108 | if cursor_in_range { | ||
109 | Some(unwrap_trivial_block(block)) | ||
110 | } else { | ||
111 | None | ||
112 | } | ||
113 | } | ||
114 | |||
115 | fn update_expr_string(expr_str: String, trim_start_pat: &[char]) -> String { | 97 | fn update_expr_string(expr_str: String, trim_start_pat: &[char]) -> String { |
116 | let expr_string = expr_str.trim_start_matches(trim_start_pat); | 98 | let expr_string = expr_str.trim_start_matches(trim_start_pat); |
117 | let mut expr_string_lines: Vec<&str> = expr_string.lines().collect(); | 99 | let mut expr_string_lines: Vec<&str> = expr_string.lines().collect(); |
@@ -490,6 +472,30 @@ mod tests { | |||
490 | } | 472 | } |
491 | 473 | ||
492 | #[test] | 474 | #[test] |
475 | fn unwrap_match_arm() { | ||
476 | check_assist( | ||
477 | unwrap_block, | ||
478 | r#" | ||
479 | fn main() { | ||
480 | match rel_path { | ||
481 | Ok(rel_path) => {<|> | ||
482 | let rel_path = RelativePathBuf::from_path(rel_path).ok()?; | ||
483 | Some((*id, rel_path)) | ||
484 | } | ||
485 | Err(_) => None, | ||
486 | } | ||
487 | } | ||
488 | "#, | ||
489 | r#" | ||
490 | fn main() { | ||
491 | let rel_path = RelativePathBuf::from_path(rel_path).ok()?; | ||
492 | Some((*id, rel_path)) | ||
493 | } | ||
494 | "#, | ||
495 | ); | ||
496 | } | ||
497 | |||
498 | #[test] | ||
493 | fn simple_if_in_while_bad_cursor_position() { | 499 | fn simple_if_in_while_bad_cursor_position() { |
494 | check_assist_not_applicable( | 500 | check_assist_not_applicable( |
495 | unwrap_block, | 501 | unwrap_block, |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 3f8f7ffbf..465b90415 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -26,10 +26,40 @@ pub(crate) use crate::assist_context::{AssistContext, Assists}; | |||
26 | 26 | ||
27 | pub use assist_config::AssistConfig; | 27 | pub use assist_config::AssistConfig; |
28 | 28 | ||
29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
30 | pub enum AssistKind { | ||
31 | None, | ||
32 | QuickFix, | ||
33 | Generate, | ||
34 | Refactor, | ||
35 | RefactorExtract, | ||
36 | RefactorInline, | ||
37 | RefactorRewrite, | ||
38 | } | ||
39 | |||
40 | impl AssistKind { | ||
41 | pub fn contains(self, other: AssistKind) -> bool { | ||
42 | if self == other { | ||
43 | return true; | ||
44 | } | ||
45 | |||
46 | match self { | ||
47 | AssistKind::None | AssistKind::Generate => return true, | ||
48 | AssistKind::Refactor => match other { | ||
49 | AssistKind::RefactorExtract | ||
50 | | AssistKind::RefactorInline | ||
51 | | AssistKind::RefactorRewrite => return true, | ||
52 | _ => return false, | ||
53 | }, | ||
54 | _ => return false, | ||
55 | } | ||
56 | } | ||
57 | } | ||
58 | |||
29 | /// Unique identifier of the assist, should not be shown to the user | 59 | /// Unique identifier of the assist, should not be shown to the user |
30 | /// directly. | 60 | /// directly. |
31 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 61 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
32 | pub struct AssistId(pub &'static str); | 62 | pub struct AssistId(pub &'static str, pub AssistKind); |
33 | 63 | ||
34 | #[derive(Clone, Debug)] | 64 | #[derive(Clone, Debug)] |
35 | pub struct GroupLabel(pub String); | 65 | pub struct GroupLabel(pub String); |
@@ -102,27 +132,28 @@ mod handlers { | |||
102 | pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>; | 132 | pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>; |
103 | 133 | ||
104 | mod add_custom_impl; | 134 | mod add_custom_impl; |
105 | mod add_derive; | ||
106 | mod add_explicit_type; | 135 | mod add_explicit_type; |
107 | mod add_from_impl_for_enum; | ||
108 | mod add_function; | ||
109 | mod add_impl; | ||
110 | mod add_missing_impl_members; | 136 | mod add_missing_impl_members; |
111 | mod add_new; | ||
112 | mod add_turbo_fish; | 137 | mod add_turbo_fish; |
113 | mod apply_demorgan; | 138 | mod apply_demorgan; |
114 | mod auto_import; | 139 | mod auto_import; |
115 | mod change_lifetime_anon_to_named; | ||
116 | mod change_return_type_to_result; | 140 | mod change_return_type_to_result; |
117 | mod change_visibility; | 141 | mod change_visibility; |
118 | mod early_return; | 142 | mod early_return; |
143 | mod extract_struct_from_enum_variant; | ||
144 | mod extract_variable; | ||
119 | mod fill_match_arms; | 145 | mod fill_match_arms; |
120 | mod fix_visibility; | 146 | mod fix_visibility; |
121 | mod flip_binexpr; | 147 | mod flip_binexpr; |
122 | mod flip_comma; | 148 | mod flip_comma; |
123 | mod flip_trait_bound; | 149 | mod flip_trait_bound; |
150 | mod generate_derive; | ||
151 | mod generate_from_impl_for_enum; | ||
152 | mod generate_function; | ||
153 | mod generate_impl; | ||
154 | mod generate_new; | ||
124 | mod inline_local_variable; | 155 | mod inline_local_variable; |
125 | mod introduce_variable; | 156 | mod introduce_named_lifetime; |
126 | mod invert_if; | 157 | mod invert_if; |
127 | mod merge_imports; | 158 | mod merge_imports; |
128 | mod merge_match_arms; | 159 | mod merge_match_arms; |
@@ -143,26 +174,27 @@ mod handlers { | |||
143 | &[ | 174 | &[ |
144 | // These are alphabetic for the foolish consistency | 175 | // These are alphabetic for the foolish consistency |
145 | add_custom_impl::add_custom_impl, | 176 | add_custom_impl::add_custom_impl, |
146 | add_derive::add_derive, | ||
147 | add_explicit_type::add_explicit_type, | 177 | add_explicit_type::add_explicit_type, |
148 | add_from_impl_for_enum::add_from_impl_for_enum, | ||
149 | add_function::add_function, | ||
150 | add_impl::add_impl, | ||
151 | add_new::add_new, | ||
152 | add_turbo_fish::add_turbo_fish, | 178 | add_turbo_fish::add_turbo_fish, |
153 | apply_demorgan::apply_demorgan, | 179 | apply_demorgan::apply_demorgan, |
154 | auto_import::auto_import, | 180 | auto_import::auto_import, |
155 | change_lifetime_anon_to_named::change_lifetime_anon_to_named, | ||
156 | change_return_type_to_result::change_return_type_to_result, | 181 | change_return_type_to_result::change_return_type_to_result, |
157 | change_visibility::change_visibility, | 182 | change_visibility::change_visibility, |
158 | early_return::convert_to_guarded_return, | 183 | early_return::convert_to_guarded_return, |
184 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, | ||
185 | extract_variable::extract_variable, | ||
159 | fill_match_arms::fill_match_arms, | 186 | fill_match_arms::fill_match_arms, |
160 | fix_visibility::fix_visibility, | 187 | fix_visibility::fix_visibility, |
161 | flip_binexpr::flip_binexpr, | 188 | flip_binexpr::flip_binexpr, |
162 | flip_comma::flip_comma, | 189 | flip_comma::flip_comma, |
163 | flip_trait_bound::flip_trait_bound, | 190 | flip_trait_bound::flip_trait_bound, |
191 | generate_derive::generate_derive, | ||
192 | generate_from_impl_for_enum::generate_from_impl_for_enum, | ||
193 | generate_function::generate_function, | ||
194 | generate_impl::generate_impl, | ||
195 | generate_new::generate_new, | ||
164 | inline_local_variable::inline_local_variable, | 196 | inline_local_variable::inline_local_variable, |
165 | introduce_variable::introduce_variable, | 197 | introduce_named_lifetime::introduce_named_lifetime, |
166 | invert_if::invert_if, | 198 | invert_if::invert_if, |
167 | merge_imports::merge_imports, | 199 | merge_imports::merge_imports, |
168 | merge_match_arms::merge_match_arms, | 200 | merge_match_arms::merge_match_arms, |
diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs index 62dd3547f..18fcb9049 100644 --- a/crates/ra_assists/src/tests.rs +++ b/crates/ra_assists/src/tests.rs | |||
@@ -1,27 +1,21 @@ | |||
1 | mod generated; | 1 | mod generated; |
2 | 2 | ||
3 | use std::sync::Arc; | ||
4 | |||
5 | use hir::Semantics; | 3 | use hir::Semantics; |
6 | use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; | 4 | use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; |
7 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; | 5 | use ra_ide_db::RootDatabase; |
8 | use ra_syntax::TextRange; | 6 | use ra_syntax::TextRange; |
9 | use test_utils::{ | 7 | use test_utils::{assert_eq_text, extract_offset, extract_range}; |
10 | assert_eq_text, extract_offset, extract_range, extract_range_or_offset, RangeOrOffset, | ||
11 | }; | ||
12 | 8 | ||
13 | use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; | 9 | use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists}; |
10 | use stdx::trim_indent; | ||
14 | 11 | ||
15 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { | 12 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { |
16 | let (mut db, file_id) = RootDatabase::with_single_file(text); | 13 | 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 | } | 14 | } |
22 | 15 | ||
23 | pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) { | 16 | pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) { |
24 | check(assist, ra_fixture_before, ExpectedResult::After(ra_fixture_after)); | 17 | let ra_fixture_after = trim_indent(ra_fixture_after); |
18 | check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after)); | ||
25 | } | 19 | } |
26 | 20 | ||
27 | // FIXME: instead of having a separate function here, maybe use | 21 | // FIXME: instead of having a separate function here, maybe use |
@@ -36,8 +30,9 @@ pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) { | |||
36 | } | 30 | } |
37 | 31 | ||
38 | fn check_doc_test(assist_id: &str, before: &str, after: &str) { | 32 | fn check_doc_test(assist_id: &str, before: &str, after: &str) { |
39 | let (selection, before) = extract_range_or_offset(before); | 33 | let after = trim_indent(after); |
40 | let (db, file_id) = crate::tests::with_single_file(&before); | 34 | let (db, file_id, selection) = RootDatabase::with_range_or_offset(&before); |
35 | let before = db.file_text(file_id).to_string(); | ||
41 | let frange = FileRange { file_id, range: selection.into() }; | 36 | let frange = FileRange { file_id, range: selection.into() }; |
42 | 37 | ||
43 | let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange) | 38 | let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange) |
@@ -57,11 +52,11 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) { | |||
57 | 52 | ||
58 | let actual = { | 53 | let actual = { |
59 | let change = assist.source_change.source_file_edits.pop().unwrap(); | 54 | let change = assist.source_change.source_file_edits.pop().unwrap(); |
60 | let mut actual = before.clone(); | 55 | let mut actual = before; |
61 | change.edit.apply(&mut actual); | 56 | change.edit.apply(&mut actual); |
62 | actual | 57 | actual |
63 | }; | 58 | }; |
64 | assert_eq_text!(after, &actual); | 59 | assert_eq_text!(&after, &actual); |
65 | } | 60 | } |
66 | 61 | ||
67 | enum ExpectedResult<'a> { | 62 | enum ExpectedResult<'a> { |
@@ -71,20 +66,8 @@ enum ExpectedResult<'a> { | |||
71 | } | 66 | } |
72 | 67 | ||
73 | fn check(handler: Handler, before: &str, expected: ExpectedResult) { | 68 | fn check(handler: Handler, before: &str, expected: ExpectedResult) { |
74 | let (text_without_caret, file_with_caret_id, range_or_offset, db) = if before.contains("//-") { | 69 | let (db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before); |
75 | let (mut db, position) = RootDatabase::with_position(before); | 70 | let text_without_caret = db.file_text(file_with_caret_id).to_string(); |
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 | 71 | ||
89 | let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; | 72 | let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; |
90 | 73 | ||
@@ -151,3 +134,46 @@ fn assist_order_if_expr() { | |||
151 | assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); | 134 | 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"); | 135 | assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match"); |
153 | } | 136 | } |
137 | |||
138 | #[test] | ||
139 | fn assist_filter_works() { | ||
140 | let before = " | ||
141 | pub fn test_some_range(a: int) -> bool { | ||
142 | if let 2..6 = <|>5<|> { | ||
143 | true | ||
144 | } else { | ||
145 | false | ||
146 | } | ||
147 | }"; | ||
148 | let (range, before) = extract_range(before); | ||
149 | let (db, file_id) = with_single_file(&before); | ||
150 | let frange = FileRange { file_id, range }; | ||
151 | |||
152 | { | ||
153 | let mut cfg = AssistConfig::default(); | ||
154 | cfg.allowed = Some(vec![AssistKind::Refactor]); | ||
155 | |||
156 | let assists = Assist::resolved(&db, &cfg, frange); | ||
157 | let mut assists = assists.iter(); | ||
158 | |||
159 | assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); | ||
160 | assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match"); | ||
161 | } | ||
162 | |||
163 | { | ||
164 | let mut cfg = AssistConfig::default(); | ||
165 | cfg.allowed = Some(vec![AssistKind::RefactorExtract]); | ||
166 | let assists = Assist::resolved(&db, &cfg, frange); | ||
167 | assert_eq!(assists.len(), 1); | ||
168 | |||
169 | let mut assists = assists.iter(); | ||
170 | assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); | ||
171 | } | ||
172 | |||
173 | { | ||
174 | let mut cfg = AssistConfig::default(); | ||
175 | cfg.allowed = Some(vec![AssistKind::QuickFix]); | ||
176 | let assists = Assist::resolved(&db, &cfg, frange); | ||
177 | assert!(assists.is_empty(), "All asserts but quickfixes should be filtered out"); | ||
178 | } | ||
179 | } | ||
diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs index 73d43283d..eff7feded 100644 --- a/crates/ra_assists/src/tests/generated.rs +++ b/crates/ra_assists/src/tests/generated.rs | |||
@@ -22,26 +22,6 @@ impl Debug for S { | |||
22 | } | 22 | } |
23 | 23 | ||
24 | #[test] | 24 | #[test] |
25 | fn doctest_add_derive() { | ||
26 | check_doc_test( | ||
27 | "add_derive", | ||
28 | r#####" | ||
29 | struct Point { | ||
30 | x: u32, | ||
31 | y: u32,<|> | ||
32 | } | ||
33 | "#####, | ||
34 | r#####" | ||
35 | #[derive($0)] | ||
36 | struct Point { | ||
37 | x: u32, | ||
38 | y: u32, | ||
39 | } | ||
40 | "#####, | ||
41 | ) | ||
42 | } | ||
43 | |||
44 | #[test] | ||
45 | fn doctest_add_explicit_type() { | 25 | fn doctest_add_explicit_type() { |
46 | check_doc_test( | 26 | check_doc_test( |
47 | "add_explicit_type", | 27 | "add_explicit_type", |
@@ -59,52 +39,6 @@ fn main() { | |||
59 | } | 39 | } |
60 | 40 | ||
61 | #[test] | 41 | #[test] |
62 | fn doctest_add_from_impl_for_enum() { | ||
63 | check_doc_test( | ||
64 | "add_from_impl_for_enum", | ||
65 | r#####" | ||
66 | enum A { <|>One(u32) } | ||
67 | "#####, | ||
68 | r#####" | ||
69 | enum A { One(u32) } | ||
70 | |||
71 | impl From<u32> for A { | ||
72 | fn from(v: u32) -> Self { | ||
73 | A::One(v) | ||
74 | } | ||
75 | } | ||
76 | "#####, | ||
77 | ) | ||
78 | } | ||
79 | |||
80 | #[test] | ||
81 | fn doctest_add_function() { | ||
82 | check_doc_test( | ||
83 | "add_function", | ||
84 | r#####" | ||
85 | struct Baz; | ||
86 | fn baz() -> Baz { Baz } | ||
87 | fn foo() { | ||
88 | bar<|>("", baz()); | ||
89 | } | ||
90 | |||
91 | "#####, | ||
92 | r#####" | ||
93 | struct Baz; | ||
94 | fn baz() -> Baz { Baz } | ||
95 | fn foo() { | ||
96 | bar("", baz()); | ||
97 | } | ||
98 | |||
99 | fn bar(arg: &str, baz: Baz) { | ||
100 | ${0:todo!()} | ||
101 | } | ||
102 | |||
103 | "#####, | ||
104 | ) | ||
105 | } | ||
106 | |||
107 | #[test] | ||
108 | fn doctest_add_hash() { | 42 | fn doctest_add_hash() { |
109 | check_doc_test( | 43 | check_doc_test( |
110 | "add_hash", | 44 | "add_hash", |
@@ -122,27 +56,6 @@ fn main() { | |||
122 | } | 56 | } |
123 | 57 | ||
124 | #[test] | 58 | #[test] |
125 | fn doctest_add_impl() { | ||
126 | check_doc_test( | ||
127 | "add_impl", | ||
128 | r#####" | ||
129 | struct Ctx<T: Clone> { | ||
130 | data: T,<|> | ||
131 | } | ||
132 | "#####, | ||
133 | r#####" | ||
134 | struct Ctx<T: Clone> { | ||
135 | data: T, | ||
136 | } | ||
137 | |||
138 | impl<T: Clone> Ctx<T> { | ||
139 | $0 | ||
140 | } | ||
141 | "#####, | ||
142 | ) | ||
143 | } | ||
144 | |||
145 | #[test] | ||
146 | fn doctest_add_impl_default_members() { | 59 | fn doctest_add_impl_default_members() { |
147 | check_doc_test( | 60 | check_doc_test( |
148 | "add_impl_default_members", | 61 | "add_impl_default_members", |
@@ -209,28 +122,6 @@ impl Trait<u32> for () { | |||
209 | } | 122 | } |
210 | 123 | ||
211 | #[test] | 124 | #[test] |
212 | fn doctest_add_new() { | ||
213 | check_doc_test( | ||
214 | "add_new", | ||
215 | r#####" | ||
216 | struct Ctx<T: Clone> { | ||
217 | data: T,<|> | ||
218 | } | ||
219 | "#####, | ||
220 | r#####" | ||
221 | struct Ctx<T: Clone> { | ||
222 | data: T, | ||
223 | } | ||
224 | |||
225 | impl<T: Clone> Ctx<T> { | ||
226 | fn $0new(data: T) -> Self { Self { data } } | ||
227 | } | ||
228 | |||
229 | "#####, | ||
230 | ) | ||
231 | } | ||
232 | |||
233 | #[test] | ||
234 | fn doctest_add_turbo_fish() { | 125 | fn doctest_add_turbo_fish() { |
235 | check_doc_test( | 126 | check_doc_test( |
236 | "add_turbo_fish", | 127 | "add_turbo_fish", |
@@ -288,31 +179,6 @@ pub mod std { pub mod collections { pub struct HashMap { } } } | |||
288 | } | 179 | } |
289 | 180 | ||
290 | #[test] | 181 | #[test] |
291 | fn doctest_change_lifetime_anon_to_named() { | ||
292 | check_doc_test( | ||
293 | "change_lifetime_anon_to_named", | ||
294 | r#####" | ||
295 | impl Cursor<'_<|>> { | ||
296 | fn node(self) -> &SyntaxNode { | ||
297 | match self { | ||
298 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
299 | } | ||
300 | } | ||
301 | } | ||
302 | "#####, | ||
303 | r#####" | ||
304 | impl<'a> Cursor<'a> { | ||
305 | fn node(self) -> &SyntaxNode { | ||
306 | match self { | ||
307 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
308 | } | ||
309 | } | ||
310 | } | ||
311 | "#####, | ||
312 | ) | ||
313 | } | ||
314 | |||
315 | #[test] | ||
316 | fn doctest_change_return_type_to_result() { | 182 | fn doctest_change_return_type_to_result() { |
317 | check_doc_test( | 183 | check_doc_test( |
318 | "change_return_type_to_result", | 184 | "change_return_type_to_result", |
@@ -363,6 +229,39 @@ fn main() { | |||
363 | } | 229 | } |
364 | 230 | ||
365 | #[test] | 231 | #[test] |
232 | fn doctest_extract_struct_from_enum_variant() { | ||
233 | check_doc_test( | ||
234 | "extract_struct_from_enum_variant", | ||
235 | r#####" | ||
236 | enum A { <|>One(u32, u32) } | ||
237 | "#####, | ||
238 | r#####" | ||
239 | struct One(pub u32, pub u32); | ||
240 | |||
241 | enum A { One(One) } | ||
242 | "#####, | ||
243 | ) | ||
244 | } | ||
245 | |||
246 | #[test] | ||
247 | fn doctest_extract_variable() { | ||
248 | check_doc_test( | ||
249 | "extract_variable", | ||
250 | r#####" | ||
251 | fn main() { | ||
252 | <|>(1 + 2)<|> * 4; | ||
253 | } | ||
254 | "#####, | ||
255 | r#####" | ||
256 | fn main() { | ||
257 | let $0var_name = (1 + 2); | ||
258 | var_name * 4; | ||
259 | } | ||
260 | "#####, | ||
261 | ) | ||
262 | } | ||
263 | |||
264 | #[test] | ||
366 | fn doctest_fill_match_arms() { | 265 | fn doctest_fill_match_arms() { |
367 | check_doc_test( | 266 | check_doc_test( |
368 | "fill_match_arms", | 267 | "fill_match_arms", |
@@ -459,6 +358,115 @@ fn foo<T: Copy + Clone>() { } | |||
459 | } | 358 | } |
460 | 359 | ||
461 | #[test] | 360 | #[test] |
361 | fn doctest_generate_derive() { | ||
362 | check_doc_test( | ||
363 | "generate_derive", | ||
364 | r#####" | ||
365 | struct Point { | ||
366 | x: u32, | ||
367 | y: u32,<|> | ||
368 | } | ||
369 | "#####, | ||
370 | r#####" | ||
371 | #[derive($0)] | ||
372 | struct Point { | ||
373 | x: u32, | ||
374 | y: u32, | ||
375 | } | ||
376 | "#####, | ||
377 | ) | ||
378 | } | ||
379 | |||
380 | #[test] | ||
381 | fn doctest_generate_from_impl_for_enum() { | ||
382 | check_doc_test( | ||
383 | "generate_from_impl_for_enum", | ||
384 | r#####" | ||
385 | enum A { <|>One(u32) } | ||
386 | "#####, | ||
387 | r#####" | ||
388 | enum A { One(u32) } | ||
389 | |||
390 | impl From<u32> for A { | ||
391 | fn from(v: u32) -> Self { | ||
392 | A::One(v) | ||
393 | } | ||
394 | } | ||
395 | "#####, | ||
396 | ) | ||
397 | } | ||
398 | |||
399 | #[test] | ||
400 | fn doctest_generate_function() { | ||
401 | check_doc_test( | ||
402 | "generate_function", | ||
403 | r#####" | ||
404 | struct Baz; | ||
405 | fn baz() -> Baz { Baz } | ||
406 | fn foo() { | ||
407 | bar<|>("", baz()); | ||
408 | } | ||
409 | |||
410 | "#####, | ||
411 | r#####" | ||
412 | struct Baz; | ||
413 | fn baz() -> Baz { Baz } | ||
414 | fn foo() { | ||
415 | bar("", baz()); | ||
416 | } | ||
417 | |||
418 | fn bar(arg: &str, baz: Baz) { | ||
419 | ${0:todo!()} | ||
420 | } | ||
421 | |||
422 | "#####, | ||
423 | ) | ||
424 | } | ||
425 | |||
426 | #[test] | ||
427 | fn doctest_generate_impl() { | ||
428 | check_doc_test( | ||
429 | "generate_impl", | ||
430 | r#####" | ||
431 | struct Ctx<T: Clone> { | ||
432 | data: T,<|> | ||
433 | } | ||
434 | "#####, | ||
435 | r#####" | ||
436 | struct Ctx<T: Clone> { | ||
437 | data: T, | ||
438 | } | ||
439 | |||
440 | impl<T: Clone> Ctx<T> { | ||
441 | $0 | ||
442 | } | ||
443 | "#####, | ||
444 | ) | ||
445 | } | ||
446 | |||
447 | #[test] | ||
448 | fn doctest_generate_new() { | ||
449 | check_doc_test( | ||
450 | "generate_new", | ||
451 | r#####" | ||
452 | struct Ctx<T: Clone> { | ||
453 | data: T,<|> | ||
454 | } | ||
455 | "#####, | ||
456 | r#####" | ||
457 | struct Ctx<T: Clone> { | ||
458 | data: T, | ||
459 | } | ||
460 | |||
461 | impl<T: Clone> Ctx<T> { | ||
462 | fn $0new(data: T) -> Self { Self { data } } | ||
463 | } | ||
464 | |||
465 | "#####, | ||
466 | ) | ||
467 | } | ||
468 | |||
469 | #[test] | ||
462 | fn doctest_inline_local_variable() { | 470 | fn doctest_inline_local_variable() { |
463 | check_doc_test( | 471 | check_doc_test( |
464 | "inline_local_variable", | 472 | "inline_local_variable", |
@@ -477,18 +485,25 @@ fn main() { | |||
477 | } | 485 | } |
478 | 486 | ||
479 | #[test] | 487 | #[test] |
480 | fn doctest_introduce_variable() { | 488 | fn doctest_introduce_named_lifetime() { |
481 | check_doc_test( | 489 | check_doc_test( |
482 | "introduce_variable", | 490 | "introduce_named_lifetime", |
483 | r#####" | 491 | r#####" |
484 | fn main() { | 492 | impl Cursor<'_<|>> { |
485 | <|>(1 + 2)<|> * 4; | 493 | fn node(self) -> &SyntaxNode { |
494 | match self { | ||
495 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
496 | } | ||
497 | } | ||
486 | } | 498 | } |
487 | "#####, | 499 | "#####, |
488 | r#####" | 500 | r#####" |
489 | fn main() { | 501 | impl<'a> Cursor<'a> { |
490 | let $0var_name = (1 + 2); | 502 | fn node(self) -> &SyntaxNode { |
491 | var_name * 4; | 503 | match self { |
504 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
505 | } | ||
506 | } | ||
492 | } | 507 | } |
493 | "#####, | 508 | "#####, |
494 | ) | 509 | ) |
diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs index 0038a9764..02de902d6 100644 --- a/crates/ra_assists/src/utils.rs +++ b/crates/ra_assists/src/utils.rs | |||
@@ -7,13 +7,15 @@ use hir::{Adt, Crate, Enum, ScopeDef, Semantics, Trait, Type}; | |||
7 | use ra_ide_db::RootDatabase; | 7 | use ra_ide_db::RootDatabase; |
8 | use ra_syntax::{ | 8 | use ra_syntax::{ |
9 | ast::{self, make, NameOwner}, | 9 | ast::{self, make, NameOwner}, |
10 | AstNode, SyntaxNode, T, | 10 | AstNode, |
11 | SyntaxKind::*, | ||
12 | SyntaxNode, TextSize, T, | ||
11 | }; | 13 | }; |
12 | use rustc_hash::FxHashSet; | 14 | use rustc_hash::FxHashSet; |
13 | 15 | ||
14 | use crate::assist_config::SnippetCap; | 16 | use crate::assist_config::SnippetCap; |
15 | 17 | ||
16 | pub(crate) use insert_use::insert_use_statement; | 18 | pub(crate) use insert_use::{find_insert_use_container, insert_use_statement}; |
17 | 19 | ||
18 | #[derive(Clone, Copy, Debug)] | 20 | #[derive(Clone, Copy, Debug)] |
19 | pub(crate) enum Cursor<'a> { | 21 | pub(crate) enum Cursor<'a> { |
@@ -120,6 +122,13 @@ pub(crate) fn resolve_target_trait( | |||
120 | } | 122 | } |
121 | } | 123 | } |
122 | 124 | ||
125 | pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize { | ||
126 | node.children_with_tokens() | ||
127 | .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) | ||
128 | .map(|it| it.text_range().start()) | ||
129 | .unwrap_or_else(|| node.text_range().start()) | ||
130 | } | ||
131 | |||
123 | pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr { | 132 | pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr { |
124 | if let Some(expr) = invert_special_case(&expr) { | 133 | if let Some(expr) = invert_special_case(&expr) { |
125 | return expr; | 134 | return expr; |
@@ -198,8 +207,7 @@ pub(crate) struct FamousDefs<'a, 'b>(pub(crate) &'a Semantics<'b, RootDatabase>, | |||
198 | #[allow(non_snake_case)] | 207 | #[allow(non_snake_case)] |
199 | impl FamousDefs<'_, '_> { | 208 | impl FamousDefs<'_, '_> { |
200 | #[cfg(test)] | 209 | #[cfg(test)] |
201 | pub(crate) const FIXTURE: &'static str = r#" | 210 | pub(crate) const FIXTURE: &'static str = r#"//- /libcore.rs crate:core |
202 | //- /libcore.rs crate:core | ||
203 | pub mod convert { | 211 | pub mod convert { |
204 | pub trait From<T> { | 212 | pub trait From<T> { |
205 | fn from(T) -> Self; | 213 | fn from(T) -> Self; |
diff --git a/crates/ra_assists/src/utils/insert_use.rs b/crates/ra_assists/src/utils/insert_use.rs index 0ee43482f..8c4f33e59 100644 --- a/crates/ra_assists/src/utils/insert_use.rs +++ b/crates/ra_assists/src/utils/insert_use.rs | |||
@@ -12,6 +12,20 @@ use ra_syntax::{ | |||
12 | use ra_text_edit::TextEditBuilder; | 12 | use ra_text_edit::TextEditBuilder; |
13 | 13 | ||
14 | use crate::assist_context::AssistContext; | 14 | use crate::assist_context::AssistContext; |
15 | use either::Either; | ||
16 | |||
17 | /// Determines the containing syntax node in which to insert a `use` statement affecting `position`. | ||
18 | pub(crate) fn find_insert_use_container( | ||
19 | position: &SyntaxNode, | ||
20 | ctx: &AssistContext, | ||
21 | ) -> Option<Either<ast::ItemList, ast::SourceFile>> { | ||
22 | ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| { | ||
23 | if let Some(module) = ast::Module::cast(n.clone()) { | ||
24 | return module.item_list().map(|it| Either::Left(it)); | ||
25 | } | ||
26 | Some(Either::Right(ast::SourceFile::cast(n)?)) | ||
27 | }) | ||
28 | } | ||
15 | 29 | ||
16 | /// Creates and inserts a use statement for the given path to import. | 30 | /// Creates and inserts a use statement for the given path to import. |
17 | /// The use statement is inserted in the scope most appropriate to the | 31 | /// The use statement is inserted in the scope most appropriate to the |
@@ -24,15 +38,11 @@ pub(crate) fn insert_use_statement( | |||
24 | builder: &mut TextEditBuilder, | 38 | builder: &mut TextEditBuilder, |
25 | ) { | 39 | ) { |
26 | let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>(); | 40 | let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>(); |
27 | let container = ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| { | 41 | let container = find_insert_use_container(position, ctx); |
28 | if let Some(module) = ast::Module::cast(n.clone()) { | ||
29 | return module.item_list().map(|it| it.syntax().clone()); | ||
30 | } | ||
31 | ast::SourceFile::cast(n).map(|it| it.syntax().clone()) | ||
32 | }); | ||
33 | 42 | ||
34 | if let Some(container) = container { | 43 | if let Some(container) = container { |
35 | let action = best_action_for_target(container, position.clone(), &target); | 44 | let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone()); |
45 | let action = best_action_for_target(syntax, position.clone(), &target); | ||
36 | make_assist(&action, &target, builder); | 46 | make_assist(&action, &target, builder); |
37 | } | 47 | } |
38 | } | 48 | } |