From 281e1071558dff3138805de49dfbb0ad91b3acd3 Mon Sep 17 00:00:00 2001 From: Geobert Quach Date: Thu, 26 Sep 2019 20:31:45 +0100 Subject: feat(assists): Make raw string unescaped --- Cargo.lock | 1 + crates/ra_assists/Cargo.toml | 1 + crates/ra_assists/src/assists/raw_string.rs | 77 +++++++++++++++++++++++++++++ crates/ra_assists/src/lib.rs | 2 +- docs/user/features.md | 15 ++++++ 5 files changed, 95 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 275b27775..dad9b1df1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -904,6 +904,7 @@ dependencies = [ "ra_syntax 0.1.0", "ra_text_edit 0.1.0", "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_lexer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "test_utils 0.1.0", ] diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml index 02966bbda..d3b6aeb36 100644 --- a/crates/ra_assists/Cargo.toml +++ b/crates/ra_assists/Cargo.toml @@ -11,6 +11,7 @@ join_to_string = "0.1.3" itertools = "0.8.0" arrayvec = "0.4.10" rustc-hash = "1.0.1" +rustc_lexer = "0.1.0" ra_syntax = { path = "../ra_syntax" } ra_text_edit = { path = "../ra_text_edit" } diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/assists/raw_string.rs index 965a64c98..fe396806b 100644 --- a/crates/ra_assists/src/assists/raw_string.rs +++ b/crates/ra_assists/src/assists/raw_string.rs @@ -1,5 +1,6 @@ use hir::db::HirDatabase; use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit}; +use rustc_lexer; use crate::{Assist, AssistCtx, AssistId}; @@ -15,6 +16,39 @@ pub(crate) fn make_raw_string(mut ctx: AssistCtx) -> Option) -> Option { + let literal = ctx.node_at_offset::()?; + if literal.token().kind() != ra_syntax::SyntaxKind::STRING { + return None; + } + let token = literal.token(); + let text = token.text().as_str(); + if !text.contains(&['\\', '\r'][..]) { + return None; + } + let usual_string_range = find_usual_string_range(text)?; + ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { + edit.target(literal.syntax().text_range()); + 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 mut unescaped = String::with_capacity(inside_str.len()); + let mut error = Ok(()); + rustc_lexer::unescape::unescape_str(inside_str, &mut |_, unescaped_char| { + match unescaped_char { + Ok(c) => unescaped.push(c), + Err(_) => error = Err(()), + } + }); + if error.is_err() { + eprintln!("Error unescaping string"); + } else { + edit.replace(literal.syntax().text_range(), format!("r\"{}\"", unescaped)); + } + }); + ctx.build() +} + fn find_usual_string_range(s: &str) -> Option { Some(TextRange::from_to( TextUnit::from(s.find('"')? as u32), @@ -145,6 +179,49 @@ mod test { ); } + #[test] + fn make_raw_string_unescaped_target() { + check_assist_target( + make_raw_string_unescaped, + r#" + fn f() { + let s = <|>"random\nstring"; + } + "#, + r#""random\nstring""#, + ); + } + + #[test] + fn make_raw_string_unescaped_works() { + check_assist( + make_raw_string_unescaped, + r#" + fn f() { + let s = <|>"random\nstring"; + } + "#, + r#" + fn f() { + let s = <|>r"random +string"; + } + "#, + ) + } + + #[test] + fn make_raw_string_unescaped_dont_works() { + check_assist_not_applicable( + make_raw_string_unescaped, + r#" + fn f() { + let s = <|>"random string"; + } + "#, + ) + } + #[test] fn add_hash_target() { check_assist_target( diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 897af2b02..d1e2d3251 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -132,6 +132,7 @@ mod assists { move_bounds::move_bounds_to_where_clause, raw_string::add_hash, raw_string::make_raw_string, + raw_string::make_raw_string_unescaped, raw_string::make_usual_string, raw_string::remove_hash, ] @@ -340,5 +341,4 @@ mod tests { assert_eq!(assists.next().expect("expected assist").0.label, "introduce variable"); assert_eq!(assists.next().expect("expected assist").0.label, "replace with match"); } - } diff --git a/docs/user/features.md b/docs/user/features.md index eb81cba26..fadd4cdf1 100644 --- a/docs/user/features.md +++ b/docs/user/features.md @@ -459,6 +459,21 @@ fn f() { } ``` +- Make raw string unescaped + +```rust +// before: +fn f() { + let s = <|>"ab\ncd"; +} + +// after: +fn f() { + let s = <|>r"ab +cd"; +} +``` + - Make usual string ```rust -- cgit v1.2.3 From e293c34e85d2343cd8f16842634133a2dee4e8ad Mon Sep 17 00:00:00 2001 From: Geobert Quach Date: Sun, 29 Sep 2019 19:14:19 +0100 Subject: feat(assists): Keep only one version of make_raw_string --- crates/ra_assists/src/assists/raw_string.rs | 95 ++++------------------------- crates/ra_assists/src/lib.rs | 1 - docs/user/features.md | 18 +----- 3 files changed, 15 insertions(+), 99 deletions(-) diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/assists/raw_string.rs index fe396806b..e57f24e7c 100644 --- a/crates/ra_assists/src/assists/raw_string.rs +++ b/crates/ra_assists/src/assists/raw_string.rs @@ -5,27 +5,12 @@ use rustc_lexer; 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 { - 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() -} - -pub(crate) fn make_raw_string_unescaped(mut ctx: AssistCtx) -> Option { let literal = ctx.node_at_offset::()?; if literal.token().kind() != ra_syntax::SyntaxKind::STRING { return None; } let token = literal.token(); let text = token.text().as_str(); - if !text.contains(&['\\', '\r'][..]) { - return None; - } let usual_string_range = find_usual_string_range(text)?; ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { edit.target(literal.syntax().text_range()); @@ -43,7 +28,7 @@ pub(crate) fn make_raw_string_unescaped(mut ctx: AssistCtx) -> if error.is_err() { eprintln!("Error unescaping string"); } else { - edit.replace(literal.syntax().text_range(), format!("r\"{}\"", unescaped)); + edit.replace(literal.syntax().text_range(), format!("r#\"{}\"#", unescaped)); } }); ctx.build() @@ -126,32 +111,15 @@ mod test { make_raw_string, r#" fn f() { - let s = <|>"random string"; + let s = <|>"random\nstring"; } "#, - r#""random string""#, + r#""random\nstring""#, ); } #[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_with_escaped_works() { check_assist( make_raw_string, r#" @@ -159,66 +127,29 @@ mod test { let s = <|>"random\nstring"; } "#, - r#" + r##" fn f() { - let s = <|>r"random\nstring"; + 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 make_raw_string_unescaped_target() { - check_assist_target( - make_raw_string_unescaped, - r#" - fn f() { - let s = <|>"random\nstring"; - } - "#, - r#""random\nstring""#, - ); - } - - #[test] - fn make_raw_string_unescaped_works() { + fn make_raw_string_nothing_to_unescape_works() { check_assist( - make_raw_string_unescaped, - r#" - fn f() { - let s = <|>"random\nstring"; - } - "#, + make_raw_string, r#" fn f() { - let s = <|>r"random -string"; + let s = <|>"random string"; } "#, - ) - } - - #[test] - fn make_raw_string_unescaped_dont_works() { - check_assist_not_applicable( - make_raw_string_unescaped, - r#" + r##" fn f() { - let s = <|>"random string"; + let s = <|>r#"random string"#; } - "#, + "##, ) } diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index d1e2d3251..3ca3320f7 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -132,7 +132,6 @@ mod assists { move_bounds::move_bounds_to_where_clause, raw_string::add_hash, raw_string::make_raw_string, - raw_string::make_raw_string_unescaped, raw_string::make_usual_string, raw_string::remove_hash, ] diff --git a/docs/user/features.md b/docs/user/features.md index fadd4cdf1..23842f409 100644 --- a/docs/user/features.md +++ b/docs/user/features.md @@ -445,20 +445,6 @@ fn foo T>() {} fn foo() where T: u32, F: FnOnce(T) -> T {} ``` -- Make raw string - -```rust -// before: -fn f() { - let s = <|>"abcd"; -} - -// after: -fn f() { - let s = <|>r"abcd"; -} -``` - - Make raw string unescaped ```rust @@ -469,8 +455,8 @@ fn f() { // after: fn f() { - let s = <|>r"ab -cd"; + let s = <|>r#"ab +cd"#; } ``` -- cgit v1.2.3 From b06c5fac14638d8a9e56b2a4cf7311a82e4a5e8c Mon Sep 17 00:00:00 2001 From: Geobert Quach Date: Mon, 30 Sep 2019 19:50:44 +0100 Subject: feat(assists): Be smart about hashes Add max_hashes_streak + 1 hashes to the raw string --- crates/ra_assists/src/assists/raw_string.rs | 37 ++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/assists/raw_string.rs index e57f24e7c..dd8db0b7d 100644 --- a/crates/ra_assists/src/assists/raw_string.rs +++ b/crates/ra_assists/src/assists/raw_string.rs @@ -28,7 +28,25 @@ pub(crate) fn make_raw_string(mut ctx: AssistCtx) -> Option max_hash_streak { + max_hash_streak = acc; + } + 0 + } + }); + let mut hashes = String::with_capacity(max_hash_streak + 1); + for _ in 0..hashes.capacity() { + hashes.push('#'); + } + edit.replace( + literal.syntax().text_range(), + format!("r{}\"{}\"{}", hashes, unescaped, hashes), + ); } }); ctx.build() @@ -136,6 +154,23 @@ string"#; ) } + #[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_nothing_to_unescape_works() { check_assist( -- cgit v1.2.3 From 6195096fb4af0158a2cd512e3c30b211366b36ab Mon Sep 17 00:00:00 2001 From: Geobert Quach Date: Tue, 1 Oct 2019 21:36:14 +0100 Subject: feat(assists): Even smarter with hashes Count `"#*` streak only, extract the counting in a function, unit test this function --- crates/ra_assists/src/assists/raw_string.rs | 56 ++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/assists/raw_string.rs index dd8db0b7d..b182687a4 100644 --- a/crates/ra_assists/src/assists/raw_string.rs +++ b/crates/ra_assists/src/assists/raw_string.rs @@ -28,17 +28,7 @@ pub(crate) fn make_raw_string(mut ctx: AssistCtx) -> Option max_hash_streak { - max_hash_streak = acc; - } - 0 - } - }); + let max_hash_streak = count_hashes(&unescaped); let mut hashes = String::with_capacity(max_hash_streak + 1); for _ in 0..hashes.capacity() { hashes.push('#'); @@ -52,6 +42,19 @@ pub(crate) fn make_raw_string(mut ctx: AssistCtx) -> Option usize { + let indexes: Vec<_> = s.match_indices("\"#").map(|(i, _)| i).collect(); + let mut max_hash_streak = 0usize; + for idx in indexes { + 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 +} + fn find_usual_string_range(s: &str) -> Option { Some(TextRange::from_to( TextUnit::from(s.find('"')? as u32), @@ -165,12 +168,31 @@ string"#; "###, r####" fn f() { - let s = <|>r###"#random## + 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( @@ -410,4 +432,14 @@ 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 From 31663c1368aedfdc52aaea7b54de0097c13cf889 Mon Sep 17 00:00:00 2001 From: Geobert Quach Date: Fri, 4 Oct 2019 18:32:14 +0100 Subject: feat(assists): Address some PR comments --- crates/ra_assists/src/assists/raw_string.rs | 49 ++++++++++++++--------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/assists/raw_string.rs index b182687a4..200aaa59a 100644 --- a/crates/ra_assists/src/assists/raw_string.rs +++ b/crates/ra_assists/src/assists/raw_string.rs @@ -12,40 +12,39 @@ pub(crate) fn make_raw_string(mut ctx: AssistCtx) -> Option unescaped.push(c), + Err(_) => error = Err(()), + }, + ); + if error.is_err() { + return None; + } ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { edit.target(literal.syntax().text_range()); - 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 mut unescaped = String::with_capacity(inside_str.len()); - let mut error = Ok(()); - rustc_lexer::unescape::unescape_str(inside_str, &mut |_, unescaped_char| { - match unescaped_char { - Ok(c) => unescaped.push(c), - Err(_) => error = Err(()), - } - }); - if error.is_err() { - eprintln!("Error unescaping string"); - } else { - let max_hash_streak = count_hashes(&unescaped); - let mut hashes = String::with_capacity(max_hash_streak + 1); - for _ in 0..hashes.capacity() { - hashes.push('#'); - } - edit.replace( - literal.syntax().text_range(), - format!("r{}\"{}\"{}", hashes, unescaped, hashes), - ); + let max_hash_streak = count_hashes(&unescaped); + let mut hashes = String::with_capacity(max_hash_streak + 1); + for _ in 0..hashes.capacity() { + hashes.push('#'); } + edit.replace( + literal.syntax().text_range(), + format!("r{}\"{}\"{}", hashes, unescaped, hashes), + ); }); ctx.build() } fn count_hashes(s: &str) -> usize { - let indexes: Vec<_> = s.match_indices("\"#").map(|(i, _)| i).collect(); let mut max_hash_streak = 0usize; - for idx in indexes { + 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 { -- cgit v1.2.3