ide_completion/completions/
format_string.rs1use hir::{ModuleDef, ScopeDef};
4use ide_db::{SymbolKind, syntax_helpers::format_string::is_format_string};
5use itertools::Itertools;
6use syntax::{AstToken, TextRange, TextSize, ToSmolStr, ast};
7
8use crate::{CompletionItem, CompletionItemKind, Completions, context::CompletionContext};
9
10pub(crate) fn format_string(
12 acc: &mut Completions,
13 ctx: &CompletionContext<'_>,
14 original: &ast::String,
15 expanded: &ast::String,
16) {
17 if !is_format_string(expanded) {
18 return;
19 }
20 let cursor = ctx.position.offset;
21 let lit_start = ctx.original_token.text_range().start();
22 let cursor_in_lit = cursor - lit_start;
23
24 let prefix = &original.text()[..cursor_in_lit.into()];
25 let Some(brace_offset) = unescaped_brace(prefix) else { return };
26 let brace_offset = lit_start + brace_offset + TextSize::of('{');
27
28 let source_range = TextRange::new(brace_offset, cursor);
29 ctx.locals.iter().sorted_by_key(|&(k, _)| k.clone()).for_each(|(name, _)| {
30 CompletionItem::new(
31 CompletionItemKind::Binding,
32 source_range,
33 name.display_no_db(ctx.edition).to_smolstr(),
34 ctx.edition,
35 )
36 .add_to(acc, ctx.db);
37 });
38 ctx.scope.process_all_names(&mut |name, scope| {
39 if let ScopeDef::ModuleDef(module_def) = scope {
40 let symbol_kind = match module_def {
41 ModuleDef::Const(..) => SymbolKind::Const,
42 ModuleDef::Static(..) => SymbolKind::Static,
43 _ => return,
44 };
45
46 CompletionItem::new(
47 CompletionItemKind::SymbolKind(symbol_kind),
48 source_range,
49 name.display_no_db(ctx.edition).to_smolstr(),
50 ctx.edition,
51 )
52 .add_to(acc, ctx.db);
53 }
54 });
55}
56
57fn unescaped_brace(prefix: &str) -> Option<TextSize> {
58 let is_ident_char = |ch: char| ch.is_alphanumeric() || ch == '_';
59 prefix
60 .trim_end_matches(is_ident_char)
61 .strip_suffix('{')
62 .filter(|it| it.chars().rev().take_while(|&ch| ch == '{').count() % 2 == 0)
63 .map(|s| TextSize::new(s.len() as u32))
64}
65
66#[cfg(test)]
67mod tests {
68 use expect_test::expect;
69
70 use crate::tests::{check_edit, check_no_kw};
71
72 #[test]
73 fn works_when_wrapped() {
74 check_no_kw(
75 r#"
76//- minicore: fmt
77macro_rules! print {
78 ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
79}
80fn main() {
81 let foobar = 1;
82 print!("f$0");
83}
84"#,
85 expect![[]],
86 );
87 }
88
89 #[test]
90 fn no_completion_without_brace() {
91 check_no_kw(
92 r#"
93//- minicore: fmt
94fn main() {
95 let foobar = 1;
96 format_args!("f$0");
97}
98"#,
99 expect![[]],
100 );
101 }
102
103 #[test]
104 fn no_completion_after_escaped() {
105 check_no_kw(
106 r#"
107//- minicore: fmt
108fn main() {
109 let foobar = 1;
110 format_args!("{{f$0");
111}
112"#,
113 expect![[]],
114 );
115 check_no_kw(
116 r#"
117//- minicore: fmt
118fn main() {
119 let foobar = 1;
120 format_args!("some text {{{{f$0");
121}
122"#,
123 expect![[]],
124 );
125 }
126
127 #[test]
128 fn completes_unescaped_after_escaped() {
129 check_edit(
130 "foobar",
131 r#"
132//- minicore: fmt
133fn main() {
134 let foobar = 1;
135 format_args!("{{{f$0");
136}
137"#,
138 r#"
139fn main() {
140 let foobar = 1;
141 format_args!("{{{foobar");
142}
143"#,
144 );
145 check_edit(
146 "foobar",
147 r#"
148//- minicore: fmt
149fn main() {
150 let foobar = 1;
151 format_args!("{{{{{f$0");
152}
153"#,
154 r#"
155fn main() {
156 let foobar = 1;
157 format_args!("{{{{{foobar");
158}
159"#,
160 );
161 check_edit(
162 "foobar",
163 r#"
164//- minicore: fmt
165fn main() {
166 let foobar = 1;
167 format_args!("}}{f$0");
168}
169"#,
170 r#"
171fn main() {
172 let foobar = 1;
173 format_args!("}}{foobar");
174}
175"#,
176 );
177 }
178
179 #[test]
180 fn completes_locals() {
181 check_edit(
182 "foobar",
183 r#"
184//- minicore: fmt
185fn main() {
186 let foobar = 1;
187 format_args!("{f$0");
188}
189"#,
190 r#"
191fn main() {
192 let foobar = 1;
193 format_args!("{foobar");
194}
195"#,
196 );
197 check_edit(
198 "foobar",
199 r#"
200//- minicore: fmt
201fn main() {
202 let foobar = 1;
203 format_args!("{$0");
204}
205"#,
206 r#"
207fn main() {
208 let foobar = 1;
209 format_args!("{foobar");
210}
211"#,
212 );
213 }
214
215 #[test]
216 fn completes_constants() {
217 check_edit(
218 "FOOBAR",
219 r#"
220//- minicore: fmt
221fn main() {
222 const FOOBAR: usize = 42;
223 format_args!("{f$0");
224}
225"#,
226 r#"
227fn main() {
228 const FOOBAR: usize = 42;
229 format_args!("{FOOBAR");
230}
231"#,
232 );
233
234 check_edit(
235 "FOOBAR",
236 r#"
237//- minicore: fmt
238fn main() {
239 const FOOBAR: usize = 42;
240 format_args!("{$0");
241}
242"#,
243 r#"
244fn main() {
245 const FOOBAR: usize = 42;
246 format_args!("{FOOBAR");
247}
248"#,
249 );
250 }
251
252 #[test]
253 fn completes_static_constants() {
254 check_edit(
255 "FOOBAR",
256 r#"
257//- minicore: fmt
258fn main() {
259 static FOOBAR: usize = 42;
260 format_args!("{f$0");
261}
262"#,
263 r#"
264fn main() {
265 static FOOBAR: usize = 42;
266 format_args!("{FOOBAR");
267}
268"#,
269 );
270
271 check_edit(
272 "FOOBAR",
273 r#"
274//- minicore: fmt
275fn main() {
276 static FOOBAR: usize = 42;
277 format_args!("{$0");
278}
279"#,
280 r#"
281fn main() {
282 static FOOBAR: usize = 42;
283 format_args!("{FOOBAR");
284}
285"#,
286 );
287 }
288}