aboutsummaryrefslogtreecommitdiff
path: root/crates/completion/src/completion_item.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/completion/src/completion_item.rs')
-rw-r--r--crates/completion/src/completion_item.rs403
1 files changed, 403 insertions, 0 deletions
diff --git a/crates/completion/src/completion_item.rs b/crates/completion/src/completion_item.rs
new file mode 100644
index 000000000..2e1ca0e59
--- /dev/null
+++ b/crates/completion/src/completion_item.rs
@@ -0,0 +1,403 @@
1//! See `CompletionItem` structure.
2
3use std::fmt;
4
5use hir::{Documentation, Mutability};
6use syntax::TextRange;
7use text_edit::TextEdit;
8
9use crate::completion_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.
14pub 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.
66impl 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)]
103pub 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)]
111pub 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
133impl 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)]
161pub(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)]
175pub enum InsertTextFormat {
176 PlainText,
177 Snippet,
178}
179
180impl 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)]
257pub(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
274impl Builder {
275 pub(crate) fn add_to(self, acc: &mut Completions) {
276 acc.add(self.build())
277 }
278
279 pub(crate) fn build(self) -> CompletionItem {
280 let label = self.label;
281 let text_edit = match self.text_edit {
282 Some(it) => it,
283 None => TextEdit::replace(
284 self.source_range,
285 self.insert_text.unwrap_or_else(|| label.clone()),
286 ),
287 };
288
289 CompletionItem {
290 source_range: self.source_range,
291 label,
292 insert_text_format: self.insert_text_format,
293 text_edit,
294 detail: self.detail,
295 documentation: self.documentation,
296 lookup: self.lookup,
297 kind: self.kind,
298 completion_kind: self.completion_kind,
299 deprecated: self.deprecated.unwrap_or(false),
300 trigger_call_info: self.trigger_call_info.unwrap_or(false),
301 score: self.score,
302 ref_match: self.ref_match,
303 }
304 }
305 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
306 self.lookup = Some(lookup.into());
307 self
308 }
309 pub(crate) fn label(mut self, label: impl Into<String>) -> Builder {
310 self.label = label.into();
311 self
312 }
313 pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder {
314 self.insert_text = Some(insert_text.into());
315 self
316 }
317 pub(crate) fn insert_snippet(
318 mut self,
319 _cap: SnippetCap,
320 snippet: impl Into<String>,
321 ) -> Builder {
322 self.insert_text_format = InsertTextFormat::Snippet;
323 self.insert_text(snippet)
324 }
325 pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder {
326 self.kind = Some(kind);
327 self
328 }
329 pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder {
330 self.text_edit = Some(edit);
331 self
332 }
333 pub(crate) fn snippet_edit(mut self, _cap: SnippetCap, edit: TextEdit) -> Builder {
334 self.insert_text_format = InsertTextFormat::Snippet;
335 self.text_edit(edit)
336 }
337 #[allow(unused)]
338 pub(crate) fn detail(self, detail: impl Into<String>) -> Builder {
339 self.set_detail(Some(detail))
340 }
341 pub(crate) fn set_detail(mut self, detail: Option<impl Into<String>>) -> Builder {
342 self.detail = detail.map(Into::into);
343 self
344 }
345 #[allow(unused)]
346 pub(crate) fn documentation(self, docs: Documentation) -> Builder {
347 self.set_documentation(Some(docs))
348 }
349 pub(crate) fn set_documentation(mut self, docs: Option<Documentation>) -> Builder {
350 self.documentation = docs.map(Into::into);
351 self
352 }
353 pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder {
354 self.deprecated = Some(deprecated);
355 self
356 }
357 pub(crate) fn set_score(mut self, score: CompletionScore) -> Builder {
358 self.score = Some(score);
359 self
360 }
361 pub(crate) fn trigger_call_info(mut self) -> Builder {
362 self.trigger_call_info = Some(true);
363 self
364 }
365 pub(crate) fn set_ref_match(
366 mut self,
367 ref_match: Option<(Mutability, CompletionScore)>,
368 ) -> Builder {
369 self.ref_match = ref_match;
370 self
371 }
372}
373
374impl<'a> Into<CompletionItem> for Builder {
375 fn into(self) -> CompletionItem {
376 self.build()
377 }
378}
379
380/// Represents an in-progress set of completions being built.
381#[derive(Debug, Default)]
382pub struct Completions {
383 buf: Vec<CompletionItem>,
384}
385
386impl Completions {
387 pub fn add(&mut self, item: impl Into<CompletionItem>) {
388 self.buf.push(item.into())
389 }
390 pub fn add_all<I>(&mut self, items: I)
391 where
392 I: IntoIterator,
393 I::Item: Into<CompletionItem>,
394 {
395 items.into_iter().for_each(|item| self.add(item.into()))
396 }
397}
398
399impl Into<Vec<CompletionItem>> for Completions {
400 fn into(self) -> Vec<CompletionItem> {
401 self.buf
402 }
403}