From 26790f02e3c46c0cbd883c9c9e48c1627db27df8 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 5 Dec 2025 23:15:58 +0800 Subject: [PATCH] Fix not complete `format!("{{{$0")` and underscore Example --- ```rust fn main() { let foobar = 1; format_args!("{{{f$0"); } ``` **Before this PR** No complete **After this PR** ```rust fn main() { let foobar = 1; format_args!("{{{foobar"); } ``` --- ```rust fn main() { let foo_bar = 1; format_args!("{foo_$0}"); } ``` **Before this PR** No complete **After this PR** ```rust fn main() { let foo_bar = 1; format_args!("{foo_bar}"); } ``` --- .../src/completions/format_string.rs | 94 +++++++++++++++++-- 1 file changed, 87 insertions(+), 7 deletions(-) diff --git a/crates/ide-completion/src/completions/format_string.rs b/crates/ide-completion/src/completions/format_string.rs index 5ae65b05bc42..eaacd8d1a8fe 100644 --- a/crates/ide-completion/src/completions/format_string.rs +++ b/crates/ide-completion/src/completions/format_string.rs @@ -22,13 +22,8 @@ pub(crate) fn format_string( let cursor_in_lit = cursor - lit_start; let prefix = &original.text()[..cursor_in_lit.into()]; - let braces = prefix.char_indices().rev().skip_while(|&(_, c)| c.is_alphanumeric()).next_tuple(); - let brace_offset = match braces { - // escaped brace - Some(((_, '{'), (_, '{'))) => return, - Some(((idx, '{'), _)) => lit_start + TextSize::from(idx as u32 + 1), - _ => return, - }; + let Some(brace_offset) = unescaped_brace(prefix) else { return }; + let brace_offset = lit_start + brace_offset + TextSize::of('{'); let source_range = TextRange::new(brace_offset, cursor); ctx.locals.iter().sorted_by_key(|&(k, _)| k.clone()).for_each(|(name, _)| { @@ -59,6 +54,15 @@ pub(crate) fn format_string( }); } +fn unescaped_brace(prefix: &str) -> Option { + let is_ident_char = |ch: char| ch.is_alphanumeric() || ch == '_'; + prefix + .trim_end_matches(is_ident_char) + .strip_suffix('{') + .filter(|it| it.chars().rev().take_while(|&ch| ch == '{').count() % 2 == 0) + .map(|s| TextSize::new(s.len() as u32)) +} + #[cfg(test)] mod tests { use expect_test::expect; @@ -96,6 +100,82 @@ fn main() { ); } + #[test] + fn no_completion_after_escaped() { + check_no_kw( + r#" +//- minicore: fmt +fn main() { + let foobar = 1; + format_args!("{{f$0"); +} +"#, + expect![[]], + ); + check_no_kw( + r#" +//- minicore: fmt +fn main() { + let foobar = 1; + format_args!("some text {{{{f$0"); +} +"#, + expect![[]], + ); + } + + #[test] + fn completes_unescaped_after_escaped() { + check_edit( + "foobar", + r#" +//- minicore: fmt +fn main() { + let foobar = 1; + format_args!("{{{f$0"); +} +"#, + r#" +fn main() { + let foobar = 1; + format_args!("{{{foobar"); +} +"#, + ); + check_edit( + "foobar", + r#" +//- minicore: fmt +fn main() { + let foobar = 1; + format_args!("{{{{{f$0"); +} +"#, + r#" +fn main() { + let foobar = 1; + format_args!("{{{{{foobar"); +} +"#, + ); + check_edit( + "foobar", + r#" +//- minicore: fmt +fn main() { + let foobar = 1; + format_args!("}}{f$0"); +} +"#, + r#" +fn main() { + let foobar = 1; + format_args!("}}{foobar"); +} +"#, + ); + } + #[test] fn completes_locals() { check_edit(