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() {