diff options
Diffstat (limited to 'crates/ra_analysis/src/completion/completion_item.rs')
-rw-r--r-- | crates/ra_analysis/src/completion/completion_item.rs | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs new file mode 100644 index 000000000..911f08468 --- /dev/null +++ b/crates/ra_analysis/src/completion/completion_item.rs | |||
@@ -0,0 +1,214 @@ | |||
1 | use crate::db; | ||
2 | |||
3 | /// `CompletionItem` describes a single completion variant in the editor pop-up. | ||
4 | /// It is basically a POD with various properties. To construct a | ||
5 | /// `CompletionItem`, use `new` method and the `Builder` struct. | ||
6 | #[derive(Debug)] | ||
7 | pub struct CompletionItem { | ||
8 | /// Used only internally in tests, to check only specific kind of | ||
9 | /// completion. | ||
10 | completion_kind: CompletionKind, | ||
11 | label: String, | ||
12 | lookup: Option<String>, | ||
13 | snippet: Option<String>, | ||
14 | kind: Option<CompletionItemKind>, | ||
15 | } | ||
16 | |||
17 | pub enum InsertText { | ||
18 | PlainText { text: String }, | ||
19 | Snippet { text: String }, | ||
20 | } | ||
21 | |||
22 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
23 | pub enum CompletionItemKind { | ||
24 | Snippet, | ||
25 | Keyword, | ||
26 | Module, | ||
27 | Function, | ||
28 | Binding, | ||
29 | } | ||
30 | |||
31 | #[derive(Debug, PartialEq, Eq)] | ||
32 | pub(crate) enum CompletionKind { | ||
33 | /// Parser-based keyword completion. | ||
34 | Keyword, | ||
35 | /// Your usual "complete all valid identifiers". | ||
36 | Reference, | ||
37 | /// "Secret sauce" completions. | ||
38 | Magic, | ||
39 | Snippet, | ||
40 | } | ||
41 | |||
42 | impl CompletionItem { | ||
43 | pub(crate) fn new(completion_kind: CompletionKind, label: impl Into<String>) -> Builder { | ||
44 | let label = label.into(); | ||
45 | Builder { | ||
46 | completion_kind, | ||
47 | label, | ||
48 | lookup: None, | ||
49 | snippet: None, | ||
50 | kind: None, | ||
51 | } | ||
52 | } | ||
53 | /// What user sees in pop-up in the UI. | ||
54 | pub fn label(&self) -> &str { | ||
55 | &self.label | ||
56 | } | ||
57 | /// What string is used for filtering. | ||
58 | pub fn lookup(&self) -> &str { | ||
59 | self.lookup | ||
60 | .as_ref() | ||
61 | .map(|it| it.as_str()) | ||
62 | .unwrap_or(self.label()) | ||
63 | } | ||
64 | /// What is inserted. | ||
65 | pub fn insert_text(&self) -> InsertText { | ||
66 | match &self.snippet { | ||
67 | None => InsertText::PlainText { | ||
68 | text: self.label.clone(), | ||
69 | }, | ||
70 | Some(it) => InsertText::Snippet { text: it.clone() }, | ||
71 | } | ||
72 | } | ||
73 | |||
74 | pub fn kind(&self) -> Option<CompletionItemKind> { | ||
75 | self.kind | ||
76 | } | ||
77 | } | ||
78 | |||
79 | /// A helper to make `CompletionItem`s. | ||
80 | #[must_use] | ||
81 | pub(crate) struct Builder { | ||
82 | completion_kind: CompletionKind, | ||
83 | label: String, | ||
84 | lookup: Option<String>, | ||
85 | snippet: Option<String>, | ||
86 | kind: Option<CompletionItemKind>, | ||
87 | } | ||
88 | |||
89 | impl Builder { | ||
90 | pub(crate) fn add_to(self, acc: &mut Completions) { | ||
91 | acc.add(self.build()) | ||
92 | } | ||
93 | |||
94 | pub(crate) fn build(self) -> CompletionItem { | ||
95 | CompletionItem { | ||
96 | label: self.label, | ||
97 | lookup: self.lookup, | ||
98 | snippet: self.snippet, | ||
99 | kind: self.kind, | ||
100 | completion_kind: self.completion_kind, | ||
101 | } | ||
102 | } | ||
103 | pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { | ||
104 | self.lookup = Some(lookup.into()); | ||
105 | self | ||
106 | } | ||
107 | pub(crate) fn snippet(mut self, snippet: impl Into<String>) -> Builder { | ||
108 | self.snippet = Some(snippet.into()); | ||
109 | self | ||
110 | } | ||
111 | pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder { | ||
112 | self.kind = Some(kind); | ||
113 | self | ||
114 | } | ||
115 | pub(crate) fn from_resolution( | ||
116 | mut self, | ||
117 | db: &db::RootDatabase, | ||
118 | resolution: &hir::Resolution, | ||
119 | ) -> Builder { | ||
120 | if let Some(def_id) = resolution.def_id { | ||
121 | if let Ok(def) = def_id.resolve(db) { | ||
122 | let kind = match def { | ||
123 | hir::Def::Module(..) => CompletionItemKind::Module, | ||
124 | hir::Def::Function(..) => CompletionItemKind::Function, | ||
125 | _ => return self, | ||
126 | }; | ||
127 | self.kind = Some(kind); | ||
128 | } | ||
129 | } | ||
130 | self | ||
131 | } | ||
132 | } | ||
133 | |||
134 | impl Into<CompletionItem> for Builder { | ||
135 | fn into(self) -> CompletionItem { | ||
136 | self.build() | ||
137 | } | ||
138 | } | ||
139 | |||
140 | /// Represents an in-progress set of completions being built. | ||
141 | #[derive(Debug, Default)] | ||
142 | pub(crate) struct Completions { | ||
143 | buf: Vec<CompletionItem>, | ||
144 | } | ||
145 | |||
146 | impl Completions { | ||
147 | pub(crate) fn add(&mut self, item: impl Into<CompletionItem>) { | ||
148 | self.buf.push(item.into()) | ||
149 | } | ||
150 | pub(crate) fn add_all<I>(&mut self, items: I) | ||
151 | where | ||
152 | I: IntoIterator, | ||
153 | I::Item: Into<CompletionItem>, | ||
154 | { | ||
155 | items.into_iter().for_each(|item| self.add(item.into())) | ||
156 | } | ||
157 | |||
158 | #[cfg(test)] | ||
159 | pub(crate) fn assert_match(&self, expected: &str, kind: CompletionKind) { | ||
160 | let expected = normalize(expected); | ||
161 | let actual = self.debug_render(kind); | ||
162 | test_utils::assert_eq_text!(expected.as_str(), actual.as_str(),); | ||
163 | |||
164 | /// Normalize the textual representation of `Completions`: | ||
165 | /// replace `;` with newlines, normalize whitespace | ||
166 | fn normalize(expected: &str) -> String { | ||
167 | use ra_syntax::{tokenize, TextUnit, TextRange, SyntaxKind::SEMI}; | ||
168 | let mut res = String::new(); | ||
169 | for line in expected.trim().lines() { | ||
170 | let line = line.trim(); | ||
171 | let mut start_offset: TextUnit = 0.into(); | ||
172 | // Yep, we use rust tokenize in completion tests :-) | ||
173 | for token in tokenize(line) { | ||
174 | let range = TextRange::offset_len(start_offset, token.len); | ||
175 | start_offset += token.len; | ||
176 | if token.kind == SEMI { | ||
177 | res.push('\n'); | ||
178 | } else { | ||
179 | res.push_str(&line[range]); | ||
180 | } | ||
181 | } | ||
182 | |||
183 | res.push('\n'); | ||
184 | } | ||
185 | res | ||
186 | } | ||
187 | } | ||
188 | |||
189 | #[cfg(test)] | ||
190 | fn debug_render(&self, kind: CompletionKind) -> String { | ||
191 | let mut res = String::new(); | ||
192 | for c in self.buf.iter() { | ||
193 | if c.completion_kind == kind { | ||
194 | if let Some(lookup) = &c.lookup { | ||
195 | res.push_str(lookup); | ||
196 | res.push_str(&format!(" {:?}", c.label)); | ||
197 | } else { | ||
198 | res.push_str(&c.label); | ||
199 | } | ||
200 | if let Some(snippet) = &c.snippet { | ||
201 | res.push_str(&format!(" {:?}", snippet)); | ||
202 | } | ||
203 | res.push('\n'); | ||
204 | } | ||
205 | } | ||
206 | res | ||
207 | } | ||
208 | } | ||
209 | |||
210 | impl Into<Vec<CompletionItem>> for Completions { | ||
211 | fn into(self) -> Vec<CompletionItem> { | ||
212 | self.buf | ||
213 | } | ||
214 | } | ||