5427: More precise ranges in remove hashes assist r=matklad a=matklad



bors r+
🤖

Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2020-07-17 14:39:50 +00:00 committed by GitHub
commit de8c5fc09b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -4,8 +4,9 @@ use ra_syntax::{
ast::{self, HasQuotes, HasStringValue}, ast::{self, HasQuotes, HasStringValue},
AstToken, AstToken,
SyntaxKind::{RAW_STRING, STRING}, SyntaxKind::{RAW_STRING, STRING},
TextSize, TextRange, TextSize,
}; };
use test_utils::mark;
use crate::{AssistContext, AssistId, AssistKind, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
@ -33,8 +34,7 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<
"Rewrite as raw string", "Rewrite as raw string",
target, target,
|edit| { |edit| {
let max_hash_streak = count_hashes(&value); let hashes = "#".repeat(required_hashes(&value).max(1));
let hashes = "#".repeat(max_hash_streak + 1);
if matches!(value, Cow::Borrowed(_)) { if matches!(value, Cow::Borrowed(_)) {
// Avoid replacing the whole string to better position the cursor. // Avoid replacing the whole string to better position the cursor.
edit.insert(token.syntax().text_range().start(), format!("r{}", hashes)); 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
pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let token = ctx.find_token_at_offset(RAW_STRING)?; let token = ctx.find_token_at_offset(RAW_STRING)?;
let target = token.text_range(); let target = token.text_range();
acc.add(AssistId("add_hash", AssistKind::Refactor), "Add # to raw string", target, |edit| { acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| {
edit.insert(token.text_range().start() + TextSize::of('r'), "#"); edit.insert(token.text_range().start() + TextSize::of('r'), "#");
edit.insert(token.text_range().end(), "#"); edit.insert(token.text_range().end(), "#");
}) })
@ -128,49 +128,58 @@ pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
// } // }
// ``` // ```
pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let token = ctx.find_token_at_offset(RAW_STRING)?; let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
let text = token.text().as_str(); let text = token.text().as_str();
if text.starts_with("r\"") { if !text.starts_with("r#") && text.ends_with("#") {
// no hash to remove
return None; return None;
} }
let target = token.text_range();
acc.add( let existing_hashes = text.chars().skip(1).take_while(|&it| it == '#').count();
AssistId("remove_hash", AssistKind::RefactorRewrite),
"Remove hash from raw string", let text_range = token.syntax().text_range();
target, let internal_text = &text[token.text_range_between_quotes()? - text_range.start()];
|edit| {
let result = &text[2..text.len() - 1]; if existing_hashes == required_hashes(internal_text) {
let result = if result.starts_with('\"') { mark::hit!(cant_remove_required_hash);
// FIXME: this logic is wrong, not only the last has has to handled specially return None;
// no more hash, escape }
let internal_str = &result[1..result.len() - 1];
format!("\"{}\"", internal_str.escape_default().to_string()) acc.add(AssistId("remove_hash", AssistKind::RefactorRewrite), "Remove #", text_range, |edit| {
} else { edit.delete(TextRange::at(text_range.start() + TextSize::of('r'), TextSize::of('#')));
result.to_owned() edit.delete(TextRange::new(text_range.end() - TextSize::of('#'), text_range.end()));
}; })
edit.replace(token.text_range(), format!("r{}", result));
},
)
} }
fn count_hashes(s: &str) -> usize { fn required_hashes(s: &str) -> usize {
let mut max_hash_streak = 0usize; let mut res = 0usize;
for idx in s.match_indices("\"#").map(|(i, _)| i) { for idx in s.match_indices('"').map(|(i, _)| i) {
let (_, sub) = s.split_at(idx + 1); let (_, sub) = s.split_at(idx + 1);
let nb_hash = sub.chars().take_while(|c| *c == '#').count(); let n_hashes = sub.chars().take_while(|c| *c == '#').count();
if nb_hash > max_hash_streak { res = res.max(n_hashes + 1)
max_hash_streak = nb_hash;
}
} }
max_hash_streak res
}
#[test]
fn test_required_hashes() {
assert_eq!(0, required_hashes("abc"));
assert_eq!(0, required_hashes("###"));
assert_eq!(1, required_hashes("\""));
assert_eq!(2, required_hashes("\"#abc"));
assert_eq!(0, required_hashes("#abc"));
assert_eq!(3, required_hashes("#ab\"##c"));
assert_eq!(5, required_hashes("#ab\"##\"####c"));
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use test_utils::mark;
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
use super::*;
#[test] #[test]
fn make_raw_string_target() { fn make_raw_string_target() {
check_assist_target( check_assist_target(
@ -372,33 +381,21 @@ string"###;
fn remove_hash_works() { fn remove_hash_works() {
check_assist( check_assist(
remove_hash, remove_hash,
r##" r##"fn f() { let s = <|>r#"random string"#; }"##,
fn f() { r#"fn f() { let s = r"random string"; }"#,
let s = <|>r#"random string"#;
}
"##,
r#"
fn f() {
let s = r"random string";
}
"#,
) )
} }
#[test] #[test]
fn remove_hash_with_quote_works() { fn cant_remove_required_hash() {
check_assist( mark::check!(cant_remove_required_hash);
check_assist_not_applicable(
remove_hash, remove_hash,
r##" r##"
fn f() { fn f() {
let s = <|>r#"random"str"ing"#; let s = <|>r#"random"str"ing"#;
} }
"##, "##,
r#"
fn f() {
let s = r"random\"str\"ing";
}
"#,
) )
} }
@ -420,27 +417,13 @@ string"###;
} }
#[test] #[test]
fn remove_hash_not_works() { fn remove_hash_doesnt_work() {
check_assist_not_applicable( check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>"random string"; }"#);
remove_hash,
r#"
fn f() {
let s = <|>"random string";
}
"#,
);
} }
#[test] #[test]
fn remove_hash_no_hash_not_works() { fn remove_hash_no_hash_doesnt_work() {
check_assist_not_applicable( check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>r"random string"; }"#);
remove_hash,
r#"
fn f() {
let s = <|>r"random string";
}
"#,
);
} }
#[test] #[test]
@ -518,14 +501,4 @@ string"###;
"#, "#,
); );
} }
#[test]
fn count_hashes_test() {
assert_eq!(0, count_hashes("abc"));
assert_eq!(0, count_hashes("###"));
assert_eq!(1, count_hashes("\"#abc"));
assert_eq!(0, count_hashes("#abc"));
assert_eq!(2, count_hashes("#ab\"##c"));
assert_eq!(4, count_hashes("#ab\"##\"####c"));
}
} }