Skip to content

Commit 00a0125

Browse files
committed
Auto merge of #16782 - DropDemBits:format-string-exprs-comments, r=Veykril
fix: Preserve $ and \ in postfix format completions `parse_format_exprs` doesn't escape these two as of #16781, so they have to be escaped as a separate step.
2 parents 48cb059 + bc38183 commit 00a0125

File tree

3 files changed

+41
-31
lines changed

3 files changed

+41
-31
lines changed

crates/ide-completion/src/completions/postfix.rs

+13-7
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ pub(crate) fn complete_postfix(
258258
}
259259

260260
fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
261-
let text = if receiver_is_ambiguous_float_literal {
261+
let mut text = if receiver_is_ambiguous_float_literal {
262262
let text = receiver.syntax().text();
263263
let without_dot = ..text.len() - TextSize::of('.');
264264
text.slice(without_dot).to_string()
@@ -267,12 +267,18 @@ fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal:
267267
};
268268

269269
// The receiver texts should be interpreted as-is, as they are expected to be
270-
// normal Rust expressions. We escape '\' and '$' so they don't get treated as
271-
// snippet-specific constructs.
272-
//
273-
// Note that we don't need to escape the other characters that can be escaped,
274-
// because they wouldn't be treated as snippet-specific constructs without '$'.
275-
text.replace('\\', "\\\\").replace('$', "\\$")
270+
// normal Rust expressions.
271+
escape_snippet_bits(&mut text);
272+
text
273+
}
274+
275+
/// Escapes `\` and `$` so that they don't get interpreted as snippet-specific constructs.
276+
///
277+
/// Note that we don't need to escape the other characters that can be escaped,
278+
/// because they wouldn't be treated as snippet-specific constructs without '$'.
279+
fn escape_snippet_bits(text: &mut String) {
280+
stdx::replace(text, '\\', "\\\\");
281+
stdx::replace(text, '$', "\\$");
276282
}
277283

278284
fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {

crates/ide-completion/src/completions/postfix/format_like.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
// image::https://user-images.githubusercontent.com/48062697/113020656-b560f500-917a-11eb-87de-02991f61beb8.gif[]
1818

1919
use ide_db::{
20-
syntax_helpers::format_string_exprs::{parse_format_exprs, with_placeholders},
20+
syntax_helpers::format_string_exprs::{parse_format_exprs, with_placeholders, Arg},
2121
SnippetCap,
2222
};
2323
use syntax::{ast, AstToken};
2424

2525
use crate::{
26-
completions::postfix::build_postfix_snippet_builder, context::CompletionContext, Completions,
26+
completions::postfix::{build_postfix_snippet_builder, escape_snippet_bits},
27+
context::CompletionContext,
28+
Completions,
2729
};
2830

2931
/// Mapping ("postfix completion item" => "macro to use")
@@ -51,7 +53,15 @@ pub(crate) fn add_format_like_completions(
5153
None => return,
5254
};
5355

54-
if let Ok((out, exprs)) = parse_format_exprs(receiver_text.text()) {
56+
if let Ok((mut out, mut exprs)) = parse_format_exprs(receiver_text.text()) {
57+
// Escape any snippet bits in the out text and any of the exprs.
58+
escape_snippet_bits(&mut out);
59+
for arg in &mut exprs {
60+
if let Arg::Ident(text) | Arg::Expr(text) = arg {
61+
escape_snippet_bits(text)
62+
}
63+
}
64+
5565
let exprs = with_placeholders(exprs);
5666
for (label, macro_name) in KINDS {
5767
let snippet = if exprs.is_empty() {

crates/ide-db/src/syntax_helpers/format_string_exprs.rs

+15-21
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,12 @@ pub enum Arg {
1111
Expr(String),
1212
}
1313

14-
/**
15-
Add placeholders like `$1` and `$2` in place of [`Arg::Placeholder`],
16-
and unwraps the [`Arg::Ident`] and [`Arg::Expr`] enums.
17-
```rust
18-
# use ide_db::syntax_helpers::format_string_exprs::*;
19-
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()])
20-
```
21-
*/
22-
14+
/// Add placeholders like `$1` and `$2` in place of [`Arg::Placeholder`],
15+
/// and unwraps the [`Arg::Ident`] and [`Arg::Expr`] enums.
16+
/// ```rust
17+
/// # use ide_db::syntax_helpers::format_string_exprs::*;
18+
/// 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()])
19+
/// ```
2320
pub fn with_placeholders(args: Vec<Arg>) -> Vec<String> {
2421
let mut placeholder_id = 1;
2522
args.into_iter()
@@ -34,18 +31,15 @@ pub fn with_placeholders(args: Vec<Arg>) -> Vec<String> {
3431
.collect()
3532
}
3633

37-
/**
38-
Parser for a format-like string. It is more allowing in terms of string contents,
39-
as we expect variable placeholders to be filled with expressions.
40-
41-
Built for completions and assists, and escapes `\` and `$` in output.
42-
(See the comments on `get_receiver_text()` for detail.)
43-
Splits a format string that may contain expressions
44-
like
45-
```rust
46-
assert_eq!(parse("{ident} {} {expr + 42} ").unwrap(), ("{} {} {}", vec![Arg::Ident("ident"), Arg::Placeholder, Arg::Expr("expr + 42")]));
47-
```
48-
*/
34+
/// Parser for a format-like string. It is more allowing in terms of string contents,
35+
/// as we expect variable placeholders to be filled with expressions.
36+
///
37+
/// Splits a format string that may contain expressions
38+
/// like
39+
/// ```rust
40+
/// # use ide_db::syntax_helpers::format_string_exprs::*;
41+
/// assert_eq!(parse_format_exprs("{ident} {} {expr + 42} ").unwrap(), ("{ident} {} {} ".to_owned(), vec![Arg::Placeholder, Arg::Expr("expr + 42".to_owned())]));
42+
/// ```
4943
pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
5044
#[derive(Debug, Clone, Copy, PartialEq)]
5145
enum State {

0 commit comments

Comments
 (0)