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