From 561b4b11ff1d87ea1ff2477dcba6ae1f396573a3 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 7 Feb 2020 15:53:31 +0100 Subject: Name assist handlers --- crates/ra_assists/src/handlers/raw_string.rs | 499 +++++++++++++++++++++++++++ 1 file changed, 499 insertions(+) create mode 100644 crates/ra_assists/src/handlers/raw_string.rs (limited to 'crates/ra_assists/src/handlers/raw_string.rs') diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs new file mode 100644 index 000000000..2c0a1e126 --- /dev/null +++ b/crates/ra_assists/src/handlers/raw_string.rs @@ -0,0 +1,499 @@ +use ra_syntax::{ + ast, AstToken, + SyntaxKind::{RAW_STRING, STRING}, + TextUnit, +}; + +use crate::{Assist, AssistCtx, AssistId}; + +// Assist: make_raw_string +// +// Adds `r#` to a plain string literal. +// +// ``` +// fn main() { +// "Hello,<|> World!"; +// } +// ``` +// -> +// ``` +// fn main() { +// r#"Hello, World!"#; +// } +// ``` +pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option { + let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; + let value = token.value()?; + ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", |edit| { + edit.target(token.syntax().text_range()); + let max_hash_streak = count_hashes(&value); + let mut hashes = String::with_capacity(max_hash_streak + 1); + for _ in 0..hashes.capacity() { + hashes.push('#'); + } + edit.replace(token.syntax().text_range(), format!("r{}\"{}\"{}", hashes, value, hashes)); + }) +} + +// Assist: make_usual_string +// +// Turns a raw string into a plain string. +// +// ``` +// fn main() { +// r#"Hello,<|> "World!""#; +// } +// ``` +// -> +// ``` +// fn main() { +// "Hello, \"World!\""; +// } +// ``` +pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option { + let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; + let value = token.value()?; + ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", |edit| { + edit.target(token.syntax().text_range()); + // parse inside string to escape `"` + let escaped = value.escape_default().to_string(); + edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); + }) +} + +// Assist: add_hash +// +// Adds a hash to a raw string literal. +// +// ``` +// fn main() { +// r#"Hello,<|> World!"#; +// } +// ``` +// -> +// ``` +// fn main() { +// r##"Hello, World!"##; +// } +// ``` +pub(crate) fn add_hash(ctx: AssistCtx) -> Option { + let token = ctx.find_token_at_offset(RAW_STRING)?; + ctx.add_assist(AssistId("add_hash"), "Add # to raw string", |edit| { + edit.target(token.text_range()); + edit.insert(token.text_range().start() + TextUnit::of_char('r'), "#"); + edit.insert(token.text_range().end(), "#"); + }) +} + +// Assist: remove_hash +// +// Removes a hash from a raw string literal. +// +// ``` +// fn main() { +// r#"Hello,<|> World!"#; +// } +// ``` +// -> +// ``` +// fn main() { +// r"Hello, World!"; +// } +// ``` +pub(crate) fn remove_hash(ctx: AssistCtx) -> Option { + let token = ctx.find_token_at_offset(RAW_STRING)?; + let text = token.text().as_str(); + if text.starts_with("r\"") { + // no hash to remove + return None; + } + ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", |edit| { + edit.target(token.text_range()); + let result = &text[2..text.len() - 1]; + let result = if result.starts_with('\"') { + // FIXME: this logic is wrong, not only the last has has to handled specially + // no more hash, escape + let internal_str = &result[1..result.len() - 1]; + format!("\"{}\"", internal_str.escape_default().to_string()) + } else { + result.to_owned() + }; + edit.replace(token.text_range(), format!("r{}", result)); + }) +} + +fn count_hashes(s: &str) -> usize { + let mut max_hash_streak = 0usize; + for idx in s.match_indices("\"#").map(|(i, _)| i) { + let (_, sub) = s.split_at(idx + 1); + let nb_hash = sub.chars().take_while(|c| *c == '#').count(); + if nb_hash > max_hash_streak { + max_hash_streak = nb_hash; + } + } + max_hash_streak +} + +#[cfg(test)] +mod test { + use super::*; + use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; + + #[test] + fn make_raw_string_target() { + check_assist_target( + make_raw_string, + r#" + fn f() { + let s = <|>"random\nstring"; + } + "#, + r#""random\nstring""#, + ); + } + + #[test] + fn make_raw_string_works() { + check_assist( + make_raw_string, + r#" + fn f() { + let s = <|>"random\nstring"; + } + "#, + r##" + fn f() { + let s = <|>r#"random +string"#; + } + "##, + ) + } + + #[test] + fn make_raw_string_works_inside_macros() { + check_assist( + make_raw_string, + r#" + fn f() { + format!(<|>"x = {}", 92) + } + "#, + r##" + fn f() { + format!(<|>r#"x = {}"#, 92) + } + "##, + ) + } + + #[test] + fn make_raw_string_hashes_inside_works() { + check_assist( + make_raw_string, + r###" + fn f() { + let s = <|>"#random##\nstring"; + } + "###, + r####" + fn f() { + let s = <|>r#"#random## +string"#; + } + "####, + ) + } + + #[test] + fn make_raw_string_closing_hashes_inside_works() { + check_assist( + make_raw_string, + r###" + fn f() { + let s = <|>"#random\"##\nstring"; + } + "###, + r####" + fn f() { + let s = <|>r###"#random"## +string"###; + } + "####, + ) + } + + #[test] + fn make_raw_string_nothing_to_unescape_works() { + check_assist( + make_raw_string, + r#" + fn f() { + let s = <|>"random string"; + } + "#, + r##" + fn f() { + let s = <|>r#"random string"#; + } + "##, + ) + } + + #[test] + fn make_raw_string_not_works_on_partial_string() { + check_assist_not_applicable( + make_raw_string, + r#" + fn f() { + let s = "foo<|> + } + "#, + ) + } + + #[test] + fn make_usual_string_not_works_on_partial_string() { + check_assist_not_applicable( + make_usual_string, + r#" + fn main() { + let s = r#"bar<|> + } + "#, + ) + } + + #[test] + fn add_hash_target() { + check_assist_target( + add_hash, + r#" + fn f() { + let s = <|>r"random string"; + } + "#, + r#"r"random string""#, + ); + } + + #[test] + fn add_hash_works() { + check_assist( + add_hash, + r#" + fn f() { + let s = <|>r"random string"; + } + "#, + r##" + fn f() { + let s = <|>r#"random string"#; + } + "##, + ) + } + + #[test] + fn add_more_hash_works() { + check_assist( + add_hash, + r##" + fn f() { + let s = <|>r#"random"string"#; + } + "##, + r###" + fn f() { + let s = <|>r##"random"string"##; + } + "###, + ) + } + + #[test] + fn add_hash_not_works() { + check_assist_not_applicable( + add_hash, + r#" + fn f() { + let s = <|>"random string"; + } + "#, + ); + } + + #[test] + fn remove_hash_target() { + check_assist_target( + remove_hash, + r##" + fn f() { + let s = <|>r#"random string"#; + } + "##, + r##"r#"random string"#"##, + ); + } + + #[test] + fn remove_hash_works() { + check_assist( + remove_hash, + r##" + fn f() { + let s = <|>r#"random string"#; + } + "##, + r#" + fn f() { + let s = <|>r"random string"; + } + "#, + ) + } + + #[test] + fn remove_hash_with_quote_works() { + check_assist( + remove_hash, + r##" + fn f() { + let s = <|>r#"random"str"ing"#; + } + "##, + r#" + fn f() { + let s = <|>r"random\"str\"ing"; + } + "#, + ) + } + + #[test] + fn remove_more_hash_works() { + check_assist( + remove_hash, + r###" + fn f() { + let s = <|>r##"random string"##; + } + "###, + r##" + fn f() { + let s = <|>r#"random string"#; + } + "##, + ) + } + + #[test] + fn remove_hash_not_works() { + check_assist_not_applicable( + remove_hash, + r#" + fn f() { + let s = <|>"random string"; + } + "#, + ); + } + + #[test] + fn remove_hash_no_hash_not_works() { + check_assist_not_applicable( + remove_hash, + r#" + fn f() { + let s = <|>r"random string"; + } + "#, + ); + } + + #[test] + fn make_usual_string_target() { + check_assist_target( + make_usual_string, + r##" + fn f() { + let s = <|>r#"random string"#; + } + "##, + r##"r#"random string"#"##, + ); + } + + #[test] + fn make_usual_string_works() { + check_assist( + make_usual_string, + r##" + fn f() { + let s = <|>r#"random string"#; + } + "##, + r#" + fn f() { + let s = <|>"random string"; + } + "#, + ) + } + + #[test] + fn make_usual_string_with_quote_works() { + check_assist( + make_usual_string, + r##" + fn f() { + let s = <|>r#"random"str"ing"#; + } + "##, + r#" + fn f() { + let s = <|>"random\"str\"ing"; + } + "#, + ) + } + + #[test] + fn make_usual_string_more_hash_works() { + check_assist( + make_usual_string, + r###" + fn f() { + let s = <|>r##"random string"##; + } + "###, + r##" + fn f() { + let s = <|>"random string"; + } + "##, + ) + } + + #[test] + fn make_usual_string_not_works() { + check_assist_not_applicable( + make_usual_string, + r#" + fn f() { + let s = <|>"random 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")); + } +} -- cgit v1.2.3