Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/ide-completion/src/completions/postfix.rs20
-rw-r--r--crates/ide-completion/src/completions/postfix/format_like.rs16
-rw-r--r--crates/ide-db/src/syntax_helpers/format_string_exprs.rs36
3 files changed, 41 insertions, 31 deletions
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index 72c0885e92..361ad821f4 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 cb242e4aa6..fd50fd4e8c 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() {
diff --git a/crates/ide-db/src/syntax_helpers/format_string_exprs.rs b/crates/ide-db/src/syntax_helpers/format_string_exprs.rs
index 93750a93b3..8ab5a6ede3 100644
--- a/crates/ide-db/src/syntax_helpers/format_string_exprs.rs
+++ b/crates/ide-db/src/syntax_helpers/format_string_exprs.rs
@@ -11,15 +11,12 @@ pub enum Arg {
Expr(String),
}
-/**
- Add placeholders like `$1` and `$2` in place of [`Arg::Placeholder`],
- and unwraps the [`Arg::Ident`] and [`Arg::Expr`] enums.
- ```rust
- # use ide_db::syntax_helpers::format_string_exprs::*;
- assert_eq!(with_placeholders(vec![Arg::Ident("ident".to_owned()), Arg::Placeholder, Arg::Expr("expr + 2".to_owned())]), vec!["ident".to_owned(), "$1".to_owned(), "expr + 2".to_owned()])
- ```
-*/
-
+/// Add placeholders like `$1` and `$2` in place of [`Arg::Placeholder`],
+/// and unwraps the [`Arg::Ident`] and [`Arg::Expr`] enums.
+/// ```rust
+/// # use ide_db::syntax_helpers::format_string_exprs::*;
+/// assert_eq!(with_placeholders(vec![Arg::Ident("ident".to_owned()), Arg::Placeholder, Arg::Expr("expr + 2".to_owned())]), vec!["ident".to_owned(), "$1".to_owned(), "expr + 2".to_owned()])
+/// ```
pub fn with_placeholders(args: Vec<Arg>) -> Vec<String> {
let mut placeholder_id = 1;
args.into_iter()
@@ -34,18 +31,15 @@ pub fn with_placeholders(args: Vec<Arg>) -> Vec<String> {
.collect()
}
-/**
- Parser for a format-like string. It is more allowing in terms of string contents,
- as we expect variable placeholders to be filled with expressions.
-
- Built for completions and assists, and escapes `\` and `$` in output.
- (See the comments on `get_receiver_text()` for detail.)
- Splits a format string that may contain expressions
- like
- ```rust
- assert_eq!(parse("{ident} {} {expr + 42} ").unwrap(), ("{} {} {}", vec![Arg::Ident("ident"), Arg::Placeholder, Arg::Expr("expr + 42")]));
- ```
-*/
+/// Parser for a format-like string. It is more allowing in terms of string contents,
+/// as we expect variable placeholders to be filled with expressions.
+///
+/// Splits a format string that may contain expressions
+/// like
+/// ```rust
+/// # use ide_db::syntax_helpers::format_string_exprs::*;
+/// assert_eq!(parse_format_exprs("{ident} {} {expr + 42} ").unwrap(), ("{ident} {} {} ".to_owned(), vec![Arg::Placeholder, Arg::Expr("expr + 42".to_owned())]));
+/// ```
pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
#[derive(Debug, Clone, Copy, PartialEq)]
enum State {