From 491e6eaf9e030d44fd0fa5f4a831199dc3257745 Mon Sep 17 00:00:00 2001 From: Ryo Yoshida <low.ryoshida@gmail.com> Date: Mon, 27 Jun 2022 19:10:37 +0900 Subject: [PATCH 1/4] fix: escape ${receiver} when completing with custom snippets --- .../ide-completion/src/completions/postfix.rs | 76 +++++++++++++++---- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs index 5af44aa4b68ac..762d7cddb97c9 100644 --- a/crates/ide-completion/src/completions/postfix.rs +++ b/crates/ide-completion/src/completions/postfix.rs @@ -193,13 +193,21 @@ pub(crate) fn complete_postfix( } fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String { - if receiver_is_ambiguous_float_literal { + let text = if receiver_is_ambiguous_float_literal { let text = receiver.syntax().text(); let without_dot = ..text.len() - TextSize::of('.'); text.slice(without_dot).to_string() } else { receiver.to_string() - } + }; + + // The receiver texts should be interpreted as-is, as they are expected to be + // normal Rust expressions. We escape '\' and '$' so they don't get treated as + // snippet-specific constructs. + // + // Note that we don't need to escape the other characters that can be escaped, + // because they wouldn't be treated as snippet-specific constructs without '$'. + text.replace('\\', "\\\\").replace('$', "\\$") } fn include_references(initial_element: &ast::Expr) -> ast::Expr { @@ -494,19 +502,21 @@ fn main() { #[test] fn custom_postfix_completion() { + let config = CompletionConfig { + snippets: vec![Snippet::new( + &[], + &["break".into()], + &["ControlFlow::Break(${receiver})".into()], + "", + &["core::ops::ControlFlow".into()], + crate::SnippetScope::Expr, + ) + .unwrap()], + ..TEST_CONFIG + }; + check_edit_with_config( - CompletionConfig { - snippets: vec![Snippet::new( - &[], - &["break".into()], - &["ControlFlow::Break(${receiver})".into()], - "", - &["core::ops::ControlFlow".into()], - crate::SnippetScope::Expr, - ) - .unwrap()], - ..TEST_CONFIG - }, + config.clone(), "break", r#" //- minicore: try @@ -516,6 +526,44 @@ fn main() { 42.$0 } use core::ops::ControlFlow; fn main() { ControlFlow::Break(42) } +"#, + ); + + check_edit_with_config( + config.clone(), + "break", + r#" +//- minicore: try +fn main() { '\\'.$0 } +"#, + r#" +use core::ops::ControlFlow; + +fn main() { ControlFlow::Break('\\\\') } +"#, + ); + + check_edit_with_config( + config.clone(), + "break", + r#" +//- minicore: try +fn main() { + match true { + true => "${1:placeholder}", + false => "\$", + }.$0 +} +"#, + r#" +use core::ops::ControlFlow; + +fn main() { + ControlFlow::Break(match true { + true => "\${1:placeholder}", + false => "\\\$", + }) +} "#, ); } From 393a18b8ce4f8f4662174e51b3f3587a322f4ebe Mon Sep 17 00:00:00 2001 From: Ryo Yoshida <low.ryoshida@gmail.com> Date: Mon, 27 Jun 2022 23:16:09 +0900 Subject: [PATCH 2/4] fix: escape receiver texts in format string completion --- .../src/completions/postfix/format_like.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/ide-completion/src/completions/postfix/format_like.rs b/crates/ide-completion/src/completions/postfix/format_like.rs index b5ef87b8812c3..16f902489b57d 100644 --- a/crates/ide-completion/src/completions/postfix/format_like.rs +++ b/crates/ide-completion/src/completions/postfix/format_like.rs @@ -115,6 +115,7 @@ impl FormatStrParser { // "{MyStruct { val_a: 0, val_b: 1 }}". let mut inexpr_open_count = 0; + // We need to escape '\' and '$'. See the comments on `get_receiver_text()` for detail. let mut chars = self.input.chars().peekable(); while let Some(chr) = chars.next() { match (self.state, chr) { @@ -127,6 +128,9 @@ impl FormatStrParser { self.state = State::MaybeIncorrect; } (State::NotExpr, _) => { + if matches!(chr, '\\' | '$') { + self.output.push('\\'); + } self.output.push(chr); } (State::MaybeIncorrect, '}') => { @@ -150,6 +154,9 @@ impl FormatStrParser { self.state = State::NotExpr; } (State::MaybeExpr, _) => { + if matches!(chr, '\\' | '$') { + current_expr.push('\\'); + } current_expr.push(chr); self.state = State::Expr; } @@ -187,6 +194,9 @@ impl FormatStrParser { inexpr_open_count += 1; } (State::Expr, _) => { + if matches!(chr, '\\' | '$') { + current_expr.push('\\'); + } current_expr.push(chr); } (State::FormatOpts, '}') => { @@ -194,6 +204,9 @@ impl FormatStrParser { self.state = State::NotExpr; } (State::FormatOpts, _) => { + if matches!(chr, '\\' | '$') { + self.output.push('\\'); + } self.output.push(chr); } } @@ -241,8 +254,11 @@ mod tests { fn format_str_parser() { let test_vector = &[ ("no expressions", expect![["no expressions"]]), + (r"no expressions with \$0$1", expect![r"no expressions with \\\$0\$1"]), ("{expr} is {2 + 2}", expect![["{} is {}; expr, 2 + 2"]]), ("{expr:?}", expect![["{:?}; expr"]]), + ("{expr:1$}", expect![[r"{:1\$}; expr"]]), + ("{$0}", expect![[r"{}; \$0"]]), ("{malformed", expect![["-"]]), ("malformed}", expect![["-"]]), ("{{correct", expect![["{{correct"]]), From 80cc0ef1bc42a9ff00ca159e8c3710abdbfb6d08 Mon Sep 17 00:00:00 2001 From: Ryo Yoshida <low.ryoshida@gmail.com> Date: Mon, 27 Jun 2022 19:15:59 +0900 Subject: [PATCH 3/4] Fix typo --- crates/ide-completion/src/snippet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ide-completion/src/snippet.rs b/crates/ide-completion/src/snippet.rs index a6bb3d0648bb0..b0a1897526b0d 100644 --- a/crates/ide-completion/src/snippet.rs +++ b/crates/ide-completion/src/snippet.rs @@ -17,7 +17,7 @@ // "body": [ // "thread::spawn(move || {", // "\t$0", -// ")};", +// "});", // ], // "description": "Insert a thread::spawn call", // "requires": "std::thread", From cfc52adc65327190b5027de19a1a6d96dcf504ab Mon Sep 17 00:00:00 2001 From: Ryo Yoshida <low.ryoshida@gmail.com> Date: Wed, 20 Jul 2022 19:10:52 +0900 Subject: [PATCH 4/4] Add comments --- crates/ide-completion/src/completions/postfix.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs index 762d7cddb97c9..b09f4634c6514 100644 --- a/crates/ide-completion/src/completions/postfix.rs +++ b/crates/ide-completion/src/completions/postfix.rs @@ -529,6 +529,11 @@ fn main() { ControlFlow::Break(42) } "#, ); + // The receiver texts should be escaped, see comments in `get_receiver_text()` + // for detail. + // + // Note that the last argument is what *lsp clients would see* rather than + // what users would see. Unescaping happens thereafter. check_edit_with_config( config.clone(), "break",