diff options
Diffstat (limited to 'crates/completion/src/item.rs')
-rw-r--r-- | crates/completion/src/item.rs | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs new file mode 100644 index 000000000..6d1d085f4 --- /dev/null +++ b/crates/completion/src/item.rs | |||
@@ -0,0 +1,374 @@ | |||
1 | //! See `CompletionItem` structure. | ||
2 | |||
3 | use std::fmt; | ||
4 | |||
5 | use hir::{Documentation, Mutability}; | ||
6 | use syntax::TextRange; | ||
7 | use text_edit::TextEdit; | ||
8 | |||
9 | use crate::config::SnippetCap; | ||
10 | |||
11 | /// `CompletionItem` describes a single completion variant in the editor pop-up. | ||
12 | /// It is basically a POD with various properties. To construct a | ||
13 | /// `CompletionItem`, use `new` method and the `Builder` struct. | ||
14 | pub struct CompletionItem { | ||
15 | /// Used only internally in tests, to check only specific kind of | ||
16 | /// completion (postfix, keyword, reference, etc). | ||
17 | #[allow(unused)] | ||
18 | pub(crate) completion_kind: CompletionKind, | ||
19 | /// Label in the completion pop up which identifies completion. | ||
20 | label: String, | ||
21 | /// Range of identifier that is being completed. | ||
22 | /// | ||
23 | /// It should be used primarily for UI, but we also use this to convert | ||
24 | /// genetic TextEdit into LSP's completion edit (see conv.rs). | ||
25 | /// | ||
26 | /// `source_range` must contain the completion offset. `insert_text` should | ||
27 | /// start with what `source_range` points to, or VSCode will filter out the | ||
28 | /// completion silently. | ||
29 | source_range: TextRange, | ||
30 | /// What happens when user selects this item. | ||
31 | /// | ||
32 | /// Typically, replaces `source_range` with new identifier. | ||
33 | text_edit: TextEdit, | ||
34 | insert_text_format: InsertTextFormat, | ||
35 | |||
36 | /// What item (struct, function, etc) are we completing. | ||
37 | kind: Option<CompletionItemKind>, | ||
38 | |||
39 | /// Lookup is used to check if completion item indeed can complete current | ||
40 | /// ident. | ||
41 | /// | ||
42 | /// That is, in `foo.bar<|>` lookup of `abracadabra` will be accepted (it | ||
43 | /// contains `bar` sub sequence), and `quux` will rejected. | ||
44 | lookup: Option<String>, | ||
45 | |||
46 | /// Additional info to show in the UI pop up. | ||
47 | detail: Option<String>, | ||
48 | documentation: Option<Documentation>, | ||
49 | |||
50 | /// Whether this item is marked as deprecated | ||
51 | deprecated: bool, | ||
52 | |||
53 | /// If completing a function call, ask the editor to show parameter popup | ||
54 | /// after completion. | ||
55 | trigger_call_info: bool, | ||
56 | |||
57 | /// Score is useful to pre select or display in better order completion items | ||
58 | score: Option<CompletionScore>, | ||
59 | |||
60 | /// Indicates that a reference or mutable reference to this variable is a | ||
61 | /// possible match. | ||
62 | ref_match: Option<(Mutability, CompletionScore)>, | ||
63 | } | ||
64 | |||
65 | // We use custom debug for CompletionItem to make snapshot tests more readable. | ||
66 | impl fmt::Debug for CompletionItem { | ||
67 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
68 | let mut s = f.debug_struct("CompletionItem"); | ||
69 | s.field("label", &self.label()).field("source_range", &self.source_range()); | ||
70 | if self.text_edit().len() == 1 { | ||
71 | let atom = &self.text_edit().iter().next().unwrap(); | ||
72 | s.field("delete", &atom.delete); | ||
73 | s.field("insert", &atom.insert); | ||
74 | } else { | ||
75 | s.field("text_edit", &self.text_edit); | ||
76 | } | ||
77 | if let Some(kind) = self.kind().as_ref() { | ||
78 | s.field("kind", kind); | ||
79 | } | ||
80 | if self.lookup() != self.label() { | ||
81 | s.field("lookup", &self.lookup()); | ||
82 | } | ||
83 | if let Some(detail) = self.detail() { | ||
84 | s.field("detail", &detail); | ||
85 | } | ||
86 | if let Some(documentation) = self.documentation() { | ||
87 | s.field("documentation", &documentation); | ||
88 | } | ||
89 | if self.deprecated { | ||
90 | s.field("deprecated", &true); | ||
91 | } | ||
92 | if let Some(score) = &self.score { | ||
93 | s.field("score", score); | ||
94 | } | ||
95 | if self.trigger_call_info { | ||
96 | s.field("trigger_call_info", &true); | ||
97 | } | ||
98 | s.finish() | ||
99 | } | ||
100 | } | ||
101 | |||
102 | #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)] | ||
103 | pub enum CompletionScore { | ||
104 | /// If only type match | ||
105 | TypeMatch, | ||
106 | /// If type and name match | ||
107 | TypeAndNameMatch, | ||
108 | } | ||
109 | |||
110 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
111 | pub enum CompletionItemKind { | ||
112 | Snippet, | ||
113 | Keyword, | ||
114 | Module, | ||
115 | Function, | ||
116 | BuiltinType, | ||
117 | Struct, | ||
118 | Enum, | ||
119 | EnumVariant, | ||
120 | Binding, | ||
121 | Field, | ||
122 | Static, | ||
123 | Const, | ||
124 | Trait, | ||
125 | TypeAlias, | ||
126 | Method, | ||
127 | TypeParam, | ||
128 | Macro, | ||
129 | Attribute, | ||
130 | UnresolvedReference, | ||
131 | } | ||
132 | |||
133 | impl CompletionItemKind { | ||
134 | #[cfg(test)] | ||
135 | pub(crate) fn tag(&self) -> &'static str { | ||
136 | match self { | ||
137 | CompletionItemKind::Attribute => "at", | ||
138 | CompletionItemKind::Binding => "bn", | ||
139 | CompletionItemKind::BuiltinType => "bt", | ||
140 | CompletionItemKind::Const => "ct", | ||
141 | CompletionItemKind::Enum => "en", | ||
142 | CompletionItemKind::EnumVariant => "ev", | ||
143 | CompletionItemKind::Field => "fd", | ||
144 | CompletionItemKind::Function => "fn", | ||
145 | CompletionItemKind::Keyword => "kw", | ||
146 | CompletionItemKind::Macro => "ma", | ||
147 | CompletionItemKind::Method => "me", | ||
148 | CompletionItemKind::Module => "md", | ||
149 | CompletionItemKind::Snippet => "sn", | ||
150 | CompletionItemKind::Static => "sc", | ||
151 | CompletionItemKind::Struct => "st", | ||
152 | CompletionItemKind::Trait => "tt", | ||
153 | CompletionItemKind::TypeAlias => "ta", | ||
154 | CompletionItemKind::TypeParam => "tp", | ||
155 | CompletionItemKind::UnresolvedReference => "??", | ||
156 | } | ||
157 | } | ||
158 | } | ||
159 | |||
160 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] | ||
161 | pub(crate) enum CompletionKind { | ||
162 | /// Parser-based keyword completion. | ||
163 | Keyword, | ||
164 | /// Your usual "complete all valid identifiers". | ||
165 | Reference, | ||
166 | /// "Secret sauce" completions. | ||
167 | Magic, | ||
168 | Snippet, | ||
169 | Postfix, | ||
170 | BuiltinType, | ||
171 | Attribute, | ||
172 | } | ||
173 | |||
174 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] | ||
175 | pub enum InsertTextFormat { | ||
176 | PlainText, | ||
177 | Snippet, | ||
178 | } | ||
179 | |||
180 | impl CompletionItem { | ||
181 | pub(crate) fn new( | ||
182 | completion_kind: CompletionKind, | ||
183 | source_range: TextRange, | ||
184 | label: impl Into<String>, | ||
185 | ) -> Builder { | ||
186 | let label = label.into(); | ||
187 | Builder { | ||
188 | source_range, | ||
189 | completion_kind, | ||
190 | label, | ||
191 | insert_text: None, | ||
192 | insert_text_format: InsertTextFormat::PlainText, | ||
193 | detail: None, | ||
194 | documentation: None, | ||
195 | lookup: None, | ||
196 | kind: None, | ||
197 | text_edit: None, | ||
198 | deprecated: None, | ||
199 | trigger_call_info: None, | ||
200 | score: None, | ||
201 | ref_match: None, | ||
202 | } | ||
203 | } | ||
204 | /// What user sees in pop-up in the UI. | ||
205 | pub fn label(&self) -> &str { | ||
206 | &self.label | ||
207 | } | ||
208 | pub fn source_range(&self) -> TextRange { | ||
209 | self.source_range | ||
210 | } | ||
211 | |||
212 | pub fn insert_text_format(&self) -> InsertTextFormat { | ||
213 | self.insert_text_format | ||
214 | } | ||
215 | |||
216 | pub fn text_edit(&self) -> &TextEdit { | ||
217 | &self.text_edit | ||
218 | } | ||
219 | |||
220 | /// Short one-line additional information, like a type | ||
221 | pub fn detail(&self) -> Option<&str> { | ||
222 | self.detail.as_deref() | ||
223 | } | ||
224 | /// A doc-comment | ||
225 | pub fn documentation(&self) -> Option<Documentation> { | ||
226 | self.documentation.clone() | ||
227 | } | ||
228 | /// What string is used for filtering. | ||
229 | pub fn lookup(&self) -> &str { | ||
230 | self.lookup.as_deref().unwrap_or(&self.label) | ||
231 | } | ||
232 | |||
233 | pub fn kind(&self) -> Option<CompletionItemKind> { | ||
234 | self.kind | ||
235 | } | ||
236 | |||
237 | pub fn deprecated(&self) -> bool { | ||
238 | self.deprecated | ||
239 | } | ||
240 | |||
241 | pub fn score(&self) -> Option<CompletionScore> { | ||
242 | self.score | ||
243 | } | ||
244 | |||
245 | pub fn trigger_call_info(&self) -> bool { | ||
246 | self.trigger_call_info | ||
247 | } | ||
248 | |||
249 | pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> { | ||
250 | self.ref_match | ||
251 | } | ||
252 | } | ||
253 | |||
254 | /// A helper to make `CompletionItem`s. | ||
255 | #[must_use] | ||
256 | #[derive(Clone)] | ||
257 | pub(crate) struct Builder { | ||
258 | source_range: TextRange, | ||
259 | completion_kind: CompletionKind, | ||
260 | label: String, | ||
261 | insert_text: Option<String>, | ||
262 | insert_text_format: InsertTextFormat, | ||
263 | detail: Option<String>, | ||
264 | documentation: Option<Documentation>, | ||
265 | lookup: Option<String>, | ||
266 | kind: Option<CompletionItemKind>, | ||
267 | text_edit: Option<TextEdit>, | ||
268 | deprecated: Option<bool>, | ||
269 | trigger_call_info: Option<bool>, | ||
270 | score: Option<CompletionScore>, | ||
271 | ref_match: Option<(Mutability, CompletionScore)>, | ||
272 | } | ||
273 | |||
274 | impl Builder { | ||
275 | pub(crate) fn build(self) -> CompletionItem { | ||
276 | let label = self.label; | ||
277 | let text_edit = match self.text_edit { | ||
278 | Some(it) => it, | ||
279 | None => TextEdit::replace( | ||
280 | self.source_range, | ||
281 | self.insert_text.unwrap_or_else(|| label.clone()), | ||
282 | ), | ||
283 | }; | ||
284 | |||
285 | CompletionItem { | ||
286 | source_range: self.source_range, | ||
287 | label, | ||
288 | insert_text_format: self.insert_text_format, | ||
289 | text_edit, | ||
290 | detail: self.detail, | ||
291 | documentation: self.documentation, | ||
292 | lookup: self.lookup, | ||
293 | kind: self.kind, | ||
294 | completion_kind: self.completion_kind, | ||
295 | deprecated: self.deprecated.unwrap_or(false), | ||
296 | trigger_call_info: self.trigger_call_info.unwrap_or(false), | ||
297 | score: self.score, | ||
298 | ref_match: self.ref_match, | ||
299 | } | ||
300 | } | ||
301 | pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { | ||
302 | self.lookup = Some(lookup.into()); | ||
303 | self | ||
304 | } | ||
305 | pub(crate) fn label(mut self, label: impl Into<String>) -> Builder { | ||
306 | self.label = label.into(); | ||
307 | self | ||
308 | } | ||
309 | pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder { | ||
310 | self.insert_text = Some(insert_text.into()); | ||
311 | self | ||
312 | } | ||
313 | pub(crate) fn insert_snippet( | ||
314 | mut self, | ||
315 | _cap: SnippetCap, | ||
316 | snippet: impl Into<String>, | ||
317 | ) -> Builder { | ||
318 | self.insert_text_format = InsertTextFormat::Snippet; | ||
319 | self.insert_text(snippet) | ||
320 | } | ||
321 | pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder { | ||
322 | self.kind = Some(kind); | ||
323 | self | ||
324 | } | ||
325 | pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder { | ||
326 | self.text_edit = Some(edit); | ||
327 | self | ||
328 | } | ||
329 | pub(crate) fn snippet_edit(mut self, _cap: SnippetCap, edit: TextEdit) -> Builder { | ||
330 | self.insert_text_format = InsertTextFormat::Snippet; | ||
331 | self.text_edit(edit) | ||
332 | } | ||
333 | #[allow(unused)] | ||
334 | pub(crate) fn detail(self, detail: impl Into<String>) -> Builder { | ||
335 | self.set_detail(Some(detail)) | ||
336 | } | ||
337 | pub(crate) fn set_detail(mut self, detail: Option<impl Into<String>>) -> Builder { | ||
338 | self.detail = detail.map(Into::into); | ||
339 | self | ||
340 | } | ||
341 | #[allow(unused)] | ||
342 | pub(crate) fn documentation(self, docs: Documentation) -> Builder { | ||
343 | self.set_documentation(Some(docs)) | ||
344 | } | ||
345 | pub(crate) fn set_documentation(mut self, docs: Option<Documentation>) -> Builder { | ||
346 | self.documentation = docs.map(Into::into); | ||
347 | self | ||
348 | } | ||
349 | pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder { | ||
350 | self.deprecated = Some(deprecated); | ||
351 | self | ||
352 | } | ||
353 | pub(crate) fn set_score(mut self, score: CompletionScore) -> Builder { | ||
354 | self.score = Some(score); | ||
355 | self | ||
356 | } | ||
357 | pub(crate) fn trigger_call_info(mut self) -> Builder { | ||
358 | self.trigger_call_info = Some(true); | ||
359 | self | ||
360 | } | ||
361 | pub(crate) fn set_ref_match( | ||
362 | mut self, | ||
363 | ref_match: Option<(Mutability, CompletionScore)>, | ||
364 | ) -> Builder { | ||
365 | self.ref_match = ref_match; | ||
366 | self | ||
367 | } | ||
368 | } | ||
369 | |||
370 | impl<'a> Into<CompletionItem> for Builder { | ||
371 | fn into(self) -> CompletionItem { | ||
372 | self.build() | ||
373 | } | ||
374 | } | ||