aboutsummaryrefslogtreecommitdiff
path: root/crates/completion/src/item.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/completion/src/item.rs')
-rw-r--r--crates/completion/src/item.rs374
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
3use std::fmt;
4
5use hir::{Documentation, Mutability};
6use syntax::TextRange;
7use text_edit::TextEdit;
8
9use 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.
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 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
370impl<'a> Into<CompletionItem> for Builder {
371 fn into(self) -> CompletionItem {
372 self.build()
373 }
374}