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