aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r--crates/ra_assists/src/assist_config.rs5
-rw-r--r--crates/ra_assists/src/assist_context.rs48
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs25
-rw-r--r--crates/ra_assists/src/handlers/add_turbo_fish.rs29
-rw-r--r--crates/ra_assists/src/handlers/change_visibility.rs16
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs210
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs158
-rw-r--r--crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs2
-rw-r--r--crates/ra_assists/src/lib.rs19
-rw-r--r--crates/ra_assists/src/tests.rs45
10 files changed, 330 insertions, 227 deletions
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
7use crate::AssistKind;
8
7#[derive(Clone, Debug, PartialEq, Eq)] 9#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct AssistConfig { 10pub struct AssistConfig {
9 pub snippet_cap: Option<SnippetCap>, 11 pub snippet_cap: Option<SnippetCap>,
12 pub allowed: Option<Vec<AssistKind>>,
10} 13}
11 14
12impl AssistConfig { 15impl AssistConfig {
@@ -22,6 +25,6 @@ pub struct SnippetCap {
22 25
23impl Default for AssistConfig { 26impl 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
20use crate::{ 20use 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
108impl Assists { 109impl 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
175pub(crate) struct AssistBuilder { 203pub(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
182impl AssistBuilder { 210impl 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#"
696trait Tr {
697 type Ty: Copy + 'static;
698}
699
700impl Tr for ()<|> {
701}"#,
702 r#"
703trait Tr {
704 type Ty: Copy + 'static;
705}
706
707impl 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// ```
27pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 27pub(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#"
98fn make<T>() -> T {}
99fn main() {
100 make()<|>;
101}
102"#,
103 r#"
104fn make<T>() -> T {}
105fn 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 @@
1use ra_syntax::{ 1use 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};
7use test_utils::mark; 9use test_utils::mark;
@@ -28,12 +30,15 @@ pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Optio
28 30
29fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 31fn 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 { 302enum A { As, Bs, Cs(Option<i32>) }
295 As, 303fn 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 { 310enum A { As, Bs, Cs(Option<i32>) }
307 As, 311fn 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 { 326enum A { As, Bs, Cs, Ds(String), Es(B) }
327 As, 327enum B { Xs, Ys }
328 Bs, 328fn 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 { 337enum A { As, Bs, Cs, Ds(String), Es(B) }
347 As, 338enum B { Xs, Ys }
348 Bs, 339fn 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 { 357enum A { As, Bs, Cs(Option<i32>) }
376 As, 358fn 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 { 366enum A { As, Bs, Cs(Option<i32>) }
389 As, 367fn 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 { 383enum 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() { 385fn 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 { 391enum 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() { 393fn 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#"
779fn foo(opt: Option<i32>) { 741fn 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 @@
1use std::borrow::Cow;
2
1use ra_syntax::{ 3use 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};
9use test_utils::mark;
7 10
8use crate::{AssistContext, AssistId, AssistKind, Assists}; 11use 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
93pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 106pub(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// ```
117pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 130pub(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
144fn count_hashes(s: &str) -> usize { 154fn 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]
165fn 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)]
157mod test { 176mod 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
40impl 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;
6use ra_syntax::TextRange; 6use ra_syntax::TextRange;
7use test_utils::{assert_eq_text, extract_offset, extract_range}; 7use test_utils::{assert_eq_text, extract_offset, extract_range};
8 8
9use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; 9use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists};
10use stdx::trim_indent; 10use stdx::trim_indent;
11 11
12pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { 12pub(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]
139fn 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}