diff options
Diffstat (limited to 'crates/ra_assists')
-rw-r--r-- | crates/ra_assists/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_assists/src/assist_config.rs | 5 | ||||
-rw-r--r-- | crates/ra_assists/src/assist_context.rs | 48 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/add_missing_impl_members.rs | 25 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/add_turbo_fish.rs | 29 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/change_visibility.rs | 16 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/fill_match_arms.rs | 210 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/raw_string.rs | 158 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs | 2 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 19 | ||||
-rw-r--r-- | crates/ra_assists/src/tests.rs | 45 |
11 files changed, 331 insertions, 227 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 3640bb4d2..3407df856 100644 --- a/crates/ra_assists/src/assist_context.rs +++ b/crates/ra_assists/src/assist_context.rs | |||
@@ -19,7 +19,7 @@ use ra_text_edit::TextEditBuilder; | |||
19 | 19 | ||
20 | use crate::{ | 20 | use crate::{ |
21 | assist_config::{AssistConfig, SnippetCap}, | 21 | assist_config::{AssistConfig, SnippetCap}, |
22 | Assist, AssistId, GroupLabel, ResolvedAssist, | 22 | Assist, AssistId, AssistKind, GroupLabel, ResolvedAssist, |
23 | }; | 23 | }; |
24 | 24 | ||
25 | /// `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. |
@@ -103,14 +103,26 @@ pub(crate) struct Assists { | |||
103 | resolve: bool, | 103 | resolve: bool, |
104 | file: FileId, | 104 | file: FileId, |
105 | buf: Vec<(Assist, Option<SourceChange>)>, | 105 | buf: Vec<(Assist, Option<SourceChange>)>, |
106 | allowed: Option<Vec<AssistKind>>, | ||
106 | } | 107 | } |
107 | 108 | ||
108 | impl Assists { | 109 | impl Assists { |
109 | pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { | 110 | pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { |
110 | 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 | } | ||
111 | } | 117 | } |
118 | |||
112 | pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { | 119 | pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { |
113 | 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 | } | ||
114 | } | 126 | } |
115 | 127 | ||
116 | pub(crate) fn finish_unresolved(self) -> Vec<Assist> { | 128 | pub(crate) fn finish_unresolved(self) -> Vec<Assist> { |
@@ -139,9 +151,13 @@ impl Assists { | |||
139 | target: TextRange, | 151 | target: TextRange, |
140 | f: impl FnOnce(&mut AssistBuilder), | 152 | f: impl FnOnce(&mut AssistBuilder), |
141 | ) -> Option<()> { | 153 | ) -> Option<()> { |
154 | if !self.is_allowed(&id) { | ||
155 | return None; | ||
156 | } | ||
142 | let label = Assist::new(id, label.into(), None, target); | 157 | let label = Assist::new(id, label.into(), None, target); |
143 | self.add_impl(label, f) | 158 | self.add_impl(label, f) |
144 | } | 159 | } |
160 | |||
145 | pub(crate) fn add_group( | 161 | pub(crate) fn add_group( |
146 | &mut self, | 162 | &mut self, |
147 | group: &GroupLabel, | 163 | group: &GroupLabel, |
@@ -150,9 +166,14 @@ impl Assists { | |||
150 | target: TextRange, | 166 | target: TextRange, |
151 | f: impl FnOnce(&mut AssistBuilder), | 167 | f: impl FnOnce(&mut AssistBuilder), |
152 | ) -> Option<()> { | 168 | ) -> Option<()> { |
169 | if !self.is_allowed(&id) { | ||
170 | return None; | ||
171 | } | ||
172 | |||
153 | let label = Assist::new(id, label.into(), Some(group.clone()), target); | 173 | let label = Assist::new(id, label.into(), Some(group.clone()), target); |
154 | self.add_impl(label, f) | 174 | self.add_impl(label, f) |
155 | } | 175 | } |
176 | |||
156 | 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<()> { |
157 | let source_change = if self.resolve { | 178 | let source_change = if self.resolve { |
158 | let mut builder = AssistBuilder::new(self.file); | 179 | let mut builder = AssistBuilder::new(self.file); |
@@ -170,13 +191,20 @@ impl Assists { | |||
170 | self.buf.sort_by_key(|(label, _edit)| label.target.len()); | 191 | self.buf.sort_by_key(|(label, _edit)| label.target.len()); |
171 | self.buf | 192 | self.buf |
172 | } | 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 | } | ||
173 | } | 201 | } |
174 | 202 | ||
175 | pub(crate) struct AssistBuilder { | 203 | pub(crate) struct AssistBuilder { |
176 | edit: TextEditBuilder, | 204 | edit: TextEditBuilder, |
177 | file_id: FileId, | 205 | file_id: FileId, |
178 | is_snippet: bool, | 206 | is_snippet: bool, |
179 | edits: Vec<SourceFileEdit>, | 207 | change: SourceChange, |
180 | } | 208 | } |
181 | 209 | ||
182 | impl AssistBuilder { | 210 | impl AssistBuilder { |
@@ -185,7 +213,7 @@ impl AssistBuilder { | |||
185 | edit: TextEditBuilder::default(), | 213 | edit: TextEditBuilder::default(), |
186 | file_id, | 214 | file_id, |
187 | is_snippet: false, | 215 | is_snippet: false, |
188 | edits: Vec::new(), | 216 | change: SourceChange::default(), |
189 | } | 217 | } |
190 | } | 218 | } |
191 | 219 | ||
@@ -197,8 +225,8 @@ impl AssistBuilder { | |||
197 | let edit = mem::take(&mut self.edit).finish(); | 225 | let edit = mem::take(&mut self.edit).finish(); |
198 | if !edit.is_empty() { | 226 | if !edit.is_empty() { |
199 | let new_edit = SourceFileEdit { file_id: self.file_id, edit }; | 227 | let new_edit = SourceFileEdit { file_id: self.file_id, edit }; |
200 | assert!(!self.edits.iter().any(|it| it.file_id == new_edit.file_id)); | 228 | assert!(!self.change.source_file_edits.iter().any(|it| it.file_id == new_edit.file_id)); |
201 | self.edits.push(new_edit); | 229 | self.change.source_file_edits.push(new_edit); |
202 | } | 230 | } |
203 | } | 231 | } |
204 | 232 | ||
@@ -265,10 +293,10 @@ impl AssistBuilder { | |||
265 | 293 | ||
266 | fn finish(mut self) -> SourceChange { | 294 | fn finish(mut self) -> SourceChange { |
267 | self.commit(); | 295 | self.commit(); |
268 | let mut res: SourceChange = mem::take(&mut self.edits).into(); | 296 | let mut change = mem::take(&mut self.change); |
269 | if self.is_snippet { | 297 | if self.is_snippet { |
270 | res.is_snippet = true; | 298 | change.is_snippet = true; |
271 | } | 299 | } |
272 | res | 300 | change |
273 | } | 301 | } |
274 | } | 302 | } |
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 d6aaf53f1..f185e61e5 100644 --- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs | |||
@@ -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 f7e1a7b05..0c565e89a 100644 --- a/crates/ra_assists/src/handlers/add_turbo_fish.rs +++ b/crates/ra_assists/src/handlers/add_turbo_fish.rs | |||
@@ -25,7 +25,14 @@ use crate::{ | |||
25 | // } | 25 | // } |
26 | // ``` | 26 | // ``` |
27 | pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 27 | pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
28 | let ident = ctx.find_token_at_offset(SyntaxKind::IDENT)?; | 28 | let ident = ctx.find_token_at_offset(SyntaxKind::IDENT).or_else(|| { |
29 | let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?; | ||
30 | if arg_list.args().count() > 0 { | ||
31 | return None; | ||
32 | } | ||
33 | mark::hit!(add_turbo_fish_after_call); | ||
34 | arg_list.l_paren_token()?.prev_token().filter(|it| it.kind() == SyntaxKind::IDENT) | ||
35 | })?; | ||
29 | let next_token = ident.next_token()?; | 36 | let next_token = ident.next_token()?; |
30 | if next_token.kind() == T![::] { | 37 | if next_token.kind() == T![::] { |
31 | mark::hit!(add_turbo_fish_one_fish_is_enough); | 38 | mark::hit!(add_turbo_fish_one_fish_is_enough); |
@@ -83,6 +90,26 @@ fn main() { | |||
83 | } | 90 | } |
84 | 91 | ||
85 | #[test] | 92 | #[test] |
93 | fn add_turbo_fish_after_call() { | ||
94 | mark::check!(add_turbo_fish_after_call); | ||
95 | check_assist( | ||
96 | add_turbo_fish, | ||
97 | r#" | ||
98 | fn make<T>() -> T {} | ||
99 | fn main() { | ||
100 | make()<|>; | ||
101 | } | ||
102 | "#, | ||
103 | r#" | ||
104 | fn make<T>() -> T {} | ||
105 | fn main() { | ||
106 | make::<${0:_}>(); | ||
107 | } | ||
108 | "#, | ||
109 | ); | ||
110 | } | ||
111 | |||
112 | #[test] | ||
86 | fn add_turbo_fish_method() { | 113 | fn add_turbo_fish_method() { |
87 | check_assist( | 114 | check_assist( |
88 | add_turbo_fish, | 115 | add_turbo_fish, |
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs index 703ee2143..4343b423c 100644 --- a/crates/ra_assists/src/handlers/change_visibility.rs +++ b/crates/ra_assists/src/handlers/change_visibility.rs | |||
@@ -1,7 +1,9 @@ | |||
1 | use ra_syntax::{ | 1 | use ra_syntax::{ |
2 | ast::{self, NameOwner, VisibilityOwner}, | 2 | ast::{self, NameOwner, VisibilityOwner}, |
3 | AstNode, | 3 | AstNode, |
4 | SyntaxKind::{CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY}, | 4 | SyntaxKind::{ |
5 | CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STATIC_DEF, STRUCT_DEF, TRAIT_DEF, VISIBILITY, | ||
6 | }, | ||
5 | T, | 7 | T, |
6 | }; | 8 | }; |
7 | use test_utils::mark; | 9 | use test_utils::mark; |
@@ -28,12 +30,15 @@ pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Optio | |||
28 | 30 | ||
29 | fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 31 | fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
30 | let item_keyword = ctx.token_at_offset().find(|leaf| { | 32 | let item_keyword = ctx.token_at_offset().find(|leaf| { |
31 | matches!(leaf.kind(), T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait]) | 33 | matches!( |
34 | leaf.kind(), | ||
35 | T![const] | T![static] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] | ||
36 | ) | ||
32 | }); | 37 | }); |
33 | 38 | ||
34 | let (offset, target) = if let Some(keyword) = item_keyword { | 39 | let (offset, target) = if let Some(keyword) = item_keyword { |
35 | let parent = keyword.parent(); | 40 | let parent = keyword.parent(); |
36 | 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]; |
37 | // Parent is not a definition, can't add visibility | 42 | // Parent is not a definition, can't add visibility |
38 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { | 43 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { |
39 | return None; | 44 | return None; |
@@ -152,6 +157,11 @@ mod tests { | |||
152 | } | 157 | } |
153 | 158 | ||
154 | #[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] | ||
155 | fn change_visibility_handles_comment_attrs() { | 165 | fn change_visibility_handles_comment_attrs() { |
156 | check_assist( | 166 | check_assist( |
157 | change_visibility, | 167 | change_visibility, |
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs index 511355e07..708e1bc6c 100644 --- a/crates/ra_assists/src/handlers/fill_match_arms.rs +++ b/crates/ra_assists/src/handlers/fill_match_arms.rs | |||
@@ -115,11 +115,19 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
115 | let old_range = match_arm_list.syntax().text_range(); | 115 | let old_range = match_arm_list.syntax().text_range(); |
116 | match (first_new_arm, ctx.config.snippet_cap) { | 116 | match (first_new_arm, ctx.config.snippet_cap) { |
117 | (Some(first_new_arm), Some(cap)) => { | 117 | (Some(first_new_arm), Some(cap)) => { |
118 | let snippet = render_snippet( | 118 | let extend_lifetime; |
119 | cap, | 119 | let cursor = match first_new_arm |
120 | new_arm_list.syntax(), | 120 | .syntax() |
121 | Cursor::Before(first_new_arm.syntax()), | 121 | .descendants() |
122 | ); | 122 | .find_map(ast::PlaceholderPat::cast) |
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); | ||
123 | builder.replace_snippet(cap, old_range, snippet); | 131 | builder.replace_snippet(cap, old_range, snippet); |
124 | } | 132 | } |
125 | _ => builder.replace(old_range, new_arm_list.to_string()), | 133 | _ => builder.replace(old_range, new_arm_list.to_string()), |
@@ -291,30 +299,22 @@ mod tests { | |||
291 | check_assist( | 299 | check_assist( |
292 | fill_match_arms, | 300 | fill_match_arms, |
293 | r#" | 301 | r#" |
294 | enum A { | 302 | enum A { As, Bs, Cs(Option<i32>) } |
295 | As, | 303 | fn main() { |
296 | Bs, | 304 | match A::As<|> { |
297 | Cs(Option<i32>), | 305 | A::Cs(_) | A::Bs => {} |
298 | } | 306 | } |
299 | fn main() { | 307 | } |
300 | match A::As<|> { | 308 | "#, |
301 | A::Cs(_) | A::Bs => {} | ||
302 | } | ||
303 | } | ||
304 | "#, | ||
305 | r#" | 309 | r#" |
306 | enum A { | 310 | enum A { As, Bs, Cs(Option<i32>) } |
307 | As, | 311 | fn main() { |
308 | Bs, | 312 | match A::As { |
309 | Cs(Option<i32>), | 313 | A::Cs(_) | A::Bs => {} |
310 | } | 314 | $0A::As => {} |
311 | fn main() { | 315 | } |
312 | match A::As { | 316 | } |
313 | A::Cs(_) | A::Bs => {} | 317 | "#, |
314 | $0A::As => {} | ||
315 | } | ||
316 | } | ||
317 | "#, | ||
318 | ); | 318 | ); |
319 | } | 319 | } |
320 | 320 | ||
@@ -323,47 +323,29 @@ mod tests { | |||
323 | check_assist( | 323 | check_assist( |
324 | fill_match_arms, | 324 | fill_match_arms, |
325 | r#" | 325 | r#" |
326 | enum A { | 326 | enum A { As, Bs, Cs, Ds(String), Es(B) } |
327 | As, | 327 | enum B { Xs, Ys } |
328 | Bs, | 328 | fn main() { |
329 | Cs, | 329 | match A::As<|> { |
330 | Ds(String), | 330 | A::Bs if 0 < 1 => {} |
331 | Es(B), | 331 | A::Ds(_value) => { let x = 1; } |
332 | } | 332 | A::Es(B::Xs) => (), |
333 | enum B { | 333 | } |
334 | Xs, | 334 | } |
335 | Ys, | 335 | "#, |
336 | } | ||
337 | fn main() { | ||
338 | match A::As<|> { | ||
339 | A::Bs if 0 < 1 => {} | ||
340 | A::Ds(_value) => { let x = 1; } | ||
341 | A::Es(B::Xs) => (), | ||
342 | } | ||
343 | } | ||
344 | "#, | ||
345 | r#" | 336 | r#" |
346 | enum A { | 337 | enum A { As, Bs, Cs, Ds(String), Es(B) } |
347 | As, | 338 | enum B { Xs, Ys } |
348 | Bs, | 339 | fn main() { |
349 | Cs, | 340 | match A::As { |
350 | Ds(String), | 341 | A::Bs if 0 < 1 => {} |
351 | Es(B), | 342 | A::Ds(_value) => { let x = 1; } |
352 | } | 343 | A::Es(B::Xs) => (), |
353 | enum B { | 344 | $0A::As => {} |
354 | Xs, | 345 | A::Cs => {} |
355 | Ys, | 346 | } |
356 | } | 347 | } |
357 | fn main() { | 348 | "#, |
358 | match A::As { | ||
359 | A::Bs if 0 < 1 => {} | ||
360 | A::Ds(_value) => { let x = 1; } | ||
361 | A::Es(B::Xs) => (), | ||
362 | $0A::As => {} | ||
363 | A::Cs => {} | ||
364 | } | ||
365 | } | ||
366 | "#, | ||
367 | ); | 349 | ); |
368 | } | 350 | } |
369 | 351 | ||
@@ -372,32 +354,24 @@ mod tests { | |||
372 | check_assist( | 354 | check_assist( |
373 | fill_match_arms, | 355 | fill_match_arms, |
374 | r#" | 356 | r#" |
375 | enum A { | 357 | enum A { As, Bs, Cs(Option<i32>) } |
376 | As, | 358 | fn main() { |
377 | Bs, | 359 | match A::As<|> { |
378 | Cs(Option<i32>), | 360 | A::As(_) => {} |
379 | } | 361 | a @ A::Bs(_) => {} |
380 | fn main() { | 362 | } |
381 | match A::As<|> { | 363 | } |
382 | A::As(_) => {} | 364 | "#, |
383 | a @ A::Bs(_) => {} | ||
384 | } | ||
385 | } | ||
386 | "#, | ||
387 | r#" | 365 | r#" |
388 | enum A { | 366 | enum A { As, Bs, Cs(Option<i32>) } |
389 | As, | 367 | fn main() { |
390 | Bs, | 368 | match A::As { |
391 | Cs(Option<i32>), | 369 | A::As(_) => {} |
392 | } | 370 | a @ A::Bs(_) => {} |
393 | fn main() { | 371 | A::Cs(${0:_}) => {} |
394 | match A::As { | 372 | } |
395 | A::As(_) => {} | 373 | } |
396 | a @ A::Bs(_) => {} | 374 | "#, |
397 | $0A::Cs(_) => {} | ||
398 | } | ||
399 | } | ||
400 | "#, | ||
401 | ); | 375 | ); |
402 | } | 376 | } |
403 | 377 | ||
@@ -406,39 +380,27 @@ mod tests { | |||
406 | check_assist( | 380 | check_assist( |
407 | fill_match_arms, | 381 | fill_match_arms, |
408 | r#" | 382 | r#" |
409 | enum A { | 383 | enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } } |
410 | As, | ||
411 | Bs, | ||
412 | Cs(String), | ||
413 | Ds(String, String), | ||
414 | Es { x: usize, y: usize } | ||
415 | } | ||
416 | 384 | ||
417 | fn main() { | 385 | fn main() { |
418 | let a = A::As; | 386 | let a = A::As; |
419 | match a<|> {} | 387 | match a<|> {} |
420 | } | 388 | } |
421 | "#, | 389 | "#, |
422 | r#" | 390 | r#" |
423 | enum A { | 391 | enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } } |
424 | As, | ||
425 | Bs, | ||
426 | Cs(String), | ||
427 | Ds(String, String), | ||
428 | Es { x: usize, y: usize } | ||
429 | } | ||
430 | 392 | ||
431 | fn main() { | 393 | fn main() { |
432 | let a = A::As; | 394 | let a = A::As; |
433 | match a { | 395 | match a { |
434 | $0A::As => {} | 396 | $0A::As => {} |
435 | A::Bs => {} | 397 | A::Bs => {} |
436 | A::Cs(_) => {} | 398 | A::Cs(_) => {} |
437 | A::Ds(_, _) => {} | 399 | A::Ds(_, _) => {} |
438 | A::Es { x, y } => {} | 400 | A::Es { x, y } => {} |
439 | } | 401 | } |
440 | } | 402 | } |
441 | "#, | 403 | "#, |
442 | ); | 404 | ); |
443 | } | 405 | } |
444 | 406 | ||
@@ -778,7 +740,7 @@ fn foo(opt: Option<i32>) { | |||
778 | r#" | 740 | r#" |
779 | fn foo(opt: Option<i32>) { | 741 | fn foo(opt: Option<i32>) { |
780 | match opt { | 742 | match opt { |
781 | $0Some(_) => {} | 743 | Some(${0:_}) => {} |
782 | None => {} | 744 | None => {} |
783 | } | 745 | } |
784 | } | 746 | } |
diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs index 96679e160..4e8a0c2db 100644 --- a/crates/ra_assists/src/handlers/raw_string.rs +++ b/crates/ra_assists/src/handlers/raw_string.rs | |||
@@ -1,9 +1,12 @@ | |||
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 | TextRange, TextSize, |
6 | }; | 8 | }; |
9 | use test_utils::mark; | ||
7 | 10 | ||
8 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 11 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
9 | 12 | ||
@@ -31,15 +34,17 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
31 | "Rewrite as raw string", | 34 | "Rewrite as raw string", |
32 | target, | 35 | target, |
33 | |edit| { | 36 | |edit| { |
34 | let max_hash_streak = count_hashes(&value); | 37 | let hashes = "#".repeat(required_hashes(&value).max(1)); |
35 | let mut hashes = String::with_capacity(max_hash_streak + 1); | 38 | if matches!(value, Cow::Borrowed(_)) { |
36 | for _ in 0..hashes.capacity() { | 39 | // Avoid replacing the whole string to better position the cursor. |
37 | hashes.push('#'); | 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 | ); | ||
38 | } | 47 | } |
39 | edit.replace( | ||
40 | token.syntax().text_range(), | ||
41 | format!("r{}\"{}\"{}", hashes, value, hashes), | ||
42 | ); | ||
43 | }, | 48 | }, |
44 | ) | 49 | ) |
45 | } | 50 | } |
@@ -70,6 +75,14 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio | |||
70 | |edit| { | 75 | |edit| { |
71 | // parse inside string to escape `"` | 76 | // parse inside string to escape `"` |
72 | let escaped = value.escape_default().to_string(); | 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 | |||
73 | edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); | 86 | edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); |
74 | }, | 87 | }, |
75 | ) | 88 | ) |
@@ -93,7 +106,7 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio | |||
93 | pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 106 | pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
94 | let token = ctx.find_token_at_offset(RAW_STRING)?; | 107 | let token = ctx.find_token_at_offset(RAW_STRING)?; |
95 | let target = token.text_range(); | 108 | let target = token.text_range(); |
96 | acc.add(AssistId("add_hash", AssistKind::Refactor), "Add # to raw string", target, |edit| { | 109 | acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| { |
97 | edit.insert(token.text_range().start() + TextSize::of('r'), "#"); | 110 | edit.insert(token.text_range().start() + TextSize::of('r'), "#"); |
98 | edit.insert(token.text_range().end(), "#"); | 111 | edit.insert(token.text_range().end(), "#"); |
99 | }) | 112 | }) |
@@ -115,49 +128,58 @@ pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
115 | // } | 128 | // } |
116 | // ``` | 129 | // ``` |
117 | pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 130 | pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
118 | let token = ctx.find_token_at_offset(RAW_STRING)?; | 131 | let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; |
132 | |||
119 | let text = token.text().as_str(); | 133 | let text = token.text().as_str(); |
120 | if text.starts_with("r\"") { | 134 | if !text.starts_with("r#") && text.ends_with('#') { |
121 | // no hash to remove | ||
122 | return None; | 135 | return None; |
123 | } | 136 | } |
124 | let target = token.text_range(); | 137 | |
125 | acc.add( | 138 | let existing_hashes = text.chars().skip(1).take_while(|&it| it == '#').count(); |
126 | AssistId("remove_hash", AssistKind::RefactorRewrite), | 139 | |
127 | "Remove hash from raw string", | 140 | let text_range = token.syntax().text_range(); |
128 | target, | 141 | let internal_text = &text[token.text_range_between_quotes()? - text_range.start()]; |
129 | |edit| { | 142 | |
130 | let result = &text[2..text.len() - 1]; | 143 | if existing_hashes == required_hashes(internal_text) { |
131 | let result = if result.starts_with('\"') { | 144 | mark::hit!(cant_remove_required_hash); |
132 | // FIXME: this logic is wrong, not only the last has has to handled specially | 145 | return None; |
133 | // no more hash, escape | 146 | } |
134 | let internal_str = &result[1..result.len() - 1]; | 147 | |
135 | format!("\"{}\"", internal_str.escape_default().to_string()) | 148 | acc.add(AssistId("remove_hash", AssistKind::RefactorRewrite), "Remove #", text_range, |edit| { |
136 | } else { | 149 | edit.delete(TextRange::at(text_range.start() + TextSize::of('r'), TextSize::of('#'))); |
137 | result.to_owned() | 150 | edit.delete(TextRange::new(text_range.end() - TextSize::of('#'), text_range.end())); |
138 | }; | 151 | }) |
139 | edit.replace(token.text_range(), format!("r{}", result)); | ||
140 | }, | ||
141 | ) | ||
142 | } | 152 | } |
143 | 153 | ||
144 | fn count_hashes(s: &str) -> usize { | 154 | fn required_hashes(s: &str) -> usize { |
145 | let mut max_hash_streak = 0usize; | 155 | let mut res = 0usize; |
146 | for idx in s.match_indices("\"#").map(|(i, _)| i) { | 156 | for idx in s.match_indices('"').map(|(i, _)| i) { |
147 | let (_, sub) = s.split_at(idx + 1); | 157 | let (_, sub) = s.split_at(idx + 1); |
148 | let nb_hash = sub.chars().take_while(|c| *c == '#').count(); | 158 | let n_hashes = sub.chars().take_while(|c| *c == '#').count(); |
149 | if nb_hash > max_hash_streak { | 159 | res = res.max(n_hashes + 1) |
150 | max_hash_streak = nb_hash; | ||
151 | } | ||
152 | } | 160 | } |
153 | max_hash_streak | 161 | res |
162 | } | ||
163 | |||
164 | #[test] | ||
165 | fn test_required_hashes() { | ||
166 | assert_eq!(0, required_hashes("abc")); | ||
167 | assert_eq!(0, required_hashes("###")); | ||
168 | assert_eq!(1, required_hashes("\"")); | ||
169 | assert_eq!(2, required_hashes("\"#abc")); | ||
170 | assert_eq!(0, required_hashes("#abc")); | ||
171 | assert_eq!(3, required_hashes("#ab\"##c")); | ||
172 | assert_eq!(5, required_hashes("#ab\"##\"####c")); | ||
154 | } | 173 | } |
155 | 174 | ||
156 | #[cfg(test)] | 175 | #[cfg(test)] |
157 | mod test { | 176 | mod test { |
158 | use super::*; | 177 | use test_utils::mark; |
178 | |||
159 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; | 179 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
160 | 180 | ||
181 | use super::*; | ||
182 | |||
161 | #[test] | 183 | #[test] |
162 | fn make_raw_string_target() { | 184 | fn make_raw_string_target() { |
163 | check_assist_target( | 185 | check_assist_target( |
@@ -359,33 +381,21 @@ string"###; | |||
359 | fn remove_hash_works() { | 381 | fn remove_hash_works() { |
360 | check_assist( | 382 | check_assist( |
361 | remove_hash, | 383 | remove_hash, |
362 | r##" | 384 | r##"fn f() { let s = <|>r#"random string"#; }"##, |
363 | fn f() { | 385 | r#"fn f() { let s = r"random string"; }"#, |
364 | let s = <|>r#"random string"#; | ||
365 | } | ||
366 | "##, | ||
367 | r#" | ||
368 | fn f() { | ||
369 | let s = r"random string"; | ||
370 | } | ||
371 | "#, | ||
372 | ) | 386 | ) |
373 | } | 387 | } |
374 | 388 | ||
375 | #[test] | 389 | #[test] |
376 | fn remove_hash_with_quote_works() { | 390 | fn cant_remove_required_hash() { |
377 | check_assist( | 391 | mark::check!(cant_remove_required_hash); |
392 | check_assist_not_applicable( | ||
378 | remove_hash, | 393 | remove_hash, |
379 | r##" | 394 | r##" |
380 | fn f() { | 395 | fn f() { |
381 | let s = <|>r#"random"str"ing"#; | 396 | let s = <|>r#"random"str"ing"#; |
382 | } | 397 | } |
383 | "##, | 398 | "##, |
384 | r#" | ||
385 | fn f() { | ||
386 | let s = r"random\"str\"ing"; | ||
387 | } | ||
388 | "#, | ||
389 | ) | 399 | ) |
390 | } | 400 | } |
391 | 401 | ||
@@ -407,27 +417,13 @@ string"###; | |||
407 | } | 417 | } |
408 | 418 | ||
409 | #[test] | 419 | #[test] |
410 | fn remove_hash_not_works() { | 420 | fn remove_hash_doesnt_work() { |
411 | check_assist_not_applicable( | 421 | check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>"random string"; }"#); |
412 | remove_hash, | ||
413 | r#" | ||
414 | fn f() { | ||
415 | let s = <|>"random string"; | ||
416 | } | ||
417 | "#, | ||
418 | ); | ||
419 | } | 422 | } |
420 | 423 | ||
421 | #[test] | 424 | #[test] |
422 | fn remove_hash_no_hash_not_works() { | 425 | fn remove_hash_no_hash_doesnt_work() { |
423 | check_assist_not_applicable( | 426 | check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>r"random string"; }"#); |
424 | remove_hash, | ||
425 | r#" | ||
426 | fn f() { | ||
427 | let s = <|>r"random string"; | ||
428 | } | ||
429 | "#, | ||
430 | ); | ||
431 | } | 427 | } |
432 | 428 | ||
433 | #[test] | 429 | #[test] |
@@ -505,14 +501,4 @@ string"###; | |||
505 | "#, | 501 | "#, |
506 | ); | 502 | ); |
507 | } | 503 | } |
508 | |||
509 | #[test] | ||
510 | fn count_hashes_test() { | ||
511 | assert_eq!(0, count_hashes("abc")); | ||
512 | assert_eq!(0, count_hashes("###")); | ||
513 | assert_eq!(1, count_hashes("\"#abc")); | ||
514 | assert_eq!(0, count_hashes("#abc")); | ||
515 | assert_eq!(2, count_hashes("#ab\"##c")); | ||
516 | assert_eq!(4, count_hashes("#ab\"##\"####c")); | ||
517 | } | ||
518 | } | 504 | } |
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 dfd314abf..3d51faa54 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 | |||
@@ -106,7 +106,7 @@ fn maybe_replace_path( | |||
106 | path: ast::Path, | 106 | path: ast::Path, |
107 | target: ast::Path, | 107 | target: ast::Path, |
108 | ) -> Option<()> { | 108 | ) -> Option<()> { |
109 | if !path_eq(path.clone(), target.clone()) { | 109 | if !path_eq(path.clone(), target) { |
110 | return None; | 110 | return None; |
111 | } | 111 | } |
112 | 112 | ||
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 3d61fbded..465b90415 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -37,6 +37,25 @@ pub enum AssistKind { | |||
37 | RefactorRewrite, | 37 | RefactorRewrite, |
38 | } | 38 | } |
39 | 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 | |||
40 | /// 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 |
41 | /// directly. | 60 | /// directly. |
42 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 61 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs index 858f5ca80..18fcb9049 100644 --- a/crates/ra_assists/src/tests.rs +++ b/crates/ra_assists/src/tests.rs | |||
@@ -6,7 +6,7 @@ use ra_ide_db::RootDatabase; | |||
6 | use ra_syntax::TextRange; | 6 | use ra_syntax::TextRange; |
7 | use test_utils::{assert_eq_text, extract_offset, extract_range}; | 7 | use test_utils::{assert_eq_text, extract_offset, extract_range}; |
8 | 8 | ||
9 | use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; | 9 | use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists}; |
10 | use stdx::trim_indent; | 10 | use stdx::trim_indent; |
11 | 11 | ||
12 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { | 12 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { |
@@ -134,3 +134,46 @@ fn assist_order_if_expr() { | |||
134 | 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"); |
135 | 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"); |
136 | } | 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 | } | ||