Skip to content

Commit de8c5fc

Browse files
bors[bot]matklad
andauthored
Merge #5427
5427: More precise ranges in remove hashes assist r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
2 parents 4eddaa4 + 7c082dc commit de8c5fc

File tree

1 file changed

+52
-79
lines changed

1 file changed

+52
-79
lines changed

crates/ra_assists/src/handlers/raw_string.rs

Lines changed: 52 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ use ra_syntax::{
44
ast::{self, HasQuotes, HasStringValue},
55
AstToken,
66
SyntaxKind::{RAW_STRING, STRING},
7-
TextSize,
7+
TextRange, TextSize,
88
};
9+
use test_utils::mark;
910

1011
use crate::{AssistContext, AssistId, AssistKind, Assists};
1112

@@ -33,8 +34,7 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<
3334
"Rewrite as raw string",
3435
target,
3536
|edit| {
36-
let max_hash_streak = count_hashes(&value);
37-
let hashes = "#".repeat(max_hash_streak + 1);
37+
let hashes = "#".repeat(required_hashes(&value).max(1));
3838
if matches!(value, Cow::Borrowed(_)) {
3939
// Avoid replacing the whole string to better position the cursor.
4040
edit.insert(token.syntax().text_range().start(), format!("r{}", hashes));
@@ -106,7 +106,7 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio
106106
pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
107107
let token = ctx.find_token_at_offset(RAW_STRING)?;
108108
let target = token.text_range();
109-
acc.add(AssistId("add_hash", AssistKind::Refactor), "Add # to raw string", target, |edit| {
109+
acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| {
110110
edit.insert(token.text_range().start() + TextSize::of('r'), "#");
111111
edit.insert(token.text_range().end(), "#");
112112
})
@@ -128,49 +128,58 @@ pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
128128
// }
129129
// ```
130130
pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
131-
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+
132133
let text = token.text().as_str();
133-
if text.starts_with("r\"") {
134-
// no hash to remove
134+
if !text.starts_with("r#") && text.ends_with("#") {
135135
return None;
136136
}
137-
let target = token.text_range();
138-
acc.add(
139-
AssistId("remove_hash", AssistKind::RefactorRewrite),
140-
"Remove hash from raw string",
141-
target,
142-
|edit| {
143-
let result = &text[2..text.len() - 1];
144-
let result = if result.starts_with('\"') {
145-
// FIXME: this logic is wrong, not only the last has has to handled specially
146-
// no more hash, escape
147-
let internal_str = &result[1..result.len() - 1];
148-
format!("\"{}\"", internal_str.escape_default().to_string())
149-
} else {
150-
result.to_owned()
151-
};
152-
edit.replace(token.text_range(), format!("r{}", result));
153-
},
154-
)
137+
138+
let existing_hashes = text.chars().skip(1).take_while(|&it| it == '#').count();
139+
140+
let text_range = token.syntax().text_range();
141+
let internal_text = &text[token.text_range_between_quotes()? - text_range.start()];
142+
143+
if existing_hashes == required_hashes(internal_text) {
144+
mark::hit!(cant_remove_required_hash);
145+
return None;
146+
}
147+
148+
acc.add(AssistId("remove_hash", AssistKind::RefactorRewrite), "Remove #", text_range, |edit| {
149+
edit.delete(TextRange::at(text_range.start() + TextSize::of('r'), TextSize::of('#')));
150+
edit.delete(TextRange::new(text_range.end() - TextSize::of('#'), text_range.end()));
151+
})
155152
}
156153

157-
fn count_hashes(s: &str) -> usize {
158-
let mut max_hash_streak = 0usize;
159-
for idx in s.match_indices("\"#").map(|(i, _)| i) {
154+
fn required_hashes(s: &str) -> usize {
155+
let mut res = 0usize;
156+
for idx in s.match_indices('"').map(|(i, _)| i) {
160157
let (_, sub) = s.split_at(idx + 1);
161-
let nb_hash = sub.chars().take_while(|c| *c == '#').count();
162-
if nb_hash > max_hash_streak {
163-
max_hash_streak = nb_hash;
164-
}
158+
let n_hashes = sub.chars().take_while(|c| *c == '#').count();
159+
res = res.max(n_hashes + 1)
165160
}
166-
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"));
167173
}
168174

169175
#[cfg(test)]
170176
mod test {
171-
use super::*;
177+
use test_utils::mark;
178+
172179
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
173180

181+
use super::*;
182+
174183
#[test]
175184
fn make_raw_string_target() {
176185
check_assist_target(
@@ -372,33 +381,21 @@ string"###;
372381
fn remove_hash_works() {
373382
check_assist(
374383
remove_hash,
375-
r##"
376-
fn f() {
377-
let s = <|>r#"random string"#;
378-
}
379-
"##,
380-
r#"
381-
fn f() {
382-
let s = r"random string";
383-
}
384-
"#,
384+
r##"fn f() { let s = <|>r#"random string"#; }"##,
385+
r#"fn f() { let s = r"random string"; }"#,
385386
)
386387
}
387388

388389
#[test]
389-
fn remove_hash_with_quote_works() {
390-
check_assist(
390+
fn cant_remove_required_hash() {
391+
mark::check!(cant_remove_required_hash);
392+
check_assist_not_applicable(
391393
remove_hash,
392394
r##"
393395
fn f() {
394396
let s = <|>r#"random"str"ing"#;
395397
}
396398
"##,
397-
r#"
398-
fn f() {
399-
let s = r"random\"str\"ing";
400-
}
401-
"#,
402399
)
403400
}
404401

@@ -420,27 +417,13 @@ string"###;
420417
}
421418

422419
#[test]
423-
fn remove_hash_not_works() {
424-
check_assist_not_applicable(
425-
remove_hash,
426-
r#"
427-
fn f() {
428-
let s = <|>"random string";
429-
}
430-
"#,
431-
);
420+
fn remove_hash_doesnt_work() {
421+
check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>"random string"; }"#);
432422
}
433423

434424
#[test]
435-
fn remove_hash_no_hash_not_works() {
436-
check_assist_not_applicable(
437-
remove_hash,
438-
r#"
439-
fn f() {
440-
let s = <|>r"random string";
441-
}
442-
"#,
443-
);
425+
fn remove_hash_no_hash_doesnt_work() {
426+
check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>r"random string"; }"#);
444427
}
445428

446429
#[test]
@@ -518,14 +501,4 @@ string"###;
518501
"#,
519502
);
520503
}
521-
522-
#[test]
523-
fn count_hashes_test() {
524-
assert_eq!(0, count_hashes("abc"));
525-
assert_eq!(0, count_hashes("###"));
526-
assert_eq!(1, count_hashes("\"#abc"));
527-
assert_eq!(0, count_hashes("#abc"));
528-
assert_eq!(2, count_hashes("#ab\"##c"));
529-
assert_eq!(4, count_hashes("#ab\"##\"####c"));
530-
}
531504
}

0 commit comments

Comments
 (0)