Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions crates/ide-completion/src/completions/postfix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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) {
Expand Down
16 changes: 13 additions & 3 deletions crates/ide-completion/src/completions/postfix/format_like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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() {
Expand Down
36 changes: 15 additions & 21 deletions crates/ide-db/src/syntax_helpers/format_string_exprs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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 {
Expand Down