aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_completion
diff options
context:
space:
mode:
authorLukas Wirth <[email protected]>2021-05-27 22:28:14 +0100
committerLukas Wirth <[email protected]>2021-05-27 22:28:14 +0100
commitfc37e2f953a0d200e875c4711c1b0bf79a75a2a2 (patch)
tree9cd8d24d855969c91d8bf81d14af034856d08674 /crates/ide_completion
parent01bfc5f5c0cf6b73c26d006802016c9b02066f94 (diff)
Attribute completion is context aware
Diffstat (limited to 'crates/ide_completion')
-rw-r--r--crates/ide_completion/Cargo.toml1
-rw-r--r--crates/ide_completion/src/completions/attribute.rs169
2 files changed, 142 insertions, 28 deletions
diff --git a/crates/ide_completion/Cargo.toml b/crates/ide_completion/Cargo.toml
index 6bd8a5500..ba81c9e04 100644
--- a/crates/ide_completion/Cargo.toml
+++ b/crates/ide_completion/Cargo.toml
@@ -15,6 +15,7 @@ itertools = "0.10.0"
15log = "0.4.8" 15log = "0.4.8"
16rustc-hash = "1.1.0" 16rustc-hash = "1.1.0"
17either = "1.6.1" 17either = "1.6.1"
18once_cell = "1.7"
18 19
19stdx = { path = "../stdx", version = "0.0.0" } 20stdx = { path = "../stdx", version = "0.0.0" }
20syntax = { path = "../syntax", version = "0.0.0" } 21syntax = { path = "../syntax", version = "0.0.0" }
diff --git a/crates/ide_completion/src/completions/attribute.rs b/crates/ide_completion/src/completions/attribute.rs
index b1505c74b..0997302a6 100644
--- a/crates/ide_completion/src/completions/attribute.rs
+++ b/crates/ide_completion/src/completions/attribute.rs
@@ -4,8 +4,9 @@
4//! for built-in attributes. 4//! for built-in attributes.
5 5
6use itertools::Itertools; 6use itertools::Itertools;
7use rustc_hash::FxHashSet; 7use once_cell::sync::Lazy;
8use syntax::{ast, AstNode, T}; 8use rustc_hash::{FxHashMap, FxHashSet};
9use syntax::{ast, AstNode, SyntaxKind, T};
9 10
10use crate::{ 11use crate::{
11 context::CompletionContext, 12 context::CompletionContext,
@@ -20,27 +21,34 @@ pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext)
20 } 21 }
21 22
22 let attribute = ctx.attribute_under_caret.as_ref()?; 23 let attribute = ctx.attribute_under_caret.as_ref()?;
23 match (attribute.path(), attribute.token_tree()) { 24 match (attribute.path().and_then(|p| p.as_single_name_ref()), attribute.token_tree()) {
24 (Some(path), Some(token_tree)) => { 25 (Some(path), Some(token_tree)) => match path.text().as_str() {
25 let path = path.syntax().text(); 26 "derive" => complete_derive(acc, ctx, token_tree),
26 if path == "derive" { 27 "feature" => complete_lint(acc, ctx, token_tree, FEATURES),
27 complete_derive(acc, ctx, token_tree) 28 "allow" | "warn" | "deny" | "forbid" => {
28 } else if path == "feature" {
29 complete_lint(acc, ctx, token_tree, FEATURES)
30 } else if path == "allow" || path == "warn" || path == "deny" || path == "forbid" {
31 complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINT_COMPLETIONS); 29 complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINT_COMPLETIONS);
32 complete_lint(acc, ctx, token_tree, CLIPPY_LINTS); 30 complete_lint(acc, ctx, token_tree, CLIPPY_LINTS);
33 } 31 }
34 } 32 _ => (),
35 (_, Some(_token_tree)) => {} 33 },
36 _ => complete_attribute_start(acc, ctx, attribute), 34 (None, Some(_)) => (),
35 _ => complete_new_attribute(acc, ctx, attribute),
37 } 36 }
38 Some(()) 37 Some(())
39} 38}
40 39
41fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { 40fn complete_new_attribute(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) {
41 let attribute_annotated_item_kind = attribute.syntax().parent().map(|it| it.kind());
42 let attributes = attribute_annotated_item_kind.and_then(|kind| {
43 if ast::Expr::can_cast(kind) {
44 Some(EXPR_ATTRIBUTES)
45 } else {
46 KIND_TO_ATTRIBUTES.get(&kind).copied()
47 }
48 });
42 let is_inner = attribute.kind() == ast::AttrKind::Inner; 49 let is_inner = attribute.kind() == ast::AttrKind::Inner;
43 for attr_completion in ATTRIBUTES.iter().filter(|compl| is_inner || !compl.prefer_inner) { 50
51 let add_completion = |attr_completion: &AttrCompletion| {
44 let mut item = CompletionItem::new( 52 let mut item = CompletionItem::new(
45 CompletionKind::Attribute, 53 CompletionKind::Attribute,
46 ctx.source_range(), 54 ctx.source_range(),
@@ -56,9 +64,19 @@ fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attr
56 item.insert_snippet(cap, snippet); 64 item.insert_snippet(cap, snippet);
57 } 65 }
58 66
59 if attribute.kind() == ast::AttrKind::Inner || !attr_completion.prefer_inner { 67 if is_inner || !attr_completion.prefer_inner {
60 acc.add(item.build()); 68 acc.add(item.build());
61 } 69 }
70 };
71
72 match attributes {
73 Some(applicable) => applicable
74 .iter()
75 .flat_map(|name| ATTRIBUTES.binary_search_by(|attr| attr.key().cmp(name)).ok())
76 .flat_map(|idx| ATTRIBUTES.get(idx))
77 .for_each(add_completion),
78 None if is_inner => ATTRIBUTES.iter().for_each(add_completion),
79 None => ATTRIBUTES.iter().filter(|compl| !compl.prefer_inner).for_each(add_completion),
62 } 80 }
63} 81}
64 82
@@ -70,6 +88,10 @@ struct AttrCompletion {
70} 88}
71 89
72impl AttrCompletion { 90impl AttrCompletion {
91 fn key(&self) -> &'static str {
92 self.lookup.unwrap_or(self.label)
93 }
94
73 const fn prefer_inner(self) -> AttrCompletion { 95 const fn prefer_inner(self) -> AttrCompletion {
74 AttrCompletion { prefer_inner: true, ..self } 96 AttrCompletion { prefer_inner: true, ..self }
75 } 97 }
@@ -83,26 +105,81 @@ const fn attr(
83 AttrCompletion { label, lookup, snippet, prefer_inner: false } 105 AttrCompletion { label, lookup, snippet, prefer_inner: false }
84} 106}
85 107
108macro_rules! attrs {
109 [$($($mac:ident!),+;)? $($key:literal),*] => {
110 &["allow", "cfg", "cfg_attr", "deny", "forbid", "warn", $($($mac!()),+,)? $($key),*] as _
111 }
112}
113macro_rules! item_attrs {
114 () => {
115 "deprecated"
116 };
117}
118
119static KIND_TO_ATTRIBUTES: Lazy<FxHashMap<SyntaxKind, &[&str]>> = Lazy::new(|| {
120 std::array::IntoIter::new([
121 (SyntaxKind::SOURCE_FILE, attrs!(item_attrs!;"crate_name")),
122 (SyntaxKind::MODULE, attrs!(item_attrs!;)),
123 (SyntaxKind::ITEM_LIST, attrs!(item_attrs!;)),
124 (SyntaxKind::MACRO_RULES, attrs!(item_attrs!;)),
125 (SyntaxKind::MACRO_DEF, attrs!(item_attrs!;)),
126 (SyntaxKind::EXTERN_CRATE, attrs!(item_attrs!;)),
127 (SyntaxKind::USE, attrs!(item_attrs!;)),
128 (SyntaxKind::FN, attrs!(item_attrs!;"cold", "must_use")),
129 (SyntaxKind::TYPE_ALIAS, attrs!(item_attrs!;)),
130 (SyntaxKind::STRUCT, attrs!(item_attrs!;"must_use")),
131 (SyntaxKind::ENUM, attrs!(item_attrs!;"must_use")),
132 (SyntaxKind::UNION, attrs!(item_attrs!;"must_use")),
133 (SyntaxKind::CONST, attrs!(item_attrs!;)),
134 (SyntaxKind::STATIC, attrs!(item_attrs!;)),
135 (SyntaxKind::TRAIT, attrs!(item_attrs!; "must_use")),
136 (SyntaxKind::IMPL, attrs!(item_attrs!;"automatically_derived")),
137 (SyntaxKind::ASSOC_ITEM_LIST, attrs!(item_attrs!;)),
138 (SyntaxKind::EXTERN_BLOCK, attrs!(item_attrs!;)),
139 (SyntaxKind::EXTERN_ITEM_LIST, attrs!(item_attrs!;)),
140 (SyntaxKind::MACRO_CALL, attrs!()),
141 (SyntaxKind::SELF_PARAM, attrs!()),
142 (SyntaxKind::PARAM, attrs!()),
143 (SyntaxKind::RECORD_FIELD, attrs!()),
144 (SyntaxKind::VARIANT, attrs!()),
145 (SyntaxKind::TYPE_PARAM, attrs!()),
146 (SyntaxKind::CONST_PARAM, attrs!()),
147 (SyntaxKind::LIFETIME_PARAM, attrs!()),
148 (SyntaxKind::LET_STMT, attrs!()),
149 (SyntaxKind::EXPR_STMT, attrs!()),
150 (SyntaxKind::LITERAL, attrs!()),
151 (SyntaxKind::RECORD_EXPR_FIELD_LIST, attrs!()),
152 (SyntaxKind::RECORD_EXPR_FIELD, attrs!()),
153 (SyntaxKind::MATCH_ARM_LIST, attrs!()),
154 (SyntaxKind::MATCH_ARM, attrs!()),
155 (SyntaxKind::IDENT_PAT, attrs!()),
156 (SyntaxKind::RECORD_PAT_FIELD, attrs!()),
157 ])
158 .collect()
159});
160const EXPR_ATTRIBUTES: &[&str] = attrs!();
161
86/// https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index 162/// https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index
163// Keep these sorted for the binary search!
87const ATTRIBUTES: &[AttrCompletion] = &[ 164const ATTRIBUTES: &[AttrCompletion] = &[
88 attr("allow(…)", Some("allow"), Some("allow(${0:lint})")), 165 attr("allow(…)", Some("allow"), Some("allow(${0:lint})")),
89 attr("automatically_derived", None, None), 166 attr("automatically_derived", None, None),
90 attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")),
91 attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")), 167 attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")),
168 attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")),
92 attr("cold", None, None), 169 attr("cold", None, None),
93 attr(r#"crate_name = """#, Some("crate_name"), Some(r#"crate_name = "${0:crate_name}""#)) 170 attr(r#"crate_name = """#, Some("crate_name"), Some(r#"crate_name = "${0:crate_name}""#))
94 .prefer_inner(), 171 .prefer_inner(),
95 attr("deny(…)", Some("deny"), Some("deny(${0:lint})")), 172 attr("deny(…)", Some("deny"), Some("deny(${0:lint})")),
96 attr(r#"deprecated"#, Some("deprecated"), Some(r#"deprecated"#)), 173 attr(r#"deprecated"#, Some("deprecated"), Some(r#"deprecated"#)),
97 attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)), 174 attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)),
175 attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)),
176 attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)),
177 attr(r#"doc(hidden)"#, Some("dochidden"), Some(r#"doc(hidden)"#)),
98 attr( 178 attr(
99 r#"export_name = "…""#, 179 r#"export_name = "…""#,
100 Some("export_name"), 180 Some("export_name"),
101 Some(r#"export_name = "${0:exported_symbol_name}""#), 181 Some(r#"export_name = "${0:exported_symbol_name}""#),
102 ), 182 ),
103 attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)),
104 attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)),
105 attr(r#"doc(hidden)"#, Some("dochidden"), Some(r#"doc(hidden)"#)),
106 attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(), 183 attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(),
107 attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")), 184 attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")),
108 // FIXME: resolve through macro resolution? 185 // FIXME: resolve through macro resolution?
@@ -119,8 +196,8 @@ const ATTRIBUTES: &[AttrCompletion] = &[
119 attr("macro_export", None, None), 196 attr("macro_export", None, None),
120 attr("macro_use", None, None), 197 attr("macro_use", None, None),
121 attr(r#"must_use"#, Some("must_use"), Some(r#"must_use"#)), 198 attr(r#"must_use"#, Some("must_use"), Some(r#"must_use"#)),
122 attr("no_link", None, None).prefer_inner(),
123 attr("no_implicit_prelude", None, None).prefer_inner(), 199 attr("no_implicit_prelude", None, None).prefer_inner(),
200 attr("no_link", None, None).prefer_inner(),
124 attr("no_main", None, None).prefer_inner(), 201 attr("no_main", None, None).prefer_inner(),
125 attr("no_mangle", None, None), 202 attr("no_mangle", None, None),
126 attr("no_std", None, None).prefer_inner(), 203 attr("no_std", None, None).prefer_inner(),
@@ -153,6 +230,22 @@ const ATTRIBUTES: &[AttrCompletion] = &[
153 .prefer_inner(), 230 .prefer_inner(),
154]; 231];
155 232
233#[test]
234fn attributes_are_sorted() {
235 let mut attrs = ATTRIBUTES.iter().map(|attr| attr.key());
236 let mut prev = attrs.next().unwrap();
237
238 attrs.for_each(|next| {
239 assert!(
240 prev < next,
241 r#"Attributes are not sorted, "{}" should come after "{}""#,
242 prev,
243 next
244 );
245 prev = next;
246 });
247}
248
156fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { 249fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) {
157 if let Ok(existing_derives) = parse_comma_sep_input(derive_input) { 250 if let Ok(existing_derives) = parse_comma_sep_input(derive_input) {
158 for derive_completion in DEFAULT_DERIVE_COMPLETIONS 251 for derive_completion in DEFAULT_DERIVE_COMPLETIONS
@@ -410,6 +503,26 @@ mod tests {
410 } 503 }
411 504
412 #[test] 505 #[test]
506 fn complete_attribute_on_struct() {
507 check(
508 r#"
509#[$0]
510struct Test {}
511 "#,
512 expect![[r#"
513 at allow(…)
514 at cfg(…)
515 at cfg_attr(…)
516 at deny(…)
517 at forbid(…)
518 at warn(…)
519 at deprecated
520 at must_use
521 "#]],
522 );
523 }
524
525 #[test]
413 fn empty_derive_completion() { 526 fn empty_derive_completion() {
414 check( 527 check(
415 r#" 528 r#"
@@ -468,16 +581,16 @@ struct Test {}
468 expect![[r#" 581 expect![[r#"
469 at allow(…) 582 at allow(…)
470 at automatically_derived 583 at automatically_derived
471 at cfg_attr(…)
472 at cfg(…) 584 at cfg(…)
585 at cfg_attr(…)
473 at cold 586 at cold
474 at deny(…) 587 at deny(…)
475 at deprecated 588 at deprecated
476 at derive(…) 589 at derive(…)
477 at export_name = "…"
478 at doc(alias = "…")
479 at doc = "…" 590 at doc = "…"
591 at doc(alias = "…")
480 at doc(hidden) 592 at doc(hidden)
593 at export_name = "…"
481 at forbid(…) 594 at forbid(…)
482 at ignore = "…" 595 at ignore = "…"
483 at inline 596 at inline
@@ -516,17 +629,17 @@ struct Test {}
516 expect![[r#" 629 expect![[r#"
517 at allow(…) 630 at allow(…)
518 at automatically_derived 631 at automatically_derived
519 at cfg_attr(…)
520 at cfg(…) 632 at cfg(…)
633 at cfg_attr(…)
521 at cold 634 at cold
522 at crate_name = "" 635 at crate_name = ""
523 at deny(…) 636 at deny(…)
524 at deprecated 637 at deprecated
525 at derive(…) 638 at derive(…)
526 at export_name = "…"
527 at doc(alias = "…")
528 at doc = "…" 639 at doc = "…"
640 at doc(alias = "…")
529 at doc(hidden) 641 at doc(hidden)
642 at export_name = "…"
530 at feature(…) 643 at feature(…)
531 at forbid(…) 644 at forbid(…)
532 at global_allocator 645 at global_allocator
@@ -538,8 +651,8 @@ struct Test {}
538 at macro_export 651 at macro_export
539 at macro_use 652 at macro_use
540 at must_use 653 at must_use
541 at no_link
542 at no_implicit_prelude 654 at no_implicit_prelude
655 at no_link
543 at no_main 656 at no_main
544 at no_mangle 657 at no_mangle
545 at no_std 658 at no_std