about summary refs log tree commit diff
diff options
context:
space:
mode:
authorDropDemBits <r3usrlnd@gmail.com>2024-03-07 16:55:09 -0500
committerDropDemBits <r3usrlnd@gmail.com>2024-03-07 16:55:09 -0500
commitbc381837e352068e49b90c9ce37d4fa6c7ffc1fa (patch)
tree48d0ad6a602e8cce0e1a0377a3650e8b1f6643e2
parent7dadc64d1ceff1a096f2ffbcc2849a221dfe3adb (diff)
downloadrust-bc381837e352068e49b90c9ce37d4fa6c7ffc1fa.tar.gz
rust-bc381837e352068e49b90c9ce37d4fa6c7ffc1fa.zip
fix: Preserve `$` and `\` in postfix format completions
`parse_format_exprs` doesn't escape these two anymore, so they have to be escaped as a separate step.
-rw-r--r--crates/ide-completion/src/completions/postfix.rs20
-rw-r--r--crates/ide-completion/src/completions/postfix/format_like.rs16
2 files changed, 26 insertions, 10 deletions
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index 72c0885e92f..361ad821f4a 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/crates/ide-completion/src/completions/postfix.rs
@@ -258,7 +258,7 @@ pub(crate) fn complete_postfix(
 }
 
 fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
-    let text = if receiver_is_ambiguous_float_literal {
+    let mut 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()
@@ -267,12 +267,18 @@ fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal:
     };
 
     // 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('$', "\\$")
+    // normal Rust expressions.
+    escape_snippet_bits(&mut text);
+    text
+}
+
+/// Escapes `\` and `$` so that they don't get interpreted 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 '$'.
+fn escape_snippet_bits(text: &mut String) {
+    stdx::replace(text, '\\', "\\\\");
+    stdx::replace(text, '$', "\\$");
 }
 
 fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {
diff --git a/crates/ide-completion/src/completions/postfix/format_like.rs b/crates/ide-completion/src/completions/postfix/format_like.rs
index cb242e4aa68..fd50fd4e8c5 100644
--- a/crates/ide-completion/src/completions/postfix/format_like.rs
+++ b/crates/ide-completion/src/completions/postfix/format_like.rs
@@ -17,13 +17,15 @@
 // image::https://user-images.githubusercontent.com/48062697/113020656-b560f500-917a-11eb-87de-02991f61beb8.gif[]
 
 use ide_db::{
-    syntax_helpers::format_string_exprs::{parse_format_exprs, with_placeholders},
+    syntax_helpers::format_string_exprs::{parse_format_exprs, with_placeholders, Arg},
     SnippetCap,
 };
 use syntax::{ast, AstToken};
 
 use crate::{
-    completions::postfix::build_postfix_snippet_builder, context::CompletionContext, Completions,
+    completions::postfix::{build_postfix_snippet_builder, escape_snippet_bits},
+    context::CompletionContext,
+    Completions,
 };
 
 /// Mapping ("postfix completion item" => "macro to use")
@@ -51,7 +53,15 @@ pub(crate) fn add_format_like_completions(
         None => return,
     };
 
-    if let Ok((out, exprs)) = parse_format_exprs(receiver_text.text()) {
+    if let Ok((mut out, mut exprs)) = parse_format_exprs(receiver_text.text()) {
+        // Escape any snippet bits in the out text and any of the exprs.
+        escape_snippet_bits(&mut out);
+        for arg in &mut exprs {
+            if let Arg::Ident(text) | Arg::Expr(text) = arg {
+                escape_snippet_bits(text)
+            }
+        }
+
         let exprs = with_placeholders(exprs);
         for (label, macro_name) in KINDS {
             let snippet = if exprs.is_empty() {