aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/src/call_info.rs30
-rw-r--r--crates/ra_ide/src/completion.rs25
-rw-r--r--crates/ra_ide/src/completion/complete_attribute.rs587
-rw-r--r--crates/ra_ide/src/completion/complete_dot.rs6
-rw-r--r--crates/ra_ide/src/completion/complete_keyword.rs12
-rw-r--r--crates/ra_ide/src/completion/complete_macro_in_item_position.rs6
-rw-r--r--crates/ra_ide/src/completion/complete_pattern.rs2
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs39
-rw-r--r--crates/ra_ide/src/completion/complete_qualified_path.rs46
-rw-r--r--crates/ra_ide/src/completion/complete_snippet.rs26
-rw-r--r--crates/ra_ide/src/completion/complete_trait_impl.rs18
-rw-r--r--crates/ra_ide/src/completion/complete_unqualified_path.rs229
-rw-r--r--crates/ra_ide/src/completion/completion_config.rs35
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs31
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs38
-rw-r--r--crates/ra_ide/src/completion/presentation.rs415
-rw-r--r--crates/ra_ide/src/display/function_signature.rs69
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs2
-rw-r--r--crates/ra_ide/src/lib.rs4
-rw-r--r--crates/ra_ide/src/marks.rs2
-rw-r--r--crates/ra_ide/src/runnables.rs35
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html82
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs232
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs70
24 files changed, 1815 insertions, 226 deletions
diff --git a/crates/ra_ide/src/call_info.rs b/crates/ra_ide/src/call_info.rs
index f95b6baf3..5da254a6e 100644
--- a/crates/ra_ide/src/call_info.rs
+++ b/crates/ra_ide/src/call_info.rs
@@ -19,10 +19,24 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal
19 call_info_for_token(&sema, token) 19 call_info_for_token(&sema, token)
20} 20}
21 21
22pub(crate) fn call_info_for_token( 22#[derive(Debug)]
23 sema: &Semantics<RootDatabase>, 23pub(crate) struct ActiveParameter {
24 token: SyntaxToken, 24 /// FIXME: should be `Type` and `Name
25) -> Option<CallInfo> { 25 pub(crate) ty: String,
26 pub(crate) name: String,
27}
28
29impl ActiveParameter {
30 pub(crate) fn at(db: &RootDatabase, position: FilePosition) -> Option<Self> {
31 call_info(db, position)?.into_active_parameter()
32 }
33
34 pub(crate) fn at_token(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Option<Self> {
35 call_info_for_token(sema, token)?.into_active_parameter()
36 }
37}
38
39fn call_info_for_token(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Option<CallInfo> {
26 // Find the calling expression and it's NameRef 40 // Find the calling expression and it's NameRef
27 let calling_node = FnCallNode::with_node(&token.parent())?; 41 let calling_node = FnCallNode::with_node(&token.parent())?;
28 42
@@ -160,6 +174,14 @@ impl FnCallNode {
160} 174}
161 175
162impl CallInfo { 176impl CallInfo {
177 fn into_active_parameter(self) -> Option<ActiveParameter> {
178 let idx = self.active_parameter?;
179 let ty = self.signature.parameter_types.get(idx)?.clone();
180 let name = self.signature.parameter_names.get(idx)?.clone();
181 let res = ActiveParameter { ty, name };
182 Some(res)
183 }
184
163 fn with_fn(db: &RootDatabase, function: hir::Function) -> Self { 185 fn with_fn(db: &RootDatabase, function: hir::Function) -> Self {
164 let signature = FunctionSignature::from_hir(db, function); 186 let signature = FunctionSignature::from_hir(db, function);
165 187
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs
index 4a1a2a04a..4ca0fdf4f 100644
--- a/crates/ra_ide/src/completion.rs
+++ b/crates/ra_ide/src/completion.rs
@@ -1,9 +1,11 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3mod completion_config;
3mod completion_item; 4mod completion_item;
4mod completion_context; 5mod completion_context;
5mod presentation; 6mod presentation;
6 7
8mod complete_attribute;
7mod complete_dot; 9mod complete_dot;
8mod complete_record; 10mod complete_record;
9mod complete_pattern; 11mod complete_pattern;
@@ -28,27 +30,11 @@ use crate::{
28 FilePosition, 30 FilePosition,
29}; 31};
30 32
31pub use crate::completion::completion_item::{ 33pub use crate::completion::{
32 CompletionItem, CompletionItemKind, InsertTextFormat, 34 completion_config::CompletionConfig,
35 completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat},
33}; 36};
34 37
35#[derive(Clone, Debug, PartialEq, Eq)]
36pub struct CompletionConfig {
37 pub enable_postfix_completions: bool,
38 pub add_call_parenthesis: bool,
39 pub add_call_argument_snippets: bool,
40}
41
42impl Default for CompletionConfig {
43 fn default() -> Self {
44 CompletionConfig {
45 enable_postfix_completions: true,
46 add_call_parenthesis: true,
47 add_call_argument_snippets: true,
48 }
49 }
50}
51
52/// Main entry point for completion. We run completion as a two-phase process. 38/// Main entry point for completion. We run completion as a two-phase process.
53/// 39///
54/// First, we look at the position and collect a so-called `CompletionContext. 40/// First, we look at the position and collect a so-called `CompletionContext.
@@ -93,6 +79,7 @@ pub(crate) fn completions(
93 complete_postfix::complete_postfix(&mut acc, &ctx); 79 complete_postfix::complete_postfix(&mut acc, &ctx);
94 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); 80 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
95 complete_trait_impl::complete_trait_impl(&mut acc, &ctx); 81 complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
82 complete_attribute::complete_attribute(&mut acc, &ctx);
96 83
97 Some(acc) 84 Some(acc)
98} 85}
diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs
new file mode 100644
index 000000000..b405042e8
--- /dev/null
+++ b/crates/ra_ide/src/completion/complete_attribute.rs
@@ -0,0 +1,587 @@
1//! Completion for attributes
2//!
3//! This module uses a bit of static metadata to provide completions
4//! for built-in attributes.
5
6use super::completion_context::CompletionContext;
7use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions};
8use ra_syntax::{
9 ast::{Attr, AttrKind},
10 AstNode,
11};
12
13pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) {
14 if !ctx.is_attribute {
15 return;
16 }
17
18 let is_inner = ctx
19 .original_token
20 .ancestors()
21 .find_map(Attr::cast)
22 .map(|attr| attr.kind() == AttrKind::Inner)
23 .unwrap_or(false);
24
25 for attr_completion in ATTRIBUTES {
26 let mut item = CompletionItem::new(
27 CompletionKind::Attribute,
28 ctx.source_range(),
29 attr_completion.label,
30 )
31 .kind(CompletionItemKind::Attribute);
32
33 match (attr_completion.snippet, ctx.config.snippet_cap) {
34 (Some(snippet), Some(cap)) => {
35 item = item.insert_snippet(cap, snippet);
36 }
37 _ => {}
38 }
39
40 if is_inner || !attr_completion.should_be_inner {
41 acc.add(item);
42 }
43 }
44}
45
46struct AttrCompletion {
47 label: &'static str,
48 snippet: Option<&'static str>,
49 should_be_inner: bool,
50}
51
52const ATTRIBUTES: &[AttrCompletion] = &[
53 AttrCompletion { label: "allow", snippet: Some("allow(${0:lint})"), should_be_inner: false },
54 AttrCompletion {
55 label: "cfg_attr",
56 snippet: Some("cfg_attr(${1:predicate}, ${0:attr})"),
57 should_be_inner: false,
58 },
59 AttrCompletion { label: "cfg", snippet: Some("cfg(${0:predicate})"), should_be_inner: false },
60 AttrCompletion { label: "deny", snippet: Some("deny(${0:lint})"), should_be_inner: false },
61 AttrCompletion {
62 label: "deprecated",
63 snippet: Some(r#"deprecated = "${0:reason}""#),
64 should_be_inner: false,
65 },
66 AttrCompletion {
67 label: "derive",
68 snippet: Some(r#"derive(${0:Debug})"#),
69 should_be_inner: false,
70 },
71 AttrCompletion { label: "doc", snippet: Some(r#"doc = "${0:docs}""#), should_be_inner: false },
72 AttrCompletion { label: "feature", snippet: Some("feature(${0:flag})"), should_be_inner: true },
73 AttrCompletion { label: "forbid", snippet: Some("forbid(${0:lint})"), should_be_inner: false },
74 // FIXME: resolve through macro resolution?
75 AttrCompletion { label: "global_allocator", snippet: None, should_be_inner: true },
76 AttrCompletion { label: "ignore", snippet: Some("ignore(${0:lint})"), should_be_inner: false },
77 AttrCompletion { label: "inline", snippet: Some("inline(${0:lint})"), should_be_inner: false },
78 AttrCompletion {
79 label: "link_name",
80 snippet: Some(r#"link_name = "${0:symbol_name}""#),
81 should_be_inner: false,
82 },
83 AttrCompletion { label: "link", snippet: None, should_be_inner: false },
84 AttrCompletion { label: "macro_export", snippet: None, should_be_inner: false },
85 AttrCompletion { label: "macro_use", snippet: None, should_be_inner: false },
86 AttrCompletion {
87 label: "must_use",
88 snippet: Some(r#"must_use = "${0:reason}""#),
89 should_be_inner: false,
90 },
91 AttrCompletion { label: "no_mangle", snippet: None, should_be_inner: false },
92 AttrCompletion { label: "no_std", snippet: None, should_be_inner: true },
93 AttrCompletion { label: "non_exhaustive", snippet: None, should_be_inner: false },
94 AttrCompletion { label: "panic_handler", snippet: None, should_be_inner: true },
95 AttrCompletion { label: "path", snippet: Some("path =\"${0:path}\""), should_be_inner: false },
96 AttrCompletion { label: "proc_macro", snippet: None, should_be_inner: false },
97 AttrCompletion { label: "proc_macro_attribute", snippet: None, should_be_inner: false },
98 AttrCompletion {
99 label: "proc_macro_derive",
100 snippet: Some("proc_macro_derive(${0:Trait})"),
101 should_be_inner: false,
102 },
103 AttrCompletion {
104 label: "recursion_limit",
105 snippet: Some("recursion_limit = ${0:128}"),
106 should_be_inner: true,
107 },
108 AttrCompletion { label: "repr", snippet: Some("repr(${0:C})"), should_be_inner: false },
109 AttrCompletion {
110 label: "should_panic",
111 snippet: Some(r#"expected = "${0:reason}""#),
112 should_be_inner: false,
113 },
114 AttrCompletion {
115 label: "target_feature",
116 snippet: Some("target_feature = \"${0:feature}\""),
117 should_be_inner: false,
118 },
119 AttrCompletion { label: "test", snippet: None, should_be_inner: false },
120 AttrCompletion { label: "used", snippet: None, should_be_inner: false },
121 AttrCompletion { label: "warn", snippet: Some("warn(${0:lint})"), should_be_inner: false },
122 AttrCompletion {
123 label: "windows_subsystem",
124 snippet: Some(r#"windows_subsystem = "${0:subsystem}""#),
125 should_be_inner: true,
126 },
127];
128
129#[cfg(test)]
130mod tests {
131 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
132 use insta::assert_debug_snapshot;
133
134 fn do_attr_completion(code: &str) -> Vec<CompletionItem> {
135 do_completion(code, CompletionKind::Attribute)
136 }
137
138 #[test]
139 fn test_attribute_completion() {
140 assert_debug_snapshot!(
141 do_attr_completion(
142 r"
143 #[<|>]
144 ",
145 ),
146 @r###"
147 [
148 CompletionItem {
149 label: "allow",
150 source_range: [19; 19),
151 delete: [19; 19),
152 insert: "allow(${0:lint})",
153 kind: Attribute,
154 },
155 CompletionItem {
156 label: "cfg",
157 source_range: [19; 19),
158 delete: [19; 19),
159 insert: "cfg(${0:predicate})",
160 kind: Attribute,
161 },
162 CompletionItem {
163 label: "cfg_attr",
164 source_range: [19; 19),
165 delete: [19; 19),
166 insert: "cfg_attr(${1:predicate}, ${0:attr})",
167 kind: Attribute,
168 },
169 CompletionItem {
170 label: "deny",
171 source_range: [19; 19),
172 delete: [19; 19),
173 insert: "deny(${0:lint})",
174 kind: Attribute,
175 },
176 CompletionItem {
177 label: "deprecated",
178 source_range: [19; 19),
179 delete: [19; 19),
180 insert: "deprecated = \"${0:reason}\"",
181 kind: Attribute,
182 },
183 CompletionItem {
184 label: "derive",
185 source_range: [19; 19),
186 delete: [19; 19),
187 insert: "derive(${0:Debug})",
188 kind: Attribute,
189 },
190 CompletionItem {
191 label: "doc",
192 source_range: [19; 19),
193 delete: [19; 19),
194 insert: "doc = \"${0:docs}\"",
195 kind: Attribute,
196 },
197 CompletionItem {
198 label: "forbid",
199 source_range: [19; 19),
200 delete: [19; 19),
201 insert: "forbid(${0:lint})",
202 kind: Attribute,
203 },
204 CompletionItem {
205 label: "ignore",
206 source_range: [19; 19),
207 delete: [19; 19),
208 insert: "ignore(${0:lint})",
209 kind: Attribute,
210 },
211 CompletionItem {
212 label: "inline",
213 source_range: [19; 19),
214 delete: [19; 19),
215 insert: "inline(${0:lint})",
216 kind: Attribute,
217 },
218 CompletionItem {
219 label: "link",
220 source_range: [19; 19),
221 delete: [19; 19),
222 insert: "link",
223 kind: Attribute,
224 },
225 CompletionItem {
226 label: "link_name",
227 source_range: [19; 19),
228 delete: [19; 19),
229 insert: "link_name = \"${0:symbol_name}\"",
230 kind: Attribute,
231 },
232 CompletionItem {
233 label: "macro_export",
234 source_range: [19; 19),
235 delete: [19; 19),
236 insert: "macro_export",
237 kind: Attribute,
238 },
239 CompletionItem {
240 label: "macro_use",
241 source_range: [19; 19),
242 delete: [19; 19),
243 insert: "macro_use",
244 kind: Attribute,
245 },
246 CompletionItem {
247 label: "must_use",
248 source_range: [19; 19),
249 delete: [19; 19),
250 insert: "must_use = \"${0:reason}\"",
251 kind: Attribute,
252 },
253 CompletionItem {
254 label: "no_mangle",
255 source_range: [19; 19),
256 delete: [19; 19),
257 insert: "no_mangle",
258 kind: Attribute,
259 },
260 CompletionItem {
261 label: "non_exhaustive",
262 source_range: [19; 19),
263 delete: [19; 19),
264 insert: "non_exhaustive",
265 kind: Attribute,
266 },
267 CompletionItem {
268 label: "path",
269 source_range: [19; 19),
270 delete: [19; 19),
271 insert: "path =\"${0:path}\"",
272 kind: Attribute,
273 },
274 CompletionItem {
275 label: "proc_macro",
276 source_range: [19; 19),
277 delete: [19; 19),
278 insert: "proc_macro",
279 kind: Attribute,
280 },
281 CompletionItem {
282 label: "proc_macro_attribute",
283 source_range: [19; 19),
284 delete: [19; 19),
285 insert: "proc_macro_attribute",
286 kind: Attribute,
287 },
288 CompletionItem {
289 label: "proc_macro_derive",
290 source_range: [19; 19),
291 delete: [19; 19),
292 insert: "proc_macro_derive(${0:Trait})",
293 kind: Attribute,
294 },
295 CompletionItem {
296 label: "repr",
297 source_range: [19; 19),
298 delete: [19; 19),
299 insert: "repr(${0:C})",
300 kind: Attribute,
301 },
302 CompletionItem {
303 label: "should_panic",
304 source_range: [19; 19),
305 delete: [19; 19),
306 insert: "expected = \"${0:reason}\"",
307 kind: Attribute,
308 },
309 CompletionItem {
310 label: "target_feature",
311 source_range: [19; 19),
312 delete: [19; 19),
313 insert: "target_feature = \"${0:feature}\"",
314 kind: Attribute,
315 },
316 CompletionItem {
317 label: "test",
318 source_range: [19; 19),
319 delete: [19; 19),
320 insert: "test",
321 kind: Attribute,
322 },
323 CompletionItem {
324 label: "used",
325 source_range: [19; 19),
326 delete: [19; 19),
327 insert: "used",
328 kind: Attribute,
329 },
330 CompletionItem {
331 label: "warn",
332 source_range: [19; 19),
333 delete: [19; 19),
334 insert: "warn(${0:lint})",
335 kind: Attribute,
336 },
337 ]
338 "###
339 );
340 }
341
342 #[test]
343 fn test_inner_attribute_completion() {
344 assert_debug_snapshot!(
345 do_attr_completion(
346 r"
347 #![<|>]
348 ",
349 ),
350 @r###"
351 [
352 CompletionItem {
353 label: "allow",
354 source_range: [20; 20),
355 delete: [20; 20),
356 insert: "allow(${0:lint})",
357 kind: Attribute,
358 },
359 CompletionItem {
360 label: "cfg",
361 source_range: [20; 20),
362 delete: [20; 20),
363 insert: "cfg(${0:predicate})",
364 kind: Attribute,
365 },
366 CompletionItem {
367 label: "cfg_attr",
368 source_range: [20; 20),
369 delete: [20; 20),
370 insert: "cfg_attr(${1:predicate}, ${0:attr})",
371 kind: Attribute,
372 },
373 CompletionItem {
374 label: "deny",
375 source_range: [20; 20),
376 delete: [20; 20),
377 insert: "deny(${0:lint})",
378 kind: Attribute,
379 },
380 CompletionItem {
381 label: "deprecated",
382 source_range: [20; 20),
383 delete: [20; 20),
384 insert: "deprecated = \"${0:reason}\"",
385 kind: Attribute,
386 },
387 CompletionItem {
388 label: "derive",
389 source_range: [20; 20),
390 delete: [20; 20),
391 insert: "derive(${0:Debug})",
392 kind: Attribute,
393 },
394 CompletionItem {
395 label: "doc",
396 source_range: [20; 20),
397 delete: [20; 20),
398 insert: "doc = \"${0:docs}\"",
399 kind: Attribute,
400 },
401 CompletionItem {
402 label: "feature",
403 source_range: [20; 20),
404 delete: [20; 20),
405 insert: "feature(${0:flag})",
406 kind: Attribute,
407 },
408 CompletionItem {
409 label: "forbid",
410 source_range: [20; 20),
411 delete: [20; 20),
412 insert: "forbid(${0:lint})",
413 kind: Attribute,
414 },
415 CompletionItem {
416 label: "global_allocator",
417 source_range: [20; 20),
418 delete: [20; 20),
419 insert: "global_allocator",
420 kind: Attribute,
421 },
422 CompletionItem {
423 label: "ignore",
424 source_range: [20; 20),
425 delete: [20; 20),
426 insert: "ignore(${0:lint})",
427 kind: Attribute,
428 },
429 CompletionItem {
430 label: "inline",
431 source_range: [20; 20),
432 delete: [20; 20),
433 insert: "inline(${0:lint})",
434 kind: Attribute,
435 },
436 CompletionItem {
437 label: "link",
438 source_range: [20; 20),
439 delete: [20; 20),
440 insert: "link",
441 kind: Attribute,
442 },
443 CompletionItem {
444 label: "link_name",
445 source_range: [20; 20),
446 delete: [20; 20),
447 insert: "link_name = \"${0:symbol_name}\"",
448 kind: Attribute,
449 },
450 CompletionItem {
451 label: "macro_export",
452 source_range: [20; 20),
453 delete: [20; 20),
454 insert: "macro_export",
455 kind: Attribute,
456 },
457 CompletionItem {
458 label: "macro_use",
459 source_range: [20; 20),
460 delete: [20; 20),
461 insert: "macro_use",
462 kind: Attribute,
463 },
464 CompletionItem {
465 label: "must_use",
466 source_range: [20; 20),
467 delete: [20; 20),
468 insert: "must_use = \"${0:reason}\"",
469 kind: Attribute,
470 },
471 CompletionItem {
472 label: "no_mangle",
473 source_range: [20; 20),
474 delete: [20; 20),
475 insert: "no_mangle",
476 kind: Attribute,
477 },
478 CompletionItem {
479 label: "no_std",
480 source_range: [20; 20),
481 delete: [20; 20),
482 insert: "no_std",
483 kind: Attribute,
484 },
485 CompletionItem {
486 label: "non_exhaustive",
487 source_range: [20; 20),
488 delete: [20; 20),
489 insert: "non_exhaustive",
490 kind: Attribute,
491 },
492 CompletionItem {
493 label: "panic_handler",
494 source_range: [20; 20),
495 delete: [20; 20),
496 insert: "panic_handler",
497 kind: Attribute,
498 },
499 CompletionItem {
500 label: "path",
501 source_range: [20; 20),
502 delete: [20; 20),
503 insert: "path =\"${0:path}\"",
504 kind: Attribute,
505 },
506 CompletionItem {
507 label: "proc_macro",
508 source_range: [20; 20),
509 delete: [20; 20),
510 insert: "proc_macro",
511 kind: Attribute,
512 },
513 CompletionItem {
514 label: "proc_macro_attribute",
515 source_range: [20; 20),
516 delete: [20; 20),
517 insert: "proc_macro_attribute",
518 kind: Attribute,
519 },
520 CompletionItem {
521 label: "proc_macro_derive",
522 source_range: [20; 20),
523 delete: [20; 20),
524 insert: "proc_macro_derive(${0:Trait})",
525 kind: Attribute,
526 },
527 CompletionItem {
528 label: "recursion_limit",
529 source_range: [20; 20),
530 delete: [20; 20),
531 insert: "recursion_limit = ${0:128}",
532 kind: Attribute,
533 },
534 CompletionItem {
535 label: "repr",
536 source_range: [20; 20),
537 delete: [20; 20),
538 insert: "repr(${0:C})",
539 kind: Attribute,
540 },
541 CompletionItem {
542 label: "should_panic",
543 source_range: [20; 20),
544 delete: [20; 20),
545 insert: "expected = \"${0:reason}\"",
546 kind: Attribute,
547 },
548 CompletionItem {
549 label: "target_feature",
550 source_range: [20; 20),
551 delete: [20; 20),
552 insert: "target_feature = \"${0:feature}\"",
553 kind: Attribute,
554 },
555 CompletionItem {
556 label: "test",
557 source_range: [20; 20),
558 delete: [20; 20),
559 insert: "test",
560 kind: Attribute,
561 },
562 CompletionItem {
563 label: "used",
564 source_range: [20; 20),
565 delete: [20; 20),
566 insert: "used",
567 kind: Attribute,
568 },
569 CompletionItem {
570 label: "warn",
571 source_range: [20; 20),
572 delete: [20; 20),
573 insert: "warn(${0:lint})",
574 kind: Attribute,
575 },
576 CompletionItem {
577 label: "windows_subsystem",
578 source_range: [20; 20),
579 delete: [20; 20),
580 insert: "windows_subsystem = \"${0:subsystem}\"",
581 kind: Attribute,
582 },
583 ]
584 "###
585 );
586 }
587}
diff --git a/crates/ra_ide/src/completion/complete_dot.rs b/crates/ra_ide/src/completion/complete_dot.rs
index f433faef3..b93153b48 100644
--- a/crates/ra_ide/src/completion/complete_dot.rs
+++ b/crates/ra_ide/src/completion/complete_dot.rs
@@ -2,9 +2,11 @@
2 2
3use hir::{HasVisibility, Type}; 3use hir::{HasVisibility, Type};
4 4
5use crate::completion::completion_item::CompletionKind;
6use crate::{ 5use crate::{
7 completion::{completion_context::CompletionContext, completion_item::Completions}, 6 completion::{
7 completion_context::CompletionContext,
8 completion_item::{CompletionKind, Completions},
9 },
8 CompletionItem, 10 CompletionItem,
9}; 11};
10use rustc_hash::FxHashSet; 12use rustc_hash::FxHashSet;
diff --git a/crates/ra_ide/src/completion/complete_keyword.rs b/crates/ra_ide/src/completion/complete_keyword.rs
index 38f9c34e7..adefb290e 100644
--- a/crates/ra_ide/src/completion/complete_keyword.rs
+++ b/crates/ra_ide/src/completion/complete_keyword.rs
@@ -42,10 +42,14 @@ pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC
42} 42}
43 43
44fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { 44fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem {
45 CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw) 45 let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw)
46 .kind(CompletionItemKind::Keyword) 46 .kind(CompletionItemKind::Keyword);
47 .insert_snippet(snippet) 47
48 .build() 48 match ctx.config.snippet_cap {
49 Some(cap) => res.insert_snippet(cap, snippet),
50 _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }),
51 }
52 .build()
49} 53}
50 54
51pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { 55pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
diff --git a/crates/ra_ide/src/completion/complete_macro_in_item_position.rs b/crates/ra_ide/src/completion/complete_macro_in_item_position.rs
index 270e96df0..6000106d0 100644
--- a/crates/ra_ide/src/completion/complete_macro_in_item_position.rs
+++ b/crates/ra_ide/src/completion/complete_macro_in_item_position.rs
@@ -41,7 +41,7 @@ mod tests {
41 @r###" 41 @r###"
42 [ 42 [
43 CompletionItem { 43 CompletionItem {
44 label: "foo!", 44 label: "foo!(…)",
45 source_range: [46; 46), 45 source_range: [46; 46),
46 delete: [46; 46), 46 delete: [46; 46),
47 insert: "foo!($0)", 47 insert: "foo!($0)",
@@ -81,7 +81,7 @@ mod tests {
81 @r###" 81 @r###"
82 [ 82 [
83 CompletionItem { 83 CompletionItem {
84 label: "vec!", 84 label: "vec![…]",
85 source_range: [280; 280), 85 source_range: [280; 280),
86 delete: [280; 280), 86 delete: [280; 280),
87 insert: "vec![$0]", 87 insert: "vec![$0]",
@@ -118,7 +118,7 @@ mod tests {
118 @r###" 118 @r###"
119 [ 119 [
120 CompletionItem { 120 CompletionItem {
121 label: "foo!", 121 label: "foo! {…}",
122 source_range: [163; 163), 122 source_range: [163; 163),
123 delete: [163; 163), 123 delete: [163; 163),
124 insert: "foo! {$0}", 124 insert: "foo! {$0}",
diff --git a/crates/ra_ide/src/completion/complete_pattern.rs b/crates/ra_ide/src/completion/complete_pattern.rs
index a8b4ce114..218829b10 100644
--- a/crates/ra_ide/src/completion/complete_pattern.rs
+++ b/crates/ra_ide/src/completion/complete_pattern.rs
@@ -125,7 +125,7 @@ mod tests {
125 kind: Enum, 125 kind: Enum,
126 }, 126 },
127 CompletionItem { 127 CompletionItem {
128 label: "m!", 128 label: "m!(…)",
129 source_range: [151; 151), 129 source_range: [151; 151),
130 delete: [151; 151), 130 delete: [151; 151),
131 insert: "m!($0)", 131 insert: "m!($0)",
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs
index 29c2881c6..8d397b0fe 100644
--- a/crates/ra_ide/src/completion/complete_postfix.rs
+++ b/crates/ra_ide/src/completion/complete_postfix.rs
@@ -6,6 +6,7 @@ use ra_syntax::{
6}; 6};
7use ra_text_edit::TextEdit; 7use ra_text_edit::TextEdit;
8 8
9use super::completion_config::SnippetCap;
9use crate::{ 10use crate::{
10 completion::{ 11 completion::{
11 completion_context::CompletionContext, 12 completion_context::CompletionContext,
@@ -32,9 +33,15 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
32 None => return, 33 None => return,
33 }; 34 };
34 35
36 let cap = match ctx.config.snippet_cap {
37 Some(it) => it,
38 None => return,
39 };
40
35 if receiver_ty.is_bool() || receiver_ty.is_unknown() { 41 if receiver_ty.is_bool() || receiver_ty.is_unknown() {
36 postfix_snippet( 42 postfix_snippet(
37 ctx, 43 ctx,
44 cap,
38 &dot_receiver, 45 &dot_receiver,
39 "if", 46 "if",
40 "if expr {}", 47 "if expr {}",
@@ -43,6 +50,7 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
43 .add_to(acc); 50 .add_to(acc);
44 postfix_snippet( 51 postfix_snippet(
45 ctx, 52 ctx,
53 cap,
46 &dot_receiver, 54 &dot_receiver,
47 "while", 55 "while",
48 "while expr {}", 56 "while expr {}",
@@ -52,11 +60,20 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
52 } 60 }
53 61
54 // !&&&42 is a compiler error, ergo process it before considering the references 62 // !&&&42 is a compiler error, ergo process it before considering the references
55 postfix_snippet(ctx, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc); 63 postfix_snippet(ctx, cap, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text))
64 .add_to(acc);
56 65
57 postfix_snippet(ctx, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc); 66 postfix_snippet(ctx, cap, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text))
58 postfix_snippet(ctx, &dot_receiver, "refm", "&mut expr", &format!("&mut {}", receiver_text))
59 .add_to(acc); 67 .add_to(acc);
68 postfix_snippet(
69 ctx,
70 cap,
71 &dot_receiver,
72 "refm",
73 "&mut expr",
74 &format!("&mut {}", receiver_text),
75 )
76 .add_to(acc);
60 77
61 // The rest of the postfix completions create an expression that moves an argument, 78 // The rest of the postfix completions create an expression that moves an argument,
62 // so it's better to consider references now to avoid breaking the compilation 79 // so it's better to consider references now to avoid breaking the compilation
@@ -66,6 +83,7 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
66 83
67 postfix_snippet( 84 postfix_snippet(
68 ctx, 85 ctx,
86 cap,
69 &dot_receiver, 87 &dot_receiver,
70 "match", 88 "match",
71 "match expr {}", 89 "match expr {}",
@@ -75,6 +93,7 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
75 93
76 postfix_snippet( 94 postfix_snippet(
77 ctx, 95 ctx,
96 cap,
78 &dot_receiver, 97 &dot_receiver,
79 "box", 98 "box",
80 "Box::new(expr)", 99 "Box::new(expr)",
@@ -82,8 +101,15 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
82 ) 101 )
83 .add_to(acc); 102 .add_to(acc);
84 103
85 postfix_snippet(ctx, &dot_receiver, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)) 104 postfix_snippet(
86 .add_to(acc); 105 ctx,
106 cap,
107 &dot_receiver,
108 "dbg",
109 "dbg!(expr)",
110 &format!("dbg!({})", receiver_text),
111 )
112 .add_to(acc);
87} 113}
88 114
89fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String { 115fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
@@ -108,6 +134,7 @@ fn include_references(initial_element: &ast::Expr) -> ast::Expr {
108 134
109fn postfix_snippet( 135fn postfix_snippet(
110 ctx: &CompletionContext, 136 ctx: &CompletionContext,
137 cap: SnippetCap,
111 receiver: &ast::Expr, 138 receiver: &ast::Expr,
112 label: &str, 139 label: &str,
113 detail: &str, 140 detail: &str,
@@ -121,7 +148,7 @@ fn postfix_snippet(
121 }; 148 };
122 CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label) 149 CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label)
123 .detail(detail) 150 .detail(detail)
124 .snippet_edit(edit) 151 .snippet_edit(cap, edit)
125} 152}
126 153
127#[cfg(test)] 154#[cfg(test)]
diff --git a/crates/ra_ide/src/completion/complete_qualified_path.rs b/crates/ra_ide/src/completion/complete_qualified_path.rs
index d98523406..5a5139e14 100644
--- a/crates/ra_ide/src/completion/complete_qualified_path.rs
+++ b/crates/ra_ide/src/completion/complete_qualified_path.rs
@@ -57,9 +57,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
57 } 57 }
58 match item { 58 match item {
59 hir::AssocItem::Function(func) => { 59 hir::AssocItem::Function(func) => {
60 if !func.has_self_param(ctx.db) { 60 acc.add_function(ctx, func, None);
61 acc.add_function(ctx, func, None);
62 }
63 } 61 }
64 hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), 62 hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
65 hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), 63 hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
@@ -86,9 +84,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
86 } 84 }
87 match item { 85 match item {
88 hir::AssocItem::Function(func) => { 86 hir::AssocItem::Function(func) => {
89 if !func.has_self_param(ctx.db) { 87 acc.add_function(ctx, func, None);
90 acc.add_function(ctx, func, None);
91 }
92 } 88 }
93 hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), 89 hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
94 hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), 90 hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
@@ -483,6 +479,42 @@ mod tests {
483 } 479 }
484 480
485 #[test] 481 #[test]
482 fn completes_struct_associated_method_with_self() {
483 assert_debug_snapshot!(
484 do_reference_completion(
485 "
486 //- /lib.rs
487 /// A Struct
488 struct S;
489
490 impl S {
491 /// An associated method
492 fn m(&self) { }
493 }
494
495 fn foo() { let _ = S::<|> }
496 "
497 ),
498 @r###"
499 [
500 CompletionItem {
501 label: "m()",
502 source_range: [105; 105),
503 delete: [105; 105),
504 insert: "m()$0",
505 kind: Method,
506 lookup: "m",
507 detail: "fn m(&self)",
508 documentation: Documentation(
509 "An associated method",
510 ),
511 },
512 ]
513 "###
514 );
515 }
516
517 #[test]
486 fn completes_struct_associated_const() { 518 fn completes_struct_associated_const() {
487 assert_debug_snapshot!( 519 assert_debug_snapshot!(
488 do_reference_completion( 520 do_reference_completion(
@@ -869,7 +901,7 @@ mod tests {
869 @r###" 901 @r###"
870 [ 902 [
871 CompletionItem { 903 CompletionItem {
872 label: "foo!", 904 label: "foo!(…)",
873 source_range: [179; 179), 905 source_range: [179; 179),
874 delete: [179; 179), 906 delete: [179; 179),
875 insert: "foo!($0)", 907 insert: "foo!($0)",
diff --git a/crates/ra_ide/src/completion/complete_snippet.rs b/crates/ra_ide/src/completion/complete_snippet.rs
index f731e9b9a..4bccfbfed 100644
--- a/crates/ra_ide/src/completion/complete_snippet.rs
+++ b/crates/ra_ide/src/completion/complete_snippet.rs
@@ -1,13 +1,13 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use crate::completion::{ 3use crate::completion::{
4 completion_item::Builder, CompletionContext, CompletionItem, CompletionItemKind, 4 completion_config::SnippetCap, completion_item::Builder, CompletionContext, CompletionItem,
5 CompletionKind, Completions, 5 CompletionItemKind, CompletionKind, Completions,
6}; 6};
7 7
8fn snippet(ctx: &CompletionContext, label: &str, snippet: &str) -> Builder { 8fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) -> Builder {
9 CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), label) 9 CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), label)
10 .insert_snippet(snippet) 10 .insert_snippet(cap, snippet)
11 .kind(CompletionItemKind::Snippet) 11 .kind(CompletionItemKind::Snippet)
12} 12}
13 13
@@ -15,17 +15,27 @@ pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionConte
15 if !(ctx.is_trivial_path && ctx.function_syntax.is_some()) { 15 if !(ctx.is_trivial_path && ctx.function_syntax.is_some()) {
16 return; 16 return;
17 } 17 }
18 let cap = match ctx.config.snippet_cap {
19 Some(it) => it,
20 None => return,
21 };
18 22
19 snippet(ctx, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc); 23 snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
20 snippet(ctx, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc); 24 snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
21} 25}
22 26
23pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) { 27pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
24 if !ctx.is_new_item { 28 if !ctx.is_new_item {
25 return; 29 return;
26 } 30 }
31 let cap = match ctx.config.snippet_cap {
32 Some(it) => it,
33 None => return,
34 };
35
27 snippet( 36 snippet(
28 ctx, 37 ctx,
38 cap,
29 "Test function", 39 "Test function",
30 "\ 40 "\
31#[test] 41#[test]
@@ -36,8 +46,8 @@ fn ${1:feature}() {
36 .lookup_by("tfn") 46 .lookup_by("tfn")
37 .add_to(acc); 47 .add_to(acc);
38 48
39 snippet(ctx, "macro_rules", "macro_rules! $1 {\n\t($2) => {\n\t\t$0\n\t};\n}").add_to(acc); 49 snippet(ctx, cap, "macro_rules", "macro_rules! $1 {\n\t($2) => {\n\t\t$0\n\t};\n}").add_to(acc);
40 snippet(ctx, "pub(crate)", "pub(crate) $0").add_to(acc); 50 snippet(ctx, cap, "pub(crate)", "pub(crate) $0").add_to(acc);
41} 51}
42 52
43#[cfg(test)] 53#[cfg(test)]
diff --git a/crates/ra_ide/src/completion/complete_trait_impl.rs b/crates/ra_ide/src/completion/complete_trait_impl.rs
index 2ec0e7ce9..c39943252 100644
--- a/crates/ra_ide/src/completion/complete_trait_impl.rs
+++ b/crates/ra_ide/src/completion/complete_trait_impl.rs
@@ -122,7 +122,7 @@ fn add_function_impl(
122 ctx: &CompletionContext, 122 ctx: &CompletionContext,
123 func: &hir::Function, 123 func: &hir::Function,
124) { 124) {
125 let display = FunctionSignature::from_hir(ctx.db, *func); 125 let signature = FunctionSignature::from_hir(ctx.db, *func);
126 126
127 let fn_name = func.name(ctx.db).to_string(); 127 let fn_name = func.name(ctx.db).to_string();
128 128
@@ -141,12 +141,20 @@ fn add_function_impl(
141 } else { 141 } else {
142 CompletionItemKind::Function 142 CompletionItemKind::Function
143 }; 143 };
144
145 let snippet = format!("{} {{\n $0\n}}", display);
146
147 let range = TextRange::from_to(fn_def_node.text_range().start(), ctx.source_range().end()); 144 let range = TextRange::from_to(fn_def_node.text_range().start(), ctx.source_range().end());
148 145
149 builder.snippet_edit(TextEdit::replace(range, snippet)).kind(completion_kind).add_to(acc); 146 match ctx.config.snippet_cap {
147 Some(cap) => {
148 let snippet = format!("{} {{\n $0\n}}", signature);
149 builder.snippet_edit(cap, TextEdit::replace(range, snippet))
150 }
151 None => {
152 let header = format!("{} {{", signature);
153 builder.text_edit(TextEdit::replace(range, header))
154 }
155 }
156 .kind(completion_kind)
157 .add_to(acc);
150} 158}
151 159
152fn add_type_alias_impl( 160fn add_type_alias_impl(
diff --git a/crates/ra_ide/src/completion/complete_unqualified_path.rs b/crates/ra_ide/src/completion/complete_unqualified_path.rs
index ad5fdcc4e..638f86eda 100644
--- a/crates/ra_ide/src/completion/complete_unqualified_path.rs
+++ b/crates/ra_ide/src/completion/complete_unqualified_path.rs
@@ -4,20 +4,23 @@ use hir::ScopeDef;
4use test_utils::tested_by; 4use test_utils::tested_by;
5 5
6use crate::completion::{CompletionContext, Completions}; 6use crate::completion::{CompletionContext, Completions};
7use hir::{Adt, ModuleDef};
7use ra_syntax::AstNode; 8use ra_syntax::AstNode;
8 9
9pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { 10pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
10 if !ctx.is_trivial_path { 11 if (!ctx.is_trivial_path && !ctx.is_pat_binding_or_const)
11 return;
12 }
13
14 if ctx.is_pat_binding_or_const
15 || ctx.record_lit_syntax.is_some() 12 || ctx.record_lit_syntax.is_some()
16 || ctx.record_pat_syntax.is_some() 13 || ctx.record_pat_syntax.is_some()
17 { 14 {
18 return; 15 return;
19 } 16 }
20 17
18 complete_enum_variants(acc, ctx);
19
20 if ctx.is_pat_binding_or_const {
21 return;
22 }
23
21 ctx.scope().process_all_names(&mut |name, res| { 24 ctx.scope().process_all_names(&mut |name, res| {
22 if ctx.use_item_syntax.is_some() { 25 if ctx.use_item_syntax.is_some() {
23 if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) { 26 if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) {
@@ -31,6 +34,24 @@ pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
31 }); 34 });
32} 35}
33 36
37fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext) {
38 if let Some(ty) = ctx.expected_type_of(&ctx.token.parent()) {
39 if let Some(Adt::Enum(enum_data)) = ty.as_adt() {
40 let variants = enum_data.variants(ctx.db);
41 let module = enum_data.module(ctx.db);
42 for variant in variants {
43 if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) {
44 // Variants with trivial paths are already added by the existing completion logic,
45 // so we should avoid adding these twice
46 if path.segments.len() > 1 {
47 acc.add_enum_variant(ctx, variant, Some(path.to_string()));
48 }
49 }
50 }
51 }
52 }
53}
54
34#[cfg(test)] 55#[cfg(test)]
35mod tests { 56mod tests {
36 use insta::assert_debug_snapshot; 57 use insta::assert_debug_snapshot;
@@ -82,7 +103,7 @@ mod tests {
82 } 103 }
83 " 104 "
84 ), 105 ),
85 @r###"[]"### 106 @"[]"
86 ); 107 );
87 } 108 }
88 109
@@ -712,7 +733,7 @@ mod tests {
712 @r###" 733 @r###"
713 [ 734 [
714 CompletionItem { 735 CompletionItem {
715 label: "bar!", 736 label: "bar!(…)",
716 source_range: [252; 252), 737 source_range: [252; 252),
717 delete: [252; 252), 738 delete: [252; 252),
718 insert: "bar!($0)", 739 insert: "bar!($0)",
@@ -720,7 +741,7 @@ mod tests {
720 detail: "macro_rules! bar", 741 detail: "macro_rules! bar",
721 }, 742 },
722 CompletionItem { 743 CompletionItem {
723 label: "baz!", 744 label: "baz!(…)",
724 source_range: [252; 252), 745 source_range: [252; 252),
725 delete: [252; 252), 746 delete: [252; 252),
726 insert: "baz!($0)", 747 insert: "baz!($0)",
@@ -728,7 +749,7 @@ mod tests {
728 detail: "#[macro_export]\nmacro_rules! baz", 749 detail: "#[macro_export]\nmacro_rules! baz",
729 }, 750 },
730 CompletionItem { 751 CompletionItem {
731 label: "foo!", 752 label: "foo!(…)",
732 source_range: [252; 252), 753 source_range: [252; 252),
733 delete: [252; 252), 754 delete: [252; 252),
734 insert: "foo!($0)", 755 insert: "foo!($0)",
@@ -781,7 +802,7 @@ mod tests {
781 @r###" 802 @r###"
782 [ 803 [
783 CompletionItem { 804 CompletionItem {
784 label: "foo!", 805 label: "foo!(…)",
785 source_range: [49; 49), 806 source_range: [49; 49),
786 delete: [49; 49), 807 delete: [49; 49),
787 insert: "foo!($0)", 808 insert: "foo!($0)",
@@ -820,7 +841,7 @@ mod tests {
820 @r###" 841 @r###"
821 [ 842 [
822 CompletionItem { 843 CompletionItem {
823 label: "foo!", 844 label: "foo!(…)",
824 source_range: [57; 57), 845 source_range: [57; 57),
825 delete: [57; 57), 846 delete: [57; 57),
826 insert: "foo!($0)", 847 insert: "foo!($0)",
@@ -859,7 +880,7 @@ mod tests {
859 @r###" 880 @r###"
860 [ 881 [
861 CompletionItem { 882 CompletionItem {
862 label: "foo!", 883 label: "foo!(…)",
863 source_range: [50; 50), 884 source_range: [50; 50),
864 delete: [50; 50), 885 delete: [50; 50),
865 insert: "foo!($0)", 886 insert: "foo!($0)",
@@ -932,7 +953,7 @@ mod tests {
932 @r###" 953 @r###"
933 [ 954 [
934 CompletionItem { 955 CompletionItem {
935 label: "m!", 956 label: "m!(…)",
936 source_range: [145; 145), 957 source_range: [145; 145),
937 delete: [145; 145), 958 delete: [145; 145),
938 insert: "m!($0)", 959 insert: "m!($0)",
@@ -985,7 +1006,7 @@ mod tests {
985 @r###" 1006 @r###"
986 [ 1007 [
987 CompletionItem { 1008 CompletionItem {
988 label: "m!", 1009 label: "m!(…)",
989 source_range: [145; 146), 1010 source_range: [145; 146),
990 delete: [145; 146), 1011 delete: [145; 146),
991 insert: "m!($0)", 1012 insert: "m!($0)",
@@ -1038,7 +1059,7 @@ mod tests {
1038 @r###" 1059 @r###"
1039 [ 1060 [
1040 CompletionItem { 1061 CompletionItem {
1041 label: "m!", 1062 label: "m!(…)",
1042 source_range: [145; 146), 1063 source_range: [145; 146),
1043 delete: [145; 146), 1064 delete: [145; 146),
1044 insert: "m!($0)", 1065 insert: "m!($0)",
@@ -1109,4 +1130,182 @@ mod tests {
1109 "### 1130 "###
1110 ); 1131 );
1111 } 1132 }
1133 #[test]
1134 fn completes_enum_variant_matcharm() {
1135 assert_debug_snapshot!(
1136 do_reference_completion(
1137 r"
1138 enum Foo {
1139 Bar,
1140 Baz,
1141 Quux
1142 }
1143
1144 fn main() {
1145 let foo = Foo::Quux;
1146
1147 match foo {
1148 Qu<|>
1149 }
1150 }
1151 "
1152 ),
1153 @r###"
1154 [
1155 CompletionItem {
1156 label: "Foo",
1157 source_range: [248; 250),
1158 delete: [248; 250),
1159 insert: "Foo",
1160 kind: Enum,
1161 },
1162 CompletionItem {
1163 label: "Foo::Bar",
1164 source_range: [248; 250),
1165 delete: [248; 250),
1166 insert: "Foo::Bar",
1167 kind: EnumVariant,
1168 detail: "()",
1169 },
1170 CompletionItem {
1171 label: "Foo::Baz",
1172 source_range: [248; 250),
1173 delete: [248; 250),
1174 insert: "Foo::Baz",
1175 kind: EnumVariant,
1176 detail: "()",
1177 },
1178 CompletionItem {
1179 label: "Foo::Quux",
1180 source_range: [248; 250),
1181 delete: [248; 250),
1182 insert: "Foo::Quux",
1183 kind: EnumVariant,
1184 detail: "()",
1185 },
1186 ]
1187 "###
1188 )
1189 }
1190
1191 #[test]
1192 fn completes_enum_variant_iflet() {
1193 assert_debug_snapshot!(
1194 do_reference_completion(
1195 r"
1196 enum Foo {
1197 Bar,
1198 Baz,
1199 Quux
1200 }
1201
1202 fn main() {
1203 let foo = Foo::Quux;
1204
1205 if let Qu<|> = foo {
1206
1207 }
1208 }
1209 "
1210 ),
1211 @r###"
1212 [
1213 CompletionItem {
1214 label: "Foo",
1215 source_range: [219; 221),
1216 delete: [219; 221),
1217 insert: "Foo",
1218 kind: Enum,
1219 },
1220 CompletionItem {
1221 label: "Foo::Bar",
1222 source_range: [219; 221),
1223 delete: [219; 221),
1224 insert: "Foo::Bar",
1225 kind: EnumVariant,
1226 detail: "()",
1227 },
1228 CompletionItem {
1229 label: "Foo::Baz",
1230 source_range: [219; 221),
1231 delete: [219; 221),
1232 insert: "Foo::Baz",
1233 kind: EnumVariant,
1234 detail: "()",
1235 },
1236 CompletionItem {
1237 label: "Foo::Quux",
1238 source_range: [219; 221),
1239 delete: [219; 221),
1240 insert: "Foo::Quux",
1241 kind: EnumVariant,
1242 detail: "()",
1243 },
1244 ]
1245 "###
1246 )
1247 }
1248
1249 #[test]
1250 fn completes_enum_variant_basic_expr() {
1251 assert_debug_snapshot!(
1252 do_reference_completion(
1253 r"
1254 enum Foo {
1255 Bar,
1256 Baz,
1257 Quux
1258 }
1259
1260 fn main() {
1261 let foo: Foo = Q<|>
1262 }
1263 "
1264 ),
1265 @r###"
1266 [
1267 CompletionItem {
1268 label: "Foo",
1269 source_range: [185; 186),
1270 delete: [185; 186),
1271 insert: "Foo",
1272 kind: Enum,
1273 },
1274 CompletionItem {
1275 label: "Foo::Bar",
1276 source_range: [185; 186),
1277 delete: [185; 186),
1278 insert: "Foo::Bar",
1279 kind: EnumVariant,
1280 detail: "()",
1281 },
1282 CompletionItem {
1283 label: "Foo::Baz",
1284 source_range: [185; 186),
1285 delete: [185; 186),
1286 insert: "Foo::Baz",
1287 kind: EnumVariant,
1288 detail: "()",
1289 },
1290 CompletionItem {
1291 label: "Foo::Quux",
1292 source_range: [185; 186),
1293 delete: [185; 186),
1294 insert: "Foo::Quux",
1295 kind: EnumVariant,
1296 detail: "()",
1297 },
1298 CompletionItem {
1299 label: "main()",
1300 source_range: [185; 186),
1301 delete: [185; 186),
1302 insert: "main()$0",
1303 kind: Function,
1304 lookup: "main",
1305 detail: "fn main()",
1306 },
1307 ]
1308 "###
1309 )
1310 }
1112} 1311}
diff --git a/crates/ra_ide/src/completion/completion_config.rs b/crates/ra_ide/src/completion/completion_config.rs
new file mode 100644
index 000000000..71b49ace8
--- /dev/null
+++ b/crates/ra_ide/src/completion/completion_config.rs
@@ -0,0 +1,35 @@
1//! Settings for tweaking completion.
2//!
3//! The fun thing here is `SnippetCap` -- this type can only be created in this
4//! module, and we use to statically check that we only produce snippet
5//! completions if we are allowed to.
6
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct CompletionConfig {
9 pub enable_postfix_completions: bool,
10 pub add_call_parenthesis: bool,
11 pub add_call_argument_snippets: bool,
12 pub snippet_cap: Option<SnippetCap>,
13}
14
15impl CompletionConfig {
16 pub fn allow_snippets(&mut self, yes: bool) {
17 self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None }
18 }
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct SnippetCap {
23 _private: (),
24}
25
26impl Default for CompletionConfig {
27 fn default() -> Self {
28 CompletionConfig {
29 enable_postfix_completions: true,
30 add_call_parenthesis: true,
31 add_call_argument_snippets: true,
32 snippet_cap: Some(SnippetCap { _private: () }),
33 }
34 }
35}
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
index 8b3401595..37880448a 100644
--- a/crates/ra_ide/src/completion/completion_context.rs
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -1,6 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use hir::{Semantics, SemanticsScope}; 3use hir::{Semantics, SemanticsScope, Type};
4use ra_db::SourceDatabase; 4use ra_db::SourceDatabase;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{
@@ -11,7 +11,7 @@ use ra_syntax::{
11}; 11};
12use ra_text_edit::AtomTextEdit; 12use ra_text_edit::AtomTextEdit;
13 13
14use crate::{completion::CompletionConfig, FilePosition}; 14use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition};
15 15
16/// `CompletionContext` is created early during completion to figure out, where 16/// `CompletionContext` is created early during completion to figure out, where
17/// exactly is the cursor, syntax-wise. 17/// exactly is the cursor, syntax-wise.
@@ -31,7 +31,10 @@ pub(crate) struct CompletionContext<'a> {
31 pub(super) use_item_syntax: Option<ast::UseItem>, 31 pub(super) use_item_syntax: Option<ast::UseItem>,
32 pub(super) record_lit_syntax: Option<ast::RecordLit>, 32 pub(super) record_lit_syntax: Option<ast::RecordLit>,
33 pub(super) record_pat_syntax: Option<ast::RecordPat>, 33 pub(super) record_pat_syntax: Option<ast::RecordPat>,
34 pub(super) record_field_syntax: Option<ast::RecordField>,
34 pub(super) impl_def: Option<ast::ImplDef>, 35 pub(super) impl_def: Option<ast::ImplDef>,
36 /// FIXME: `ActiveParameter` is string-based, which is very wrong
37 pub(super) active_parameter: Option<ActiveParameter>,
35 pub(super) is_param: bool, 38 pub(super) is_param: bool,
36 /// If a name-binding or reference to a const in a pattern. 39 /// If a name-binding or reference to a const in a pattern.
37 /// Irrefutable patterns (like let) are excluded. 40 /// Irrefutable patterns (like let) are excluded.
@@ -54,6 +57,7 @@ pub(crate) struct CompletionContext<'a> {
54 pub(super) is_macro_call: bool, 57 pub(super) is_macro_call: bool,
55 pub(super) is_path_type: bool, 58 pub(super) is_path_type: bool,
56 pub(super) has_type_args: bool, 59 pub(super) has_type_args: bool,
60 pub(super) is_attribute: bool,
57} 61}
58 62
59impl<'a> CompletionContext<'a> { 63impl<'a> CompletionContext<'a> {
@@ -94,7 +98,9 @@ impl<'a> CompletionContext<'a> {
94 use_item_syntax: None, 98 use_item_syntax: None,
95 record_lit_syntax: None, 99 record_lit_syntax: None,
96 record_pat_syntax: None, 100 record_pat_syntax: None,
101 record_field_syntax: None,
97 impl_def: None, 102 impl_def: None,
103 active_parameter: ActiveParameter::at(db, position),
98 is_param: false, 104 is_param: false,
99 is_pat_binding_or_const: false, 105 is_pat_binding_or_const: false,
100 is_trivial_path: false, 106 is_trivial_path: false,
@@ -108,6 +114,7 @@ impl<'a> CompletionContext<'a> {
108 is_path_type: false, 114 is_path_type: false,
109 has_type_args: false, 115 has_type_args: false,
110 dot_receiver_is_ambiguous_float_literal: false, 116 dot_receiver_is_ambiguous_float_literal: false,
117 is_attribute: false,
111 }; 118 };
112 119
113 let mut original_file = original_file.syntax().clone(); 120 let mut original_file = original_file.syntax().clone();
@@ -168,6 +175,17 @@ impl<'a> CompletionContext<'a> {
168 self.sema.scope_at_offset(&self.token.parent(), self.offset) 175 self.sema.scope_at_offset(&self.token.parent(), self.offset)
169 } 176 }
170 177
178 pub(crate) fn expected_type_of(&self, node: &SyntaxNode) -> Option<Type> {
179 for ancestor in node.ancestors() {
180 if let Some(pat) = ast::Pat::cast(ancestor.clone()) {
181 return self.sema.type_of_pat(&pat);
182 } else if let Some(expr) = ast::Expr::cast(ancestor) {
183 return self.sema.type_of_expr(&expr);
184 }
185 }
186 None
187 }
188
171 fn fill( 189 fn fill(
172 &mut self, 190 &mut self,
173 original_file: &SyntaxNode, 191 original_file: &SyntaxNode,
@@ -268,6 +286,14 @@ impl<'a> CompletionContext<'a> {
268 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) 286 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
269 .find_map(ast::FnDef::cast); 287 .find_map(ast::FnDef::cast);
270 288
289 self.record_field_syntax = self
290 .sema
291 .ancestors_with_macros(self.token.parent())
292 .take_while(|it| {
293 it.kind() != SOURCE_FILE && it.kind() != MODULE && it.kind() != CALL_EXPR
294 })
295 .find_map(ast::RecordField::cast);
296
271 let parent = match name_ref.syntax().parent() { 297 let parent = match name_ref.syntax().parent() {
272 Some(it) => it, 298 Some(it) => it,
273 None => return, 299 None => return,
@@ -282,6 +308,7 @@ impl<'a> CompletionContext<'a> {
282 .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) 308 .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast))
283 .is_some(); 309 .is_some();
284 self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some(); 310 self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some();
311 self.is_attribute = path.syntax().parent().and_then(ast::Attr::cast).is_some();
285 312
286 self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); 313 self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some();
287 self.has_type_args = segment.type_arg_list().is_some(); 314 self.has_type_args = segment.type_arg_list().is_some();
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs
index bc0f1aff5..5936fb8f7 100644
--- a/crates/ra_ide/src/completion/completion_item.rs
+++ b/crates/ra_ide/src/completion/completion_item.rs
@@ -2,6 +2,7 @@
2 2
3use std::fmt; 3use std::fmt;
4 4
5use super::completion_config::SnippetCap;
5use hir::Documentation; 6use hir::Documentation;
6use ra_syntax::TextRange; 7use ra_syntax::TextRange;
7use ra_text_edit::TextEdit; 8use ra_text_edit::TextEdit;
@@ -51,6 +52,9 @@ pub struct CompletionItem {
51 /// If completing a function call, ask the editor to show parameter popup 52 /// If completing a function call, ask the editor to show parameter popup
52 /// after completion. 53 /// after completion.
53 trigger_call_info: bool, 54 trigger_call_info: bool,
55
56 /// Score is useful to pre select or display in better order completion items
57 score: Option<CompletionScore>,
54} 58}
55 59
56// We use custom debug for CompletionItem to make `insta`'s diffs more readable. 60// We use custom debug for CompletionItem to make `insta`'s diffs more readable.
@@ -80,6 +84,9 @@ impl fmt::Debug for CompletionItem {
80 if self.deprecated { 84 if self.deprecated {
81 s.field("deprecated", &true); 85 s.field("deprecated", &true);
82 } 86 }
87 if let Some(score) = &self.score {
88 s.field("score", score);
89 }
83 if self.trigger_call_info { 90 if self.trigger_call_info {
84 s.field("trigger_call_info", &true); 91 s.field("trigger_call_info", &true);
85 } 92 }
@@ -87,6 +94,14 @@ impl fmt::Debug for CompletionItem {
87 } 94 }
88} 95}
89 96
97#[derive(Debug, Clone, Copy)]
98pub enum CompletionScore {
99 /// If only type match
100 TypeMatch,
101 /// If type and name match
102 TypeAndNameMatch,
103}
104
90#[derive(Debug, Clone, Copy, PartialEq, Eq)] 105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum CompletionItemKind { 106pub enum CompletionItemKind {
92 Snippet, 107 Snippet,
@@ -106,6 +121,7 @@ pub enum CompletionItemKind {
106 Method, 121 Method,
107 TypeParam, 122 TypeParam,
108 Macro, 123 Macro,
124 Attribute,
109} 125}
110 126
111#[derive(Debug, PartialEq, Eq, Copy, Clone)] 127#[derive(Debug, PartialEq, Eq, Copy, Clone)]
@@ -119,6 +135,7 @@ pub(crate) enum CompletionKind {
119 Snippet, 135 Snippet,
120 Postfix, 136 Postfix,
121 BuiltinType, 137 BuiltinType,
138 Attribute,
122} 139}
123 140
124#[derive(Debug, PartialEq, Eq, Copy, Clone)] 141#[derive(Debug, PartialEq, Eq, Copy, Clone)]
@@ -147,6 +164,7 @@ impl CompletionItem {
147 text_edit: None, 164 text_edit: None,
148 deprecated: None, 165 deprecated: None,
149 trigger_call_info: None, 166 trigger_call_info: None,
167 score: None,
150 } 168 }
151 } 169 }
152 /// What user sees in pop-up in the UI. 170 /// What user sees in pop-up in the UI.
@@ -175,7 +193,7 @@ impl CompletionItem {
175 } 193 }
176 /// What string is used for filtering. 194 /// What string is used for filtering.
177 pub fn lookup(&self) -> &str { 195 pub fn lookup(&self) -> &str {
178 self.lookup.as_deref().unwrap_or_else(|| self.label()) 196 self.lookup.as_deref().unwrap_or(&self.label)
179 } 197 }
180 198
181 pub fn kind(&self) -> Option<CompletionItemKind> { 199 pub fn kind(&self) -> Option<CompletionItemKind> {
@@ -186,6 +204,10 @@ impl CompletionItem {
186 self.deprecated 204 self.deprecated
187 } 205 }
188 206
207 pub fn score(&self) -> Option<CompletionScore> {
208 self.score
209 }
210
189 pub fn trigger_call_info(&self) -> bool { 211 pub fn trigger_call_info(&self) -> bool {
190 self.trigger_call_info 212 self.trigger_call_info
191 } 213 }
@@ -206,6 +228,7 @@ pub(crate) struct Builder {
206 text_edit: Option<TextEdit>, 228 text_edit: Option<TextEdit>,
207 deprecated: Option<bool>, 229 deprecated: Option<bool>,
208 trigger_call_info: Option<bool>, 230 trigger_call_info: Option<bool>,
231 score: Option<CompletionScore>,
209} 232}
210 233
211impl Builder { 234impl Builder {
@@ -235,6 +258,7 @@ impl Builder {
235 completion_kind: self.completion_kind, 258 completion_kind: self.completion_kind,
236 deprecated: self.deprecated.unwrap_or(false), 259 deprecated: self.deprecated.unwrap_or(false),
237 trigger_call_info: self.trigger_call_info.unwrap_or(false), 260 trigger_call_info: self.trigger_call_info.unwrap_or(false),
261 score: self.score,
238 } 262 }
239 } 263 }
240 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { 264 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
@@ -249,7 +273,11 @@ impl Builder {
249 self.insert_text = Some(insert_text.into()); 273 self.insert_text = Some(insert_text.into());
250 self 274 self
251 } 275 }
252 pub(crate) fn insert_snippet(mut self, snippet: impl Into<String>) -> Builder { 276 pub(crate) fn insert_snippet(
277 mut self,
278 _cap: SnippetCap,
279 snippet: impl Into<String>,
280 ) -> Builder {
253 self.insert_text_format = InsertTextFormat::Snippet; 281 self.insert_text_format = InsertTextFormat::Snippet;
254 self.insert_text(snippet) 282 self.insert_text(snippet)
255 } 283 }
@@ -261,7 +289,7 @@ impl Builder {
261 self.text_edit = Some(edit); 289 self.text_edit = Some(edit);
262 self 290 self
263 } 291 }
264 pub(crate) fn snippet_edit(mut self, edit: TextEdit) -> Builder { 292 pub(crate) fn snippet_edit(mut self, _cap: SnippetCap, edit: TextEdit) -> Builder {
265 self.insert_text_format = InsertTextFormat::Snippet; 293 self.insert_text_format = InsertTextFormat::Snippet;
266 self.text_edit(edit) 294 self.text_edit(edit)
267 } 295 }
@@ -285,6 +313,10 @@ impl Builder {
285 self.deprecated = Some(deprecated); 313 self.deprecated = Some(deprecated);
286 self 314 self
287 } 315 }
316 pub(crate) fn set_score(mut self, score: CompletionScore) -> Builder {
317 self.score = Some(score);
318 self
319 }
288 pub(crate) fn trigger_call_info(mut self) -> Builder { 320 pub(crate) fn trigger_call_info(mut self) -> Builder {
289 self.trigger_call_info = Some(true); 321 self.trigger_call_info = Some(true);
290 self 322 self
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs
index 2189cef65..6a6ddc7bd 100644
--- a/crates/ra_ide/src/completion/presentation.rs
+++ b/crates/ra_ide/src/completion/presentation.rs
@@ -11,7 +11,7 @@ use crate::{
11 CompletionKind, Completions, 11 CompletionKind, Completions,
12 }, 12 },
13 display::{const_label, macro_label, type_label, FunctionSignature}, 13 display::{const_label, macro_label, type_label, FunctionSignature},
14 RootDatabase, 14 CompletionScore, RootDatabase,
15}; 15};
16 16
17impl Completions { 17impl Completions {
@@ -22,16 +22,20 @@ impl Completions {
22 ty: &Type, 22 ty: &Type,
23 ) { 23 ) {
24 let is_deprecated = is_deprecated(field, ctx.db); 24 let is_deprecated = is_deprecated(field, ctx.db);
25 CompletionItem::new( 25 let ty = ty.display(ctx.db).to_string();
26 CompletionKind::Reference, 26 let name = field.name(ctx.db);
27 ctx.source_range(), 27 let mut completion_item =
28 field.name(ctx.db).to_string(), 28 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string())
29 ) 29 .kind(CompletionItemKind::Field)
30 .kind(CompletionItemKind::Field) 30 .detail(ty.clone())
31 .detail(ty.display(ctx.db).to_string()) 31 .set_documentation(field.docs(ctx.db))
32 .set_documentation(field.docs(ctx.db)) 32 .set_deprecated(is_deprecated);
33 .set_deprecated(is_deprecated) 33
34 .add_to(self); 34 if let Some(score) = compute_score(ctx, &ty, &name.to_string()) {
35 completion_item = completion_item.set_score(score);
36 }
37
38 completion_item.add_to(self);
35 } 39 }
36 40
37 pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) { 41 pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) {
@@ -110,44 +114,23 @@ impl Completions {
110 114
111 // Add `<>` for generic types 115 // Add `<>` for generic types
112 if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis { 116 if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis {
113 let has_non_default_type_params = match resolution { 117 if let Some(cap) = ctx.config.snippet_cap {
114 ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db), 118 let has_non_default_type_params = match resolution {
115 ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db), 119 ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db),
116 _ => false, 120 ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db),
117 }; 121 _ => false,
118 if has_non_default_type_params { 122 };
119 tested_by!(inserts_angle_brackets_for_generics); 123 if has_non_default_type_params {
120 completion_item = completion_item 124 tested_by!(inserts_angle_brackets_for_generics);
121 .lookup_by(local_name.clone()) 125 completion_item = completion_item
122 .label(format!("{}<…>", local_name)) 126 .lookup_by(local_name.clone())
123 .insert_snippet(format!("{}<$0>", local_name)); 127 .label(format!("{}<…>", local_name))
124 } 128 .insert_snippet(cap, format!("{}<$0>", local_name));
125 }
126
127 completion_item.kind(kind).set_documentation(docs).add_to(self)
128 }
129
130 fn guess_macro_braces(&self, macro_name: &str, docs: &str) -> &'static str {
131 let mut votes = [0, 0, 0];
132 for (idx, s) in docs.match_indices(&macro_name) {
133 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
134 // Ensure to match the full word
135 if after.starts_with('!')
136 && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
137 {
138 // It may have spaces before the braces like `foo! {}`
139 match after[1..].chars().find(|&c| !c.is_whitespace()) {
140 Some('{') => votes[0] += 1,
141 Some('[') => votes[1] += 1,
142 Some('(') => votes[2] += 1,
143 _ => {}
144 } 129 }
145 } 130 }
146 } 131 }
147 132
148 // Insert a space before `{}`. 133 completion_item.kind(kind).set_documentation(docs).add_to(self)
149 // We prefer the last one when some votes equal.
150 *votes.iter().zip(&[" {$0}", "[$0]", "($0)"]).max_by_key(|&(&vote, _)| vote).unwrap().1
151 } 134 }
152 135
153 pub(crate) fn add_macro( 136 pub(crate) fn add_macro(
@@ -171,22 +154,31 @@ impl Completions {
171 let detail = macro_label(&ast_node); 154 let detail = macro_label(&ast_node);
172 155
173 let docs = macro_.docs(ctx.db); 156 let docs = macro_.docs(ctx.db);
174 let macro_declaration = format!("{}!", name);
175 157
176 let mut builder = 158 let mut builder = CompletionItem::new(
177 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), &macro_declaration) 159 CompletionKind::Reference,
178 .kind(CompletionItemKind::Macro) 160 ctx.source_range(),
179 .set_documentation(docs.clone()) 161 &format!("{}!", name),
180 .set_deprecated(is_deprecated(macro_, ctx.db)) 162 )
181 .detail(detail); 163 .kind(CompletionItemKind::Macro)
182 164 .set_documentation(docs.clone())
183 builder = if ctx.use_item_syntax.is_some() || ctx.is_macro_call { 165 .set_deprecated(is_deprecated(macro_, ctx.db))
184 tested_by!(dont_insert_macro_call_parens_unncessary); 166 .detail(detail);
185 builder.insert_text(name) 167
186 } else { 168 let needs_bang = ctx.use_item_syntax.is_none() && !ctx.is_macro_call;
187 let macro_braces_to_insert = 169 builder = match ctx.config.snippet_cap {
188 self.guess_macro_braces(&name, docs.as_ref().map_or("", |s| s.as_str())); 170 Some(cap) if needs_bang => {
189 builder.insert_snippet(macro_declaration + macro_braces_to_insert) 171 let docs = docs.as_ref().map_or("", |s| s.as_str());
172 let (bra, ket) = guess_macro_braces(&name, docs);
173 builder
174 .insert_snippet(cap, format!("{}!{}$0{}", name, bra, ket))
175 .label(format!("{}!{}…{}", name, bra, ket))
176 }
177 None if needs_bang => builder.insert_text(format!("{}!", name)),
178 _ => {
179 tested_by!(dont_insert_macro_call_parens_unncessary);
180 builder.insert_text(name)
181 }
190 }; 182 };
191 183
192 self.add(builder); 184 self.add(builder);
@@ -300,6 +292,42 @@ impl Completions {
300 } 292 }
301} 293}
302 294
295pub(crate) fn compute_score(
296 ctx: &CompletionContext,
297 // FIXME: this definitely should be a `Type`
298 ty: &str,
299 name: &str,
300) -> Option<CompletionScore> {
301 let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax {
302 tested_by!(test_struct_field_completion_in_record_lit);
303 let (struct_field, _local) = ctx.sema.resolve_record_field(record_field)?;
304 (
305 struct_field.name(ctx.db).to_string(),
306 struct_field.signature_ty(ctx.db).display(ctx.db).to_string(),
307 )
308 } else if let Some(active_parameter) = &ctx.active_parameter {
309 tested_by!(test_struct_field_completion_in_func_call);
310 (active_parameter.name.clone(), active_parameter.ty.clone())
311 } else {
312 return None;
313 };
314
315 // Compute score
316 // For the same type
317 if &active_type != ty {
318 return None;
319 }
320
321 let mut res = CompletionScore::TypeMatch;
322
323 // If same type + same name then go top position
324 if active_name == name {
325 res = CompletionScore::TypeAndNameMatch
326 }
327
328 Some(res)
329}
330
303enum Params { 331enum Params {
304 Named(Vec<String>), 332 Named(Vec<String>),
305 Anonymous(usize), 333 Anonymous(usize),
@@ -326,6 +354,10 @@ impl Builder {
326 if ctx.use_item_syntax.is_some() || ctx.is_call { 354 if ctx.use_item_syntax.is_some() || ctx.is_call {
327 return self; 355 return self;
328 } 356 }
357 let cap = match ctx.config.snippet_cap {
358 Some(it) => it,
359 None => return self,
360 };
329 // If not an import, add parenthesis automatically. 361 // If not an import, add parenthesis automatically.
330 tested_by!(inserts_parens_for_function_calls); 362 tested_by!(inserts_parens_for_function_calls);
331 363
@@ -347,7 +379,7 @@ impl Builder {
347 379
348 (snippet, format!("{}(…)", name)) 380 (snippet, format!("{}(…)", name))
349 }; 381 };
350 self.lookup_by(name).label(label).insert_snippet(snippet) 382 self.lookup_by(name).label(label).insert_snippet(cap, snippet)
351 } 383 }
352} 384}
353 385
@@ -355,6 +387,34 @@ fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool {
355 node.attrs(db).by_key("deprecated").exists() 387 node.attrs(db).by_key("deprecated").exists()
356} 388}
357 389
390fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
391 let mut votes = [0, 0, 0];
392 for (idx, s) in docs.match_indices(&macro_name) {
393 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
394 // Ensure to match the full word
395 if after.starts_with('!')
396 && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
397 {
398 // It may have spaces before the braces like `foo! {}`
399 match after[1..].chars().find(|&c| !c.is_whitespace()) {
400 Some('{') => votes[0] += 1,
401 Some('[') => votes[1] += 1,
402 Some('(') => votes[2] += 1,
403 _ => {}
404 }
405 }
406 }
407
408 // Insert a space before `{}`.
409 // We prefer the last one when some votes equal.
410 let (_vote, (bra, ket)) = votes
411 .iter()
412 .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
413 .max_by_key(|&(&vote, _)| vote)
414 .unwrap();
415 (*bra, *ket)
416}
417
358#[cfg(test)] 418#[cfg(test)]
359mod tests { 419mod tests {
360 use insta::assert_debug_snapshot; 420 use insta::assert_debug_snapshot;
@@ -1031,4 +1091,237 @@ mod tests {
1031 "### 1091 "###
1032 ); 1092 );
1033 } 1093 }
1094
1095 #[test]
1096 fn test_struct_field_completion_in_func_call() {
1097 covers!(test_struct_field_completion_in_func_call);
1098 assert_debug_snapshot!(
1099 do_reference_completion(
1100 r"
1101 struct A { another_field: i64, the_field: u32, my_string: String }
1102 fn test(my_param: u32) -> u32 { my_param }
1103 fn foo(a: A) {
1104 test(a.<|>)
1105 }
1106 ",
1107 ),
1108 @r###"
1109 [
1110 CompletionItem {
1111 label: "another_field",
1112 source_range: [201; 201),
1113 delete: [201; 201),
1114 insert: "another_field",
1115 kind: Field,
1116 detail: "i64",
1117 },
1118 CompletionItem {
1119 label: "my_string",
1120 source_range: [201; 201),
1121 delete: [201; 201),
1122 insert: "my_string",
1123 kind: Field,
1124 detail: "{unknown}",
1125 },
1126 CompletionItem {
1127 label: "the_field",
1128 source_range: [201; 201),
1129 delete: [201; 201),
1130 insert: "the_field",
1131 kind: Field,
1132 detail: "u32",
1133 score: TypeMatch,
1134 },
1135 ]
1136 "###
1137 );
1138 }
1139
1140 #[test]
1141 fn test_struct_field_completion_in_func_call_with_type_and_name() {
1142 assert_debug_snapshot!(
1143 do_reference_completion(
1144 r"
1145 struct A { another_field: i64, another_good_type: u32, the_field: u32 }
1146 fn test(the_field: u32) -> u32 { the_field }
1147 fn foo(a: A) {
1148 test(a.<|>)
1149 }
1150 ",
1151 ),
1152 @r###"
1153 [
1154 CompletionItem {
1155 label: "another_field",
1156 source_range: [208; 208),
1157 delete: [208; 208),
1158 insert: "another_field",
1159 kind: Field,
1160 detail: "i64",
1161 },
1162 CompletionItem {
1163 label: "another_good_type",
1164 source_range: [208; 208),
1165 delete: [208; 208),
1166 insert: "another_good_type",
1167 kind: Field,
1168 detail: "u32",
1169 score: TypeMatch,
1170 },
1171 CompletionItem {
1172 label: "the_field",
1173 source_range: [208; 208),
1174 delete: [208; 208),
1175 insert: "the_field",
1176 kind: Field,
1177 detail: "u32",
1178 score: TypeAndNameMatch,
1179 },
1180 ]
1181 "###
1182 );
1183 }
1184
1185 #[test]
1186 fn test_struct_field_completion_in_record_lit() {
1187 covers!(test_struct_field_completion_in_func_call);
1188 assert_debug_snapshot!(
1189 do_reference_completion(
1190 r"
1191 struct A { another_field: i64, another_good_type: u32, the_field: u32 }
1192 struct B { my_string: String, my_vec: Vec<u32>, the_field: u32 }
1193 fn foo(a: A) {
1194 let b = B {
1195 the_field: a.<|>
1196 };
1197 }
1198 ",
1199 ),
1200 @r###"
1201 [
1202 CompletionItem {
1203 label: "another_field",
1204 source_range: [270; 270),
1205 delete: [270; 270),
1206 insert: "another_field",
1207 kind: Field,
1208 detail: "i64",
1209 },
1210 CompletionItem {
1211 label: "another_good_type",
1212 source_range: [270; 270),
1213 delete: [270; 270),
1214 insert: "another_good_type",
1215 kind: Field,
1216 detail: "u32",
1217 score: TypeMatch,
1218 },
1219 CompletionItem {
1220 label: "the_field",
1221 source_range: [270; 270),
1222 delete: [270; 270),
1223 insert: "the_field",
1224 kind: Field,
1225 detail: "u32",
1226 score: TypeAndNameMatch,
1227 },
1228 ]
1229 "###
1230 );
1231 }
1232
1233 #[test]
1234 fn test_struct_field_completion_in_record_lit_and_fn_call() {
1235 assert_debug_snapshot!(
1236 do_reference_completion(
1237 r"
1238 struct A { another_field: i64, another_good_type: u32, the_field: u32 }
1239 struct B { my_string: String, my_vec: Vec<u32>, the_field: u32 }
1240 fn test(the_field: i64) -> i64 { the_field }
1241 fn foo(a: A) {
1242 let b = B {
1243 the_field: test(a.<|>)
1244 };
1245 }
1246 ",
1247 ),
1248 @r###"
1249 [
1250 CompletionItem {
1251 label: "another_field",
1252 source_range: [336; 336),
1253 delete: [336; 336),
1254 insert: "another_field",
1255 kind: Field,
1256 detail: "i64",
1257 score: TypeMatch,
1258 },
1259 CompletionItem {
1260 label: "another_good_type",
1261 source_range: [336; 336),
1262 delete: [336; 336),
1263 insert: "another_good_type",
1264 kind: Field,
1265 detail: "u32",
1266 },
1267 CompletionItem {
1268 label: "the_field",
1269 source_range: [336; 336),
1270 delete: [336; 336),
1271 insert: "the_field",
1272 kind: Field,
1273 detail: "u32",
1274 },
1275 ]
1276 "###
1277 );
1278 }
1279
1280 #[test]
1281 fn test_struct_field_completion_in_fn_call_and_record_lit() {
1282 assert_debug_snapshot!(
1283 do_reference_completion(
1284 r"
1285 struct A { another_field: i64, another_good_type: u32, the_field: u32 }
1286 struct B { my_string: String, my_vec: Vec<u32>, the_field: u32 }
1287 fn test(the_field: i64) -> i64 { the_field }
1288 fn foo(a: A) {
1289 test(B {
1290 the_field: a.<|>
1291 });
1292 }
1293 ",
1294 ),
1295 @r###"
1296 [
1297 CompletionItem {
1298 label: "another_field",
1299 source_range: [328; 328),
1300 delete: [328; 328),
1301 insert: "another_field",
1302 kind: Field,
1303 detail: "i64",
1304 },
1305 CompletionItem {
1306 label: "another_good_type",
1307 source_range: [328; 328),
1308 delete: [328; 328),
1309 insert: "another_good_type",
1310 kind: Field,
1311 detail: "u32",
1312 score: TypeMatch,
1313 },
1314 CompletionItem {
1315 label: "the_field",
1316 source_range: [328; 328),
1317 delete: [328; 328),
1318 insert: "the_field",
1319 kind: Field,
1320 detail: "u32",
1321 score: TypeAndNameMatch,
1322 },
1323 ]
1324 "###
1325 );
1326 }
1034} 1327}
diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs
index b967a6816..b5e2785fe 100644
--- a/crates/ra_ide/src/display/function_signature.rs
+++ b/crates/ra_ide/src/display/function_signature.rs
@@ -36,6 +36,8 @@ pub struct FunctionSignature {
36 pub parameters: Vec<String>, 36 pub parameters: Vec<String>,
37 /// Parameter names of the function 37 /// Parameter names of the function
38 pub parameter_names: Vec<String>, 38 pub parameter_names: Vec<String>,
39 /// Parameter types of the function
40 pub parameter_types: Vec<String>,
39 /// Optional return type 41 /// Optional return type
40 pub ret_type: Option<String>, 42 pub ret_type: Option<String>,
41 /// Where predicates 43 /// Where predicates
@@ -62,14 +64,20 @@ impl FunctionSignature {
62 return None; 64 return None;
63 }; 65 };
64 66
65 let params = st 67 let mut params = vec![];
66 .fields(db) 68 let mut parameter_types = vec![];
67 .into_iter() 69 for field in st.fields(db).into_iter() {
68 .map(|field: hir::StructField| { 70 let ty = field.signature_ty(db);
69 let ty = field.signature_ty(db); 71 let raw_param = format!("{}", ty.display(db));
70 format!("{}", ty.display(db)) 72
71 }) 73 if let Some(param_type) = raw_param.split(':').nth(1) {
72 .collect(); 74 parameter_types.push(param_type[1..].to_string());
75 } else {
76 // useful when you have tuple struct
77 parameter_types.push(raw_param.clone());
78 }
79 params.push(raw_param);
80 }
73 81
74 Some( 82 Some(
75 FunctionSignature { 83 FunctionSignature {
@@ -79,6 +87,7 @@ impl FunctionSignature {
79 ret_type: node.name().map(|n| n.text().to_string()), 87 ret_type: node.name().map(|n| n.text().to_string()),
80 parameters: params, 88 parameters: params,
81 parameter_names: vec![], 89 parameter_names: vec![],
90 parameter_types,
82 generic_parameters: generic_parameters(&node), 91 generic_parameters: generic_parameters(&node),
83 where_predicates: where_predicates(&node), 92 where_predicates: where_predicates(&node),
84 doc: None, 93 doc: None,
@@ -99,15 +108,21 @@ impl FunctionSignature {
99 108
100 let name = format!("{}::{}", parent_name, variant.name(db)); 109 let name = format!("{}::{}", parent_name, variant.name(db));
101 110
102 let params = variant 111 let mut params = vec![];
103 .fields(db) 112 let mut parameter_types = vec![];
104 .into_iter() 113 for field in variant.fields(db).into_iter() {
105 .map(|field: hir::StructField| { 114 let ty = field.signature_ty(db);
106 let name = field.name(db); 115 let raw_param = format!("{}", ty.display(db));
107 let ty = field.signature_ty(db); 116 if let Some(param_type) = raw_param.split(':').nth(1) {
108 format!("{}: {}", name, ty.display(db)) 117 parameter_types.push(param_type[1..].to_string());
109 }) 118 } else {
110 .collect(); 119 // The unwrap_or_else is useful when you have tuple
120 parameter_types.push(raw_param);
121 }
122 let name = field.name(db);
123
124 params.push(format!("{}: {}", name, ty.display(db)));
125 }
111 126
112 Some( 127 Some(
113 FunctionSignature { 128 FunctionSignature {
@@ -117,6 +132,7 @@ impl FunctionSignature {
117 ret_type: None, 132 ret_type: None,
118 parameters: params, 133 parameters: params,
119 parameter_names: vec![], 134 parameter_names: vec![],
135 parameter_types,
120 generic_parameters: vec![], 136 generic_parameters: vec![],
121 where_predicates: vec![], 137 where_predicates: vec![],
122 doc: None, 138 doc: None,
@@ -139,6 +155,7 @@ impl FunctionSignature {
139 ret_type: None, 155 ret_type: None,
140 parameters: params, 156 parameters: params,
141 parameter_names: vec![], 157 parameter_names: vec![],
158 parameter_types: vec![],
142 generic_parameters: vec![], 159 generic_parameters: vec![],
143 where_predicates: vec![], 160 where_predicates: vec![],
144 doc: None, 161 doc: None,
@@ -151,18 +168,27 @@ impl FunctionSignature {
151 168
152impl From<&'_ ast::FnDef> for FunctionSignature { 169impl From<&'_ ast::FnDef> for FunctionSignature {
153 fn from(node: &ast::FnDef) -> FunctionSignature { 170 fn from(node: &ast::FnDef) -> FunctionSignature {
154 fn param_list(node: &ast::FnDef) -> (bool, Vec<String>) { 171 fn param_list(node: &ast::FnDef) -> (bool, Vec<String>, Vec<String>) {
155 let mut res = vec![]; 172 let mut res = vec![];
173 let mut res_types = vec![];
156 let mut has_self_param = false; 174 let mut has_self_param = false;
157 if let Some(param_list) = node.param_list() { 175 if let Some(param_list) = node.param_list() {
158 if let Some(self_param) = param_list.self_param() { 176 if let Some(self_param) = param_list.self_param() {
159 has_self_param = true; 177 has_self_param = true;
160 res.push(self_param.syntax().text().to_string()) 178 let raw_param = self_param.syntax().text().to_string();
179
180 res_types.push(
181 raw_param.split(':').nth(1).unwrap_or_else(|| " Self")[1..].to_string(),
182 );
183 res.push(raw_param);
161 } 184 }
162 185
163 res.extend(param_list.params().map(|param| param.syntax().text().to_string())); 186 res.extend(param_list.params().map(|param| param.syntax().text().to_string()));
187 res_types.extend(param_list.params().map(|param| {
188 param.syntax().text().to_string().split(':').nth(1).unwrap()[1..].to_string()
189 }));
164 } 190 }
165 (has_self_param, res) 191 (has_self_param, res, res_types)
166 } 192 }
167 193
168 fn param_name_list(node: &ast::FnDef) -> Vec<String> { 194 fn param_name_list(node: &ast::FnDef) -> Vec<String> {
@@ -192,7 +218,7 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
192 res 218 res
193 } 219 }
194 220
195 let (has_self_param, parameters) = param_list(node); 221 let (has_self_param, parameters, parameter_types) = param_list(node);
196 222
197 FunctionSignature { 223 FunctionSignature {
198 kind: CallableKind::Function, 224 kind: CallableKind::Function,
@@ -204,6 +230,7 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
204 .map(|n| n.syntax().text().to_string()), 230 .map(|n| n.syntax().text().to_string()),
205 parameters, 231 parameters,
206 parameter_names: param_name_list(node), 232 parameter_names: param_name_list(node),
233 parameter_types,
207 generic_parameters: generic_parameters(node), 234 generic_parameters: generic_parameters(node),
208 where_predicates: where_predicates(node), 235 where_predicates: where_predicates(node),
209 // docs are processed separately 236 // docs are processed separately
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs
index 6289f53f3..67bc9c31b 100644
--- a/crates/ra_ide/src/display/navigation_target.rs
+++ b/crates/ra_ide/src/display/navigation_target.rs
@@ -176,7 +176,7 @@ impl ToNav for FileSymbol {
176 file_id: self.file_id, 176 file_id: self.file_id,
177 name: self.name.clone(), 177 name: self.name.clone(),
178 kind: self.kind, 178 kind: self.kind,
179 full_range: self.ptr.range(), 179 full_range: self.range,
180 focus_range: self.name_range, 180 focus_range: self.name_range,
181 container_name: self.container_name.clone(), 181 container_name: self.container_name.clone(),
182 description: description_from_symbol(db, self), 182 description: description_from_symbol(db, self),
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 5599f143f..f692fbaa2 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -67,7 +67,9 @@ use crate::display::ToNav;
67pub use crate::{ 67pub use crate::{
68 assists::{Assist, AssistId}, 68 assists::{Assist, AssistId},
69 call_hierarchy::CallItem, 69 call_hierarchy::CallItem,
70 completion::{CompletionConfig, CompletionItem, CompletionItemKind, InsertTextFormat}, 70 completion::{
71 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat,
72 },
71 diagnostics::Severity, 73 diagnostics::Severity,
72 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, 74 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode},
73 expand_macro::ExpandedMacro, 75 expand_macro::ExpandedMacro,
diff --git a/crates/ra_ide/src/marks.rs b/crates/ra_ide/src/marks.rs
index eee44e886..bea30fe2a 100644
--- a/crates/ra_ide/src/marks.rs
+++ b/crates/ra_ide/src/marks.rs
@@ -9,4 +9,6 @@ test_utils::marks!(
9 search_filters_by_range 9 search_filters_by_range
10 dont_insert_macro_call_parens_unncessary 10 dont_insert_macro_call_parens_unncessary
11 self_fulfilling_completion 11 self_fulfilling_completion
12 test_struct_field_completion_in_func_call
13 test_struct_field_completion_in_record_lit
12); 14);
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index 9433f3a24..05a66e03c 100644
--- a/crates/ra_ide/src/runnables.rs
+++ b/crates/ra_ide/src/runnables.rs
@@ -34,7 +34,7 @@ impl Display for TestId {
34 34
35#[derive(Debug)] 35#[derive(Debug)]
36pub enum RunnableKind { 36pub enum RunnableKind {
37 Test { test_id: TestId }, 37 Test { test_id: TestId, attr: TestAttr },
38 TestMod { path: String }, 38 TestMod { path: String },
39 Bench { test_id: TestId }, 39 Bench { test_id: TestId },
40 Bin, 40 Bin,
@@ -77,7 +77,8 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
77 }; 77 };
78 78
79 if has_test_related_attribute(&fn_def) { 79 if has_test_related_attribute(&fn_def) {
80 RunnableKind::Test { test_id } 80 let attr = TestAttr::from_fn(&fn_def);
81 RunnableKind::Test { test_id, attr }
81 } else if fn_def.has_atom_attr("bench") { 82 } else if fn_def.has_atom_attr("bench") {
82 RunnableKind::Bench { test_id } 83 RunnableKind::Bench { test_id }
83 } else { 84 } else {
@@ -87,6 +88,21 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
87 Some(Runnable { range: fn_def.syntax().text_range(), kind }) 88 Some(Runnable { range: fn_def.syntax().text_range(), kind })
88} 89}
89 90
91#[derive(Debug)]
92pub struct TestAttr {
93 pub ignore: bool,
94}
95
96impl TestAttr {
97 fn from_fn(fn_def: &ast::FnDef) -> TestAttr {
98 let ignore = fn_def
99 .attrs()
100 .filter_map(|attr| attr.simple_name())
101 .any(|attribute_text| attribute_text == "ignore");
102 TestAttr { ignore }
103 }
104}
105
90/// This is a method with a heuristics to support test methods annotated with custom test annotations, such as 106/// This is a method with a heuristics to support test methods annotated with custom test annotations, such as
91/// `#[test_case(...)]`, `#[tokio::test]` and similar. 107/// `#[test_case(...)]`, `#[tokio::test]` and similar.
92/// Also a regular `#[test]` annotation is supported. 108/// Also a regular `#[test]` annotation is supported.
@@ -157,6 +173,9 @@ mod tests {
157 test_id: Path( 173 test_id: Path(
158 "test_foo", 174 "test_foo",
159 ), 175 ),
176 attr: TestAttr {
177 ignore: false,
178 },
160 }, 179 },
161 }, 180 },
162 Runnable { 181 Runnable {
@@ -165,6 +184,9 @@ mod tests {
165 test_id: Path( 184 test_id: Path(
166 "test_foo", 185 "test_foo",
167 ), 186 ),
187 attr: TestAttr {
188 ignore: true,
189 },
168 }, 190 },
169 }, 191 },
170 ] 192 ]
@@ -200,6 +222,9 @@ mod tests {
200 test_id: Path( 222 test_id: Path(
201 "test_mod::test_foo1", 223 "test_mod::test_foo1",
202 ), 224 ),
225 attr: TestAttr {
226 ignore: false,
227 },
203 }, 228 },
204 }, 229 },
205 ] 230 ]
@@ -237,6 +262,9 @@ mod tests {
237 test_id: Path( 262 test_id: Path(
238 "foo::test_mod::test_foo1", 263 "foo::test_mod::test_foo1",
239 ), 264 ),
265 attr: TestAttr {
266 ignore: false,
267 },
240 }, 268 },
241 }, 269 },
242 ] 270 ]
@@ -276,6 +304,9 @@ mod tests {
276 test_id: Path( 304 test_id: Path(
277 "foo::bar::test_mod::test_foo1", 305 "foo::bar::test_mod::test_foo1",
278 ), 306 ),
307 attr: TestAttr {
308 ignore: false,
309 },
279 }, 310 },
280 }, 311 },
281 ] 312 ]
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html
new file mode 100644
index 000000000..433f2e0c5
--- /dev/null
+++ b/crates/ra_ide/src/snapshots/highlight_strings.html
@@ -0,0 +1,82 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.lifetime { color: #DFAF8F; font-style: italic; }
7.comment { color: #7F9F7F; }
8.struct, .enum { color: #7CB8BB; }
9.enum_variant { color: #BDE0F3; }
10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; }
12.function { color: #93E0E3; }
13.parameter { color: #94BFF3; }
14.text { color: #DCDCCC; }
15.type { color: #7CB8BB; }
16.builtin_type { color: #8CD0D3; }
17.type_param { color: #DFAF8F; }
18.attribute { color: #94BFF3; }
19.numeric_literal { color: #BFEBBF; }
20.macro { color: #94BFF3; }
21.module { color: #AFD8AF; }
22.variable { color: #DCDCCC; }
23.mutable { text-decoration: underline; }
24
25.keyword { color: #F0DFAF; font-weight: bold; }
26.keyword.unsafe { color: #BC8383; font-weight: bold; }
27.control { font-style: italic; }
28</style>
29<pre><code><span class="macro">macro_rules!</span> println {
30 ($($arg:tt)*) =&gt; ({
31 $<span class="keyword">crate</span>::io::_print($<span class="keyword">crate</span>::format_args_nl!($($arg)*));
32 })
33}
34#[rustc_builtin_macro]
35<span class="macro">macro_rules!</span> format_args_nl {
36 ($fmt:expr) =&gt; {{ <span class="comment">/* compiler built-in */</span> }};
37 ($fmt:expr, $($args:tt)*) =&gt; {{ <span class="comment">/* compiler built-in */</span> }};
38}
39
40<span class="keyword">fn</span> <span class="function declaration">main</span>() {
41 <span class="comment">// from https://doc.rust-lang.org/std/fmt/index.html</span>
42 <span class="macro">println!</span>(<span class="string_literal">"Hello"</span>); <span class="comment">// =&gt; "Hello"</span>
43 <span class="macro">println!</span>(<span class="string_literal">"Hello, </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>); <span class="comment">// =&gt; "Hello, world!"</span>
44 <span class="macro">println!</span>(<span class="string_literal">"The number is </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>); <span class="comment">// =&gt; "The number is 1"</span>
45 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">?</span><span class="attribute">}</span><span class="string_literal">"</span>, (<span class="numeric_literal">3</span>, <span class="numeric_literal">4</span>)); <span class="comment">// =&gt; "(3, 4)"</span>
46 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">value</span><span class="attribute">}</span><span class="string_literal">"</span>, value=<span class="numeric_literal">4</span>); <span class="comment">// =&gt; "4"</span>
47 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, <span class="numeric_literal">2</span>); <span class="comment">// =&gt; "1 2"</span>
48 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="numeric_literal">4</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">42</span>); <span class="comment">// =&gt; "0042" with leading zerosV</span>
49 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="numeric_literal">0</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, <span class="numeric_literal">2</span>); <span class="comment">// =&gt; "2 1 1 2"</span>
50 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">argument</span><span class="attribute">}</span><span class="string_literal">"</span>, argument = <span class="string_literal">"test"</span>); <span class="comment">// =&gt; "test"</span>
51 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, name = <span class="numeric_literal">2</span>); <span class="comment">// =&gt; "2 1"</span>
52 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">a</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="variable">c</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="variable">b</span><span class="attribute">}</span><span class="string_literal">"</span>, a=<span class="string_literal">"a"</span>, b=<span class="char_literal">'b'</span>, c=<span class="numeric_literal">3</span>); <span class="comment">// =&gt; "a 3 b"</span>
53 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
54 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">1</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>);
55 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>);
56 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="variable">width</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, width = <span class="numeric_literal">5</span>);
57 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">&lt;</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
58 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">-</span><span class="attribute">&lt;</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
59 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">^</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
60 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">&gt;</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
61 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">+</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>);
62 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">#</span><span class="variable">x</span><span class="string_literal">}!"</span>, <span class="numeric_literal">27</span>);
63 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>);
64 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, -<span class="numeric_literal">5</span>);
65 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">#</span><span class="numeric_literal">0</span><span class="numeric_literal">10</span><span class="variable">x</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">27</span>);
66 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">0</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">:</span><span class="attribute">.</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">0.01</span>);
67 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">2</span><span class="attribute">:</span><span class="attribute">.</span><span class="numeric_literal">0</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">0.01</span>);
68 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">0</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">2</span><span class="attribute">:</span><span class="attribute">.</span><span class="numeric_literal">1</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>, <span class="numeric_literal">0.01</span>);
69 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>, <span class="numeric_literal">0.01</span>);
70 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">2</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>, <span class="numeric_literal">0.01</span>);
71 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="variable">number</span><span class="attribute">:</span><span class="attribute">.</span><span class="variable">prec</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, prec = <span class="numeric_literal">5</span>, number = <span class="numeric_literal">0.01</span>);
72 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 fractional digits"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="numeric_literal">1234.56</span>);
73 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 characters"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="string_literal">"1234.56"</span>);
74 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">&gt;</span><span class="numeric_literal">8</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 right-aligned characters"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="string_literal">"1234.56"</span>);
75 <span class="macro">println!</span>(<span class="string_literal">"Hello {{}}"</span>);
76 <span class="macro">println!</span>(<span class="string_literal">"{{ Hello"</span>);
77
78 <span class="macro">println!</span>(<span class="string_literal">r"Hello, </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>);
79
80 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">\x41</span><span class="attribute">}</span><span class="string_literal">"</span>, A = <span class="numeric_literal">92</span>);
81 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">ничоси</span><span class="attribute">}</span><span class="string_literal">"</span>, ничоси = <span class="numeric_literal">92</span>);
82}</code></pre> \ No newline at end of file
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index 93d502875..c0728bfb1 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -12,15 +12,16 @@ use ra_ide_db::{
12}; 12};
13use ra_prof::profile; 13use ra_prof::profile;
14use ra_syntax::{ 14use ra_syntax::{
15 ast::{self, HasQuotes, HasStringValue}, 15 ast::{self, HasFormatSpecifier, HasQuotes, HasStringValue},
16 AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, 16 AstNode, AstToken, Direction, NodeOrToken, SyntaxElement,
17 SyntaxKind::*, 17 SyntaxKind::*,
18 SyntaxToken, TextRange, WalkEvent, T, 18 SyntaxToken, TextRange, WalkEvent, T,
19}; 19};
20use rustc_hash::FxHashMap; 20use rustc_hash::FxHashMap;
21 21
22use crate::{call_info::call_info_for_token, Analysis, FileId}; 22use crate::{call_info::ActiveParameter, Analysis, FileId};
23 23
24use ast::FormatSpecifier;
24pub(crate) use html::highlight_as_html; 25pub(crate) use html::highlight_as_html;
25pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; 26pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
26 27
@@ -31,6 +32,81 @@ pub struct HighlightedRange {
31 pub binding_hash: Option<u64>, 32 pub binding_hash: Option<u64>,
32} 33}
33 34
35#[derive(Debug)]
36struct HighlightedRangeStack {
37 stack: Vec<Vec<HighlightedRange>>,
38}
39
40/// We use a stack to implement the flattening logic for the highlighted
41/// syntax ranges.
42impl HighlightedRangeStack {
43 fn new() -> Self {
44 Self { stack: vec![Vec::new()] }
45 }
46
47 fn push(&mut self) {
48 self.stack.push(Vec::new());
49 }
50
51 /// Flattens the highlighted ranges.
52 ///
53 /// For example `#[cfg(feature = "foo")]` contains the nested ranges:
54 /// 1) parent-range: Attribute [0, 23)
55 /// 2) child-range: String [16, 21)
56 ///
57 /// The following code implements the flattening, for our example this results to:
58 /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
59 fn pop(&mut self) {
60 let children = self.stack.pop().unwrap();
61 let prev = self.stack.last_mut().unwrap();
62 let needs_flattening = !children.is_empty()
63 && !prev.is_empty()
64 && children.first().unwrap().range.is_subrange(&prev.last().unwrap().range);
65 if !needs_flattening {
66 prev.extend(children);
67 } else {
68 let mut parent = prev.pop().unwrap();
69 for ele in children {
70 assert!(ele.range.is_subrange(&parent.range));
71 let mut cloned = parent.clone();
72 parent.range = TextRange::from_to(parent.range.start(), ele.range.start());
73 cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end());
74 if !parent.range.is_empty() {
75 prev.push(parent);
76 }
77 prev.push(ele);
78 parent = cloned;
79 }
80 if !parent.range.is_empty() {
81 prev.push(parent);
82 }
83 }
84 }
85
86 fn add(&mut self, range: HighlightedRange) {
87 self.stack
88 .last_mut()
89 .expect("during DFS traversal, the stack must not be empty")
90 .push(range)
91 }
92
93 fn flattened(mut self) -> Vec<HighlightedRange> {
94 assert_eq!(
95 self.stack.len(),
96 1,
97 "after DFS traversal, the stack should only contain a single element"
98 );
99 let mut res = self.stack.pop().unwrap();
100 res.sort_by_key(|range| range.range.start());
101 // Check that ranges are sorted and disjoint
102 assert!(res
103 .iter()
104 .zip(res.iter().skip(1))
105 .all(|(left, right)| left.range.end() <= right.range.start()));
106 res
107 }
108}
109
34pub(crate) fn highlight( 110pub(crate) fn highlight(
35 db: &RootDatabase, 111 db: &RootDatabase,
36 file_id: FileId, 112 file_id: FileId,
@@ -57,52 +133,18 @@ pub(crate) fn highlight(
57 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); 133 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
58 // We use a stack for the DFS traversal below. 134 // We use a stack for the DFS traversal below.
59 // When we leave a node, the we use it to flatten the highlighted ranges. 135 // When we leave a node, the we use it to flatten the highlighted ranges.
60 let mut res: Vec<Vec<HighlightedRange>> = vec![Vec::new()]; 136 let mut stack = HighlightedRangeStack::new();
61 137
62 let mut current_macro_call: Option<ast::MacroCall> = None; 138 let mut current_macro_call: Option<ast::MacroCall> = None;
139 let mut format_string: Option<SyntaxElement> = None;
63 140
64 // Walk all nodes, keeping track of whether we are inside a macro or not. 141 // Walk all nodes, keeping track of whether we are inside a macro or not.
65 // If in macro, expand it first and highlight the expanded code. 142 // If in macro, expand it first and highlight the expanded code.
66 for event in root.preorder_with_tokens() { 143 for event in root.preorder_with_tokens() {
67 match &event { 144 match &event {
68 WalkEvent::Enter(_) => res.push(Vec::new()), 145 WalkEvent::Enter(_) => stack.push(),
69 WalkEvent::Leave(_) => { 146 WalkEvent::Leave(_) => stack.pop(),
70 /* Flattens the highlighted ranges.
71 *
72 * For example `#[cfg(feature = "foo")]` contains the nested ranges:
73 * 1) parent-range: Attribute [0, 23)
74 * 2) child-range: String [16, 21)
75 *
76 * The following code implements the flattening, for our example this results to:
77 * `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
78 */
79 let children = res.pop().unwrap();
80 let prev = res.last_mut().unwrap();
81 let needs_flattening = !children.is_empty()
82 && !prev.is_empty()
83 && children.first().unwrap().range.is_subrange(&prev.last().unwrap().range);
84 if !needs_flattening {
85 prev.extend(children);
86 } else {
87 let mut parent = prev.pop().unwrap();
88 for ele in children {
89 assert!(ele.range.is_subrange(&parent.range));
90 let mut cloned = parent.clone();
91 parent.range = TextRange::from_to(parent.range.start(), ele.range.start());
92 cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end());
93 if !parent.range.is_empty() {
94 prev.push(parent);
95 }
96 prev.push(ele);
97 parent = cloned;
98 }
99 if !parent.range.is_empty() {
100 prev.push(parent);
101 }
102 }
103 }
104 }; 147 };
105 let current = res.last_mut().expect("during DFS traversal, the stack must not be empty");
106 148
107 let event_range = match &event { 149 let event_range = match &event {
108 WalkEvent::Enter(it) => it.text_range(), 150 WalkEvent::Enter(it) => it.text_range(),
@@ -119,7 +161,7 @@ pub(crate) fn highlight(
119 WalkEvent::Enter(Some(mc)) => { 161 WalkEvent::Enter(Some(mc)) => {
120 current_macro_call = Some(mc.clone()); 162 current_macro_call = Some(mc.clone());
121 if let Some(range) = macro_call_range(&mc) { 163 if let Some(range) = macro_call_range(&mc) {
122 current.push(HighlightedRange { 164 stack.add(HighlightedRange {
123 range, 165 range,
124 highlight: HighlightTag::Macro.into(), 166 highlight: HighlightTag::Macro.into(),
125 binding_hash: None, 167 binding_hash: None,
@@ -130,6 +172,7 @@ pub(crate) fn highlight(
130 WalkEvent::Leave(Some(mc)) => { 172 WalkEvent::Leave(Some(mc)) => {
131 assert!(current_macro_call == Some(mc)); 173 assert!(current_macro_call == Some(mc));
132 current_macro_call = None; 174 current_macro_call = None;
175 format_string = None;
133 continue; 176 continue;
134 } 177 }
135 _ => (), 178 _ => (),
@@ -150,6 +193,30 @@ pub(crate) fn highlight(
150 }; 193 };
151 let token = sema.descend_into_macros(token.clone()); 194 let token = sema.descend_into_macros(token.clone());
152 let parent = token.parent(); 195 let parent = token.parent();
196
197 // Check if macro takes a format string and remember it for highlighting later.
198 // The macros that accept a format string expand to a compiler builtin macros
199 // `format_args` and `format_args_nl`.
200 if let Some(fmt_macro_call) = parent.parent().and_then(ast::MacroCall::cast) {
201 if let Some(name) =
202 fmt_macro_call.path().and_then(|p| p.segment()).and_then(|s| s.name_ref())
203 {
204 match name.text().as_str() {
205 "format_args" | "format_args_nl" => {
206 format_string = parent
207 .children_with_tokens()
208 .filter(|t| t.kind() != WHITESPACE)
209 .nth(1)
210 .filter(|e| {
211 ast::String::can_cast(e.kind())
212 || ast::RawString::can_cast(e.kind())
213 })
214 }
215 _ => {}
216 }
217 }
218 }
219
153 // We only care Name and Name_ref 220 // We only care Name and Name_ref
154 match (token.kind(), parent.kind()) { 221 match (token.kind(), parent.kind()) {
155 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), 222 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
@@ -161,27 +228,72 @@ pub(crate) fn highlight(
161 228
162 if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { 229 if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) {
163 let expanded = element_to_highlight.as_token().unwrap().clone(); 230 let expanded = element_to_highlight.as_token().unwrap().clone();
164 if highlight_injection(current, &sema, token, expanded).is_some() { 231 if highlight_injection(&mut stack, &sema, token, expanded).is_some() {
165 continue; 232 continue;
166 } 233 }
167 } 234 }
168 235
236 let is_format_string = format_string.as_ref() == Some(&element_to_highlight);
237
169 if let Some((highlight, binding_hash)) = 238 if let Some((highlight, binding_hash)) =
170 highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight) 239 highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight.clone())
171 { 240 {
172 current.push(HighlightedRange { range, highlight, binding_hash }); 241 stack.add(HighlightedRange { range, highlight, binding_hash });
242 if let Some(string) =
243 element_to_highlight.as_token().cloned().and_then(ast::String::cast)
244 {
245 stack.push();
246 if is_format_string {
247 string.lex_format_specifier(|piece_range, kind| {
248 if let Some(highlight) = highlight_format_specifier(kind) {
249 stack.add(HighlightedRange {
250 range: piece_range + range.start(),
251 highlight: highlight.into(),
252 binding_hash: None,
253 });
254 }
255 });
256 }
257 stack.pop();
258 } else if let Some(string) =
259 element_to_highlight.as_token().cloned().and_then(ast::RawString::cast)
260 {
261 stack.push();
262 if is_format_string {
263 string.lex_format_specifier(|piece_range, kind| {
264 if let Some(highlight) = highlight_format_specifier(kind) {
265 stack.add(HighlightedRange {
266 range: piece_range + range.start(),
267 highlight: highlight.into(),
268 binding_hash: None,
269 });
270 }
271 });
272 }
273 stack.pop();
274 }
173 } 275 }
174 } 276 }
175 277
176 assert_eq!(res.len(), 1, "after DFS traversal, the stack should only contain a single element"); 278 stack.flattened()
177 let mut res = res.pop().unwrap(); 279}
178 res.sort_by_key(|range| range.range.start()); 280
179 // Check that ranges are sorted and disjoint 281fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> {
180 assert!(res 282 Some(match kind {
181 .iter() 283 FormatSpecifier::Open
182 .zip(res.iter().skip(1)) 284 | FormatSpecifier::Close
183 .all(|(left, right)| left.range.end() <= right.range.start())); 285 | FormatSpecifier::Colon
184 res 286 | FormatSpecifier::Fill
287 | FormatSpecifier::Align
288 | FormatSpecifier::Sign
289 | FormatSpecifier::NumberSign
290 | FormatSpecifier::DollarSign
291 | FormatSpecifier::Dot
292 | FormatSpecifier::Asterisk
293 | FormatSpecifier::QuestionMark => HighlightTag::Attribute,
294 FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral,
295 FormatSpecifier::Identifier => HighlightTag::Local,
296 })
185} 297}
186 298
187fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { 299fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
@@ -359,22 +471,20 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
359} 471}
360 472
361fn highlight_injection( 473fn highlight_injection(
362 acc: &mut Vec<HighlightedRange>, 474 acc: &mut HighlightedRangeStack,
363 sema: &Semantics<RootDatabase>, 475 sema: &Semantics<RootDatabase>,
364 literal: ast::RawString, 476 literal: ast::RawString,
365 expanded: SyntaxToken, 477 expanded: SyntaxToken,
366) -> Option<()> { 478) -> Option<()> {
367 let call_info = call_info_for_token(&sema, expanded)?; 479 let active_parameter = ActiveParameter::at_token(&sema, expanded)?;
368 let idx = call_info.active_parameter?; 480 if !active_parameter.name.starts_with("ra_fixture") {
369 let name = call_info.signature.parameter_names.get(idx)?;
370 if !name.starts_with("ra_fixture") {
371 return None; 481 return None;
372 } 482 }
373 let value = literal.value()?; 483 let value = literal.value()?;
374 let (analysis, tmp_file_id) = Analysis::from_single_file(value); 484 let (analysis, tmp_file_id) = Analysis::from_single_file(value);
375 485
376 if let Some(range) = literal.open_quote_text_range() { 486 if let Some(range) = literal.open_quote_text_range() {
377 acc.push(HighlightedRange { 487 acc.add(HighlightedRange {
378 range, 488 range,
379 highlight: HighlightTag::StringLiteral.into(), 489 highlight: HighlightTag::StringLiteral.into(),
380 binding_hash: None, 490 binding_hash: None,
@@ -384,12 +494,12 @@ fn highlight_injection(
384 for mut h in analysis.highlight(tmp_file_id).unwrap() { 494 for mut h in analysis.highlight(tmp_file_id).unwrap() {
385 if let Some(r) = literal.map_range_up(h.range) { 495 if let Some(r) = literal.map_range_up(h.range) {
386 h.range = r; 496 h.range = r;
387 acc.push(h) 497 acc.add(h)
388 } 498 }
389 } 499 }
390 500
391 if let Some(range) = literal.close_quote_text_range() { 501 if let Some(range) = literal.close_quote_text_range() {
392 acc.push(HighlightedRange { 502 acc.add(HighlightedRange {
393 range, 503 range,
394 highlight: HighlightTag::StringLiteral.into(), 504 highlight: HighlightTag::StringLiteral.into(),
395 binding_hash: None, 505 binding_hash: None,
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index 73611e23a..a9aae957f 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -168,3 +168,73 @@ macro_rules! test {}
168 ); 168 );
169 let _ = analysis.highlight(file_id).unwrap(); 169 let _ = analysis.highlight(file_id).unwrap();
170} 170}
171
172#[test]
173fn test_string_highlighting() {
174 // The format string detection is based on macro-expansion,
175 // thus, we have to copy the macro definition from `std`
176 let (analysis, file_id) = single_file(
177 r#"
178macro_rules! println {
179 ($($arg:tt)*) => ({
180 $crate::io::_print($crate::format_args_nl!($($arg)*));
181 })
182}
183#[rustc_builtin_macro]
184macro_rules! format_args_nl {
185 ($fmt:expr) => {{ /* compiler built-in */ }};
186 ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
187}
188
189fn main() {
190 // from https://doc.rust-lang.org/std/fmt/index.html
191 println!("Hello"); // => "Hello"
192 println!("Hello, {}!", "world"); // => "Hello, world!"
193 println!("The number is {}", 1); // => "The number is 1"
194 println!("{:?}", (3, 4)); // => "(3, 4)"
195 println!("{value}", value=4); // => "4"
196 println!("{} {}", 1, 2); // => "1 2"
197 println!("{:04}", 42); // => "0042" with leading zerosV
198 println!("{1} {} {0} {}", 1, 2); // => "2 1 1 2"
199 println!("{argument}", argument = "test"); // => "test"
200 println!("{name} {}", 1, name = 2); // => "2 1"
201 println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b"
202 println!("Hello {:5}!", "x");
203 println!("Hello {:1$}!", "x", 5);
204 println!("Hello {1:0$}!", 5, "x");
205 println!("Hello {:width$}!", "x", width = 5);
206 println!("Hello {:<5}!", "x");
207 println!("Hello {:-<5}!", "x");
208 println!("Hello {:^5}!", "x");
209 println!("Hello {:>5}!", "x");
210 println!("Hello {:+}!", 5);
211 println!("{:#x}!", 27);
212 println!("Hello {:05}!", 5);
213 println!("Hello {:05}!", -5);
214 println!("{:#010x}!", 27);
215 println!("Hello {0} is {1:.5}", "x", 0.01);
216 println!("Hello {1} is {2:.0$}", 5, "x", 0.01);
217 println!("Hello {0} is {2:.1$}", "x", 5, 0.01);
218 println!("Hello {} is {:.*}", "x", 5, 0.01);
219 println!("Hello {} is {2:.*}", "x", 5, 0.01);
220 println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01);
221 println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56);
222 println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56");
223 println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56");
224 println!("Hello {{}}");
225 println!("{{ Hello");
226
227 println!(r"Hello, {}!", "world");
228
229 println!("{\x41}", A = 92);
230 println!("{ничоси}", ничоси = 92);
231}"#
232 .trim(),
233 );
234
235 let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_strings.html");
236 let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
237 let expected_html = &read_text(&dst_file);
238 fs::write(dst_file, &actual_html).unwrap();
239 assert_eq_text!(expected_html, actual_html);
240}