//! Completes identifiers in format string literals. use ide_db::syntax_helpers::format_string::is_format_string; use itertools::Itertools; use syntax::{ast, AstToken, TextRange, TextSize}; use crate::{context::CompletionContext, CompletionItem, CompletionItemKind, Completions}; /// Complete identifiers in format strings. pub(crate) fn format_string( acc: &mut Completions, ctx: &CompletionContext<'_>, original: &ast::String, expanded: &ast::String, ) { if !is_format_string(expanded) { return; } let cursor = ctx.position.offset; let lit_start = ctx.original_token.text_range().start(); 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 source_range = TextRange::new(brace_offset, cursor); ctx.locals.iter().for_each(|(name, _)| { CompletionItem::new(CompletionItemKind::Binding, source_range, name.to_smol_str()) .add_to(acc, ctx.db); }) } #[cfg(test)] mod tests { use expect_test::{expect, Expect}; use crate::tests::{check_edit, completion_list_no_kw}; fn check(ra_fixture: &str, expect: Expect) { let actual = completion_list_no_kw(ra_fixture); expect.assert_eq(&actual); } #[test] fn works_when_wrapped() { check( r#" macro_rules! format_args { ($lit:literal $(tt:tt)*) => { 0 }, } macro_rules! print { ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); } fn main() { let foobar = 1; print!("f$0"); } "#, expect![[]], ); } #[test] fn no_completion_without_brace() { check( r#" macro_rules! format_args { ($lit:literal $(tt:tt)*) => { 0 }, } fn main() { let foobar = 1; format_args!("f$0"); } "#, expect![[]], ); } #[test] fn completes_locals() { check_edit( "foobar", r#" macro_rules! format_args { ($lit:literal $(tt:tt)*) => { 0 }, } fn main() { let foobar = 1; format_args!("{f$0"); } "#, r#" macro_rules! format_args { ($lit:literal $(tt:tt)*) => { 0 }, } fn main() { let foobar = 1; format_args!("{foobar"); } "#, ); check_edit( "foobar", r#" macro_rules! format_args { ($lit:literal $(tt:tt)*) => { 0 }, } fn main() { let foobar = 1; format_args!("{$0"); } "#, r#" macro_rules! format_args { ($lit:literal $(tt:tt)*) => { 0 }, } fn main() { let foobar = 1; format_args!("{foobar"); } "#, ); } }