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 mut const_value = None;
41 let symbol_kind = match module_def {
42 ModuleDef::Const(c) => {
43 const_value = Some(c);
44 SymbolKind::Const
45 }
46 ModuleDef::Static(..) => SymbolKind::Static,
47 _ => return,
48 };
49
50 let mut builder = CompletionItem::new(
51 CompletionItemKind::SymbolKind(symbol_kind),
52 source_range,
53 name.display_no_db(ctx.edition).to_smolstr(),
54 ctx.edition,
55 );
56 builder.const_value(const_value, ctx.db, ctx.display_target);
57 builder.add_to(acc, ctx.db);
58 }
59 });
60}
61
62fn unescaped_brace(prefix: &str) -> Option<TextSize> {
63 let is_ident_char = |ch: char| ch.is_alphanumeric() || ch == '_';
64 prefix
65 .trim_end_matches(is_ident_char)
66 .strip_suffix('{')
67 .filter(|it| it.chars().rev().take_while(|&ch| ch == '{').count() % 2 == 0)
68 .map(|s| TextSize::new(s.len() as u32))
69}
70
71#[cfg(test)]
72mod tests {
73 use expect_test::expect;
74
75 use crate::tests::{check_edit, check_no_kw};
76
77 #[test]
78 fn works_when_wrapped() {
79 check_no_kw(
80 r#"
81//- minicore: fmt
82macro_rules! print {
83 ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
84}
85fn main() {
86 let foobar = 1;
87 print!("f$0");
88}
89"#,
90 expect![[]],
91 );
92 }
93
94 #[test]
95 fn no_completion_without_brace() {
96 check_no_kw(
97 r#"
98//- minicore: fmt
99fn main() {
100 let foobar = 1;
101 format_args!("f$0");
102}
103"#,
104 expect![[]],
105 );
106 }
107
108 #[test]
109 fn no_completion_after_escaped() {
110 check_no_kw(
111 r#"
112//- minicore: fmt
113fn main() {
114 let foobar = 1;
115 format_args!("{{f$0");
116}
117"#,
118 expect![[]],
119 );
120 check_no_kw(
121 r#"
122//- minicore: fmt
123fn main() {
124 let foobar = 1;
125 format_args!("some text {{{{f$0");
126}
127"#,
128 expect![[]],
129 );
130 }
131
132 #[test]
133 fn completes_unescaped_after_escaped() {
134 check_edit(
135 "foobar",
136 r#"
137//- minicore: fmt
138fn main() {
139 let foobar = 1;
140 format_args!("{{{f$0");
141}
142"#,
143 r#"
144fn main() {
145 let foobar = 1;
146 format_args!("{{{foobar");
147}
148"#,
149 );
150 check_edit(
151 "foobar",
152 r#"
153//- minicore: fmt
154fn main() {
155 let foobar = 1;
156 format_args!("{{{{{f$0");
157}
158"#,
159 r#"
160fn main() {
161 let foobar = 1;
162 format_args!("{{{{{foobar");
163}
164"#,
165 );
166 check_edit(
167 "foobar",
168 r#"
169//- minicore: fmt
170fn main() {
171 let foobar = 1;
172 format_args!("}}{f$0");
173}
174"#,
175 r#"
176fn main() {
177 let foobar = 1;
178 format_args!("}}{foobar");
179}
180"#,
181 );
182 }
183
184 #[test]
185 fn completes_locals() {
186 check_edit(
187 "foobar",
188 r#"
189//- minicore: fmt
190fn main() {
191 let foobar = 1;
192 format_args!("{f$0");
193}
194"#,
195 r#"
196fn main() {
197 let foobar = 1;
198 format_args!("{foobar");
199}
200"#,
201 );
202 check_edit(
203 "foobar",
204 r#"
205//- minicore: fmt
206fn main() {
207 let foobar = 1;
208 format_args!("{$0");
209}
210"#,
211 r#"
212fn main() {
213 let foobar = 1;
214 format_args!("{foobar");
215}
216"#,
217 );
218 }
219
220 #[test]
221 fn completes_constants() {
222 check_edit(
223 "FOOBAR",
224 r#"
225//- minicore: fmt
226fn main() {
227 const FOOBAR: usize = 42;
228 format_args!("{f$0");
229}
230"#,
231 r#"
232fn main() {
233 const FOOBAR: usize = 42;
234 format_args!("{FOOBAR");
235}
236"#,
237 );
238
239 check_edit(
240 "FOOBAR",
241 r#"
242//- minicore: fmt
243fn main() {
244 const FOOBAR: usize = 42;
245 format_args!("{$0");
246}
247"#,
248 r#"
249fn main() {
250 const FOOBAR: usize = 42;
251 format_args!("{FOOBAR");
252}
253"#,
254 );
255 }
256
257 #[test]
258 fn completes_static_constants() {
259 check_edit(
260 "FOOBAR",
261 r#"
262//- minicore: fmt
263fn main() {
264 static FOOBAR: usize = 42;
265 format_args!("{f$0");
266}
267"#,
268 r#"
269fn main() {
270 static FOOBAR: usize = 42;
271 format_args!("{FOOBAR");
272}
273"#,
274 );
275
276 check_edit(
277 "FOOBAR",
278 r#"
279//- minicore: fmt
280fn main() {
281 static FOOBAR: usize = 42;
282 format_args!("{$0");
283}
284"#,
285 r#"
286fn main() {
287 static FOOBAR: usize = 42;
288 format_args!("{FOOBAR");
289}
290"#,
291 );
292 }
293}