From b6d55290a1e416e68bb258bb1e19861692476bd2 Mon Sep 17 00:00:00 2001 From: Geobert Quach Date: Sun, 15 Sep 2019 18:51:34 +0100 Subject: feat(assists): raw string <-> usual string manipulation Fixes #1730 --- crates/ra_assists/src/lib.rs | 5 + crates/ra_assists/src/raw_string.rs | 326 ++++++++++++++++++++++++++++++++++++ 2 files changed, 331 insertions(+) create mode 100644 crates/ra_assists/src/raw_string.rs (limited to 'crates') diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 66cf32524..756acf415 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -96,6 +96,7 @@ mod fill_match_arms; mod merge_match_arms; mod introduce_variable; mod inline_local_variable; +mod raw_string; mod replace_if_let_with_match; mod split_import; mod remove_dbg; @@ -125,6 +126,10 @@ fn all_assists() -> &'static [fn(AssistCtx) -> Option) -> Option { + let literal = ctx.node_at_offset::()?; + if literal.token().kind() == ra_syntax::SyntaxKind::STRING { + ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { + edit.target(literal.syntax().text_range()); + edit.insert(literal.syntax().text_range().start(), "r"); + }); + ctx.build() + } else { + None + } +} + +pub(crate) fn make_usual_string(mut ctx: AssistCtx) -> Option { + let literal = ctx.node_at_offset::()?; + if literal.token().kind() == ra_syntax::SyntaxKind::RAW_STRING { + ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| { + let text = literal.syntax().text(); + let usual_start_pos = text.find_char('"').unwrap(); // we have a RAW_STRING + let end = literal.syntax().text_range().end(); + dbg!(&end); + let mut i = 0; + let mut pos = 0; + let mut c = text.char_at(end - TextUnit::from(i)); + while c != Some('"') { + if c != None { + pos += 1; + } + i += 1; + c = text.char_at(end - TextUnit::from(i)); + } + + edit.target(literal.syntax().text_range()); + edit.delete(TextRange::from_to( + literal.syntax().text_range().start(), + literal.syntax().text_range().start() + usual_start_pos, + )); + edit.delete(TextRange::from_to( + literal.syntax().text_range().end() - TextUnit::from(pos), + literal.syntax().text_range().end(), + )); + }); + ctx.build() + } else { + None + } +} + +pub(crate) fn add_hash(mut ctx: AssistCtx) -> Option { + let literal = ctx.node_at_offset::()?; + if literal.token().kind() == ra_syntax::SyntaxKind::RAW_STRING { + ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| { + edit.target(literal.syntax().text_range()); + edit.insert(literal.syntax().text_range().start() + TextUnit::from(1), "#"); + edit.insert(literal.syntax().text_range().end(), "#"); + }); + ctx.build() + } else { + None + } +} + +pub(crate) fn remove_hash(mut ctx: AssistCtx) -> Option { + let literal = ctx.node_at_offset::()?; + if literal.token().kind() == ra_syntax::SyntaxKind::RAW_STRING { + if !literal.syntax().text().contains_char('#') { + return None; + } + ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| { + edit.target(literal.syntax().text_range()); + edit.delete(TextRange::from_to( + literal.syntax().text_range().start() + TextUnit::from(1), + literal.syntax().text_range().start() + TextUnit::from(2), + )); + edit.delete(TextRange::from_to( + literal.syntax().text_range().end() - TextUnit::from(1), + literal.syntax().text_range().end(), + )); + }); + ctx.build() + } else { + None + } +} + +#[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 string"; + } + "#, + r#""random string""#, + ); + } + + #[test] + fn make_raw_string_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() { + check_assist_not_applicable( + make_raw_string, + r#" + fn f() { + let s = <|>r"random string"; + } + "#, + ); + } + + #[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_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_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"; + } + "#, + ); + } +} -- cgit v1.2.3 From 016c99724356c5bd7e27bd7c6061c7a458f078bf Mon Sep 17 00:00:00 2001 From: Geobert Quach Date: Sun, 15 Sep 2019 21:27:21 +0100 Subject: feat(assists): manage `"` when removing hash and make_usual_string --- crates/ra_assists/src/raw_string.rs | 95 +++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 5 deletions(-) (limited to 'crates') diff --git a/crates/ra_assists/src/raw_string.rs b/crates/ra_assists/src/raw_string.rs index 59dc8d078..10ed93173 100644 --- a/crates/ra_assists/src/raw_string.rs +++ b/crates/ra_assists/src/raw_string.rs @@ -1,7 +1,7 @@ use hir::db::HirDatabase; -use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit}; +use ra_syntax::{ast::AstNode, ast::Literal, SyntaxText, TextRange, TextUnit}; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{assist_ctx::AssistBuilder, Assist, AssistCtx, AssistId}; pub(crate) fn make_raw_string(mut ctx: AssistCtx) -> Option { let literal = ctx.node_at_offset::()?; @@ -23,7 +23,6 @@ pub(crate) fn make_usual_string(mut ctx: AssistCtx) -> Option< let text = literal.syntax().text(); let usual_start_pos = text.find_char('"').unwrap(); // we have a RAW_STRING let end = literal.syntax().text_range().end(); - dbg!(&end); let mut i = 0; let mut pos = 0; let mut c = text.char_at(end - TextUnit::from(i)); @@ -44,6 +43,15 @@ pub(crate) fn make_usual_string(mut ctx: AssistCtx) -> Option< literal.syntax().text_range().end() - TextUnit::from(pos), literal.syntax().text_range().end(), )); + // parse inside string to escape `"` + let start_of_inside = usual_start_pos + TextUnit::from(1); + let inside_str = + text.slice(TextRange::from_to(start_of_inside, text.len() - TextUnit::from(2))); + escape_quote( + edit, + &inside_str, + literal.syntax().text_range().start() + start_of_inside, + ); }); ctx.build() } else { @@ -65,6 +73,21 @@ pub(crate) fn add_hash(mut ctx: AssistCtx) -> Option { } } +fn escape_quote(edit: &mut AssistBuilder, inside_str: &SyntaxText, offset: TextUnit) { + let mut start = TextUnit::from(0); + inside_str.for_each_chunk(|chunk| { + let end = start + TextUnit::of_str(chunk); + let mut i = 0; + for c in chunk.to_string().chars() { + if c == '"' { + edit.insert(offset + start + TextUnit::from(i), "\\"); + } + i += 1; + } + start = end; + }); +} + pub(crate) fn remove_hash(mut ctx: AssistCtx) -> Option { let literal = ctx.node_at_offset::()?; if literal.token().kind() == ra_syntax::SyntaxKind::RAW_STRING { @@ -81,6 +104,17 @@ pub(crate) fn remove_hash(mut ctx: AssistCtx) -> Option"random\nstring"; + } + "#, + r#" + fn f() { + let s = <|>r"random\nstring"; + } + "#, + ) + } + #[test] fn make_raw_string_not_works() { check_assist_not_applicable( @@ -171,12 +222,12 @@ mod test { add_hash, r##" fn f() { - let s = <|>r#"random string"#; + let s = <|>r#"random"string"#; } "##, r###" fn f() { - let s = <|>r##"random string"##; + let s = <|>r##"random"string"##; } "###, ) @@ -224,6 +275,23 @@ mod test { ) } + #[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( @@ -295,6 +363,23 @@ mod test { ) } + #[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( -- cgit v1.2.3 From 889f1f0a7c4295503f65d11510e232c1ea757235 Mon Sep 17 00:00:00 2001 From: Geobert Quach Date: Sun, 15 Sep 2019 21:36:17 +0100 Subject: feature(assists): Fix regression --- crates/ra_assists/src/raw_string.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'crates') diff --git a/crates/ra_assists/src/raw_string.rs b/crates/ra_assists/src/raw_string.rs index 10ed93173..867f09f28 100644 --- a/crates/ra_assists/src/raw_string.rs +++ b/crates/ra_assists/src/raw_string.rs @@ -45,8 +45,8 @@ pub(crate) fn make_usual_string(mut ctx: AssistCtx) -> Option< )); // parse inside string to escape `"` let start_of_inside = usual_start_pos + TextUnit::from(1); - let inside_str = - text.slice(TextRange::from_to(start_of_inside, text.len() - TextUnit::from(2))); + let end_of_inside = text.len() - usual_start_pos - TextUnit::from(1); + let inside_str = text.slice(TextRange::from_to(start_of_inside, end_of_inside)); escape_quote( edit, &inside_str, -- cgit v1.2.3 From a3ab80b332d18056f39e945f3d28894f10f66872 Mon Sep 17 00:00:00 2001 From: Geobert Quach Date: Sun, 15 Sep 2019 21:56:44 +0100 Subject: feat(assists): Rename `escape_quote` to `escape_double_quote` --- crates/ra_assists/src/raw_string.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'crates') diff --git a/crates/ra_assists/src/raw_string.rs b/crates/ra_assists/src/raw_string.rs index 867f09f28..1cb4297a9 100644 --- a/crates/ra_assists/src/raw_string.rs +++ b/crates/ra_assists/src/raw_string.rs @@ -47,7 +47,7 @@ pub(crate) fn make_usual_string(mut ctx: AssistCtx) -> Option< let start_of_inside = usual_start_pos + TextUnit::from(1); let end_of_inside = text.len() - usual_start_pos - TextUnit::from(1); let inside_str = text.slice(TextRange::from_to(start_of_inside, end_of_inside)); - escape_quote( + escape_double_quote( edit, &inside_str, literal.syntax().text_range().start() + start_of_inside, @@ -73,7 +73,7 @@ pub(crate) fn add_hash(mut ctx: AssistCtx) -> Option { } } -fn escape_quote(edit: &mut AssistBuilder, inside_str: &SyntaxText, offset: TextUnit) { +fn escape_double_quote(edit: &mut AssistBuilder, inside_str: &SyntaxText, offset: TextUnit) { let mut start = TextUnit::from(0); inside_str.for_each_chunk(|chunk| { let end = start + TextUnit::of_str(chunk); @@ -109,7 +109,7 @@ pub(crate) fn remove_hash(mut ctx: AssistCtx) -> Option Date: Thu, 19 Sep 2019 22:06:36 +0100 Subject: feat(assists): Apply comments --- crates/ra_assists/src/raw_string.rs | 159 +++++++++++++----------------------- 1 file changed, 59 insertions(+), 100 deletions(-) (limited to 'crates') diff --git a/crates/ra_assists/src/raw_string.rs b/crates/ra_assists/src/raw_string.rs index 1cb4297a9..e00267060 100644 --- a/crates/ra_assists/src/raw_string.rs +++ b/crates/ra_assists/src/raw_string.rs @@ -1,125 +1,84 @@ use hir::db::HirDatabase; -use ra_syntax::{ast::AstNode, ast::Literal, SyntaxText, TextRange, TextUnit}; +use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit}; -use crate::{assist_ctx::AssistBuilder, Assist, AssistCtx, AssistId}; +use crate::{Assist, AssistCtx, AssistId}; pub(crate) fn make_raw_string(mut ctx: AssistCtx) -> Option { let literal = ctx.node_at_offset::()?; - if literal.token().kind() == ra_syntax::SyntaxKind::STRING { - ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { - edit.target(literal.syntax().text_range()); - edit.insert(literal.syntax().text_range().start(), "r"); - }); - ctx.build() - } else { - None + if literal.token().kind() != ra_syntax::SyntaxKind::STRING { + return None; } + ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { + edit.target(literal.syntax().text_range()); + edit.insert(literal.syntax().text_range().start(), "r"); + }); + ctx.build() +} + +fn find_usual_string_range(s: &str) -> Option { + Some(TextRange::from_to( + TextUnit::from(s.find('"')? as u32), + TextUnit::from(s.rfind('"')? as u32), + )) } pub(crate) fn make_usual_string(mut ctx: AssistCtx) -> Option { let literal = ctx.node_at_offset::()?; - if literal.token().kind() == ra_syntax::SyntaxKind::RAW_STRING { - ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| { - let text = literal.syntax().text(); - let usual_start_pos = text.find_char('"').unwrap(); // we have a RAW_STRING - let end = literal.syntax().text_range().end(); - let mut i = 0; - let mut pos = 0; - let mut c = text.char_at(end - TextUnit::from(i)); - while c != Some('"') { - if c != None { - pos += 1; - } - i += 1; - c = text.char_at(end - TextUnit::from(i)); - } - - edit.target(literal.syntax().text_range()); - edit.delete(TextRange::from_to( - literal.syntax().text_range().start(), - literal.syntax().text_range().start() + usual_start_pos, - )); - edit.delete(TextRange::from_to( - literal.syntax().text_range().end() - TextUnit::from(pos), - literal.syntax().text_range().end(), - )); - // parse inside string to escape `"` - let start_of_inside = usual_start_pos + TextUnit::from(1); - let end_of_inside = text.len() - usual_start_pos - TextUnit::from(1); - let inside_str = text.slice(TextRange::from_to(start_of_inside, end_of_inside)); - escape_double_quote( - edit, - &inside_str, - literal.syntax().text_range().start() + start_of_inside, - ); - }); - ctx.build() - } else { - None + if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { + return None; } + let token = literal.token(); + let text = token.text().as_str(); + let usual_string_range = find_usual_string_range(text)?; + ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| { + edit.target(literal.syntax().text_range()); + // parse inside string to escape `"` + let start_of_inside = usual_string_range.start().to_usize() + 1; + let end_of_inside = usual_string_range.end().to_usize(); + let inside_str = &text[start_of_inside..end_of_inside]; + let escaped = inside_str.escape_default().to_string(); + edit.replace(literal.syntax().text_range(), format!("\"{}\"", escaped)); + }); + ctx.build() } pub(crate) fn add_hash(mut ctx: AssistCtx) -> Option { let literal = ctx.node_at_offset::()?; - if literal.token().kind() == ra_syntax::SyntaxKind::RAW_STRING { - ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| { - edit.target(literal.syntax().text_range()); - edit.insert(literal.syntax().text_range().start() + TextUnit::from(1), "#"); - edit.insert(literal.syntax().text_range().end(), "#"); - }); - ctx.build() - } else { - None + if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { + return None; } -} - -fn escape_double_quote(edit: &mut AssistBuilder, inside_str: &SyntaxText, offset: TextUnit) { - let mut start = TextUnit::from(0); - inside_str.for_each_chunk(|chunk| { - let end = start + TextUnit::of_str(chunk); - let mut i = 0; - for c in chunk.to_string().chars() { - if c == '"' { - edit.insert(offset + start + TextUnit::from(i), "\\"); - } - i += 1; - } - start = end; + ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| { + edit.target(literal.syntax().text_range()); + edit.insert(literal.syntax().text_range().start() + TextUnit::of_char('r'), "#"); + edit.insert(literal.syntax().text_range().end(), "#"); }); + ctx.build() } pub(crate) fn remove_hash(mut ctx: AssistCtx) -> Option { let literal = ctx.node_at_offset::()?; - if literal.token().kind() == ra_syntax::SyntaxKind::RAW_STRING { - if !literal.syntax().text().contains_char('#') { - return None; - } - ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| { - edit.target(literal.syntax().text_range()); - edit.delete(TextRange::from_to( - literal.syntax().text_range().start() + TextUnit::from(1), - literal.syntax().text_range().start() + TextUnit::from(2), - )); - edit.delete(TextRange::from_to( - literal.syntax().text_range().end() - TextUnit::from(1), - literal.syntax().text_range().end(), - )); - let text = literal.syntax().text(); - if text.char_at(TextUnit::from(2)) == Some('"') { - // no more hash after assist, need to escape any `"` in the string - let inside_str = text - .slice(TextRange::from_to(TextUnit::from(3), text.len() - TextUnit::from(2))); - escape_double_quote( - edit, - &inside_str, - literal.syntax().text_range().start() + TextUnit::from(3), - ); - } - }); - ctx.build() - } else { - None + if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { + return None; + } + let token = literal.token(); + let text = token.text().as_str(); + if text.starts_with("r\"") { + // no hash to remove + return None; } + ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| { + edit.target(literal.syntax().text_range()); + let result = &text[2..text.len() - 1]; + let result = if result.starts_with("\"") { + // 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(literal.syntax().text_range(), format!("r{}", result)); + }); + ctx.build() } #[cfg(test)] -- cgit v1.2.3