aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/completion/completion_item.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/completion/completion_item.rs')
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs322
1 files changed, 322 insertions, 0 deletions
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs
new file mode 100644
index 000000000..93f336370
--- /dev/null
+++ b/crates/ra_ide/src/completion/completion_item.rs
@@ -0,0 +1,322 @@
1//! FIXME: write short doc here
2
3use std::fmt;
4
5use hir::Documentation;
6use ra_syntax::TextRange;
7use ra_text_edit::TextEdit;
8
9/// `CompletionItem` describes a single completion variant in the editor pop-up.
10/// It is basically a POD with various properties. To construct a
11/// `CompletionItem`, use `new` method and the `Builder` struct.
12pub struct CompletionItem {
13 /// Used only internally in tests, to check only specific kind of
14 /// completion (postfix, keyword, reference, etc).
15 #[allow(unused)]
16 completion_kind: CompletionKind,
17 /// Label in the completion pop up which identifies completion.
18 label: String,
19 /// Range of identifier that is being completed.
20 ///
21 /// It should be used primarily for UI, but we also use this to convert
22 /// genetic TextEdit into LSP's completion edit (see conv.rs).
23 ///
24 /// `source_range` must contain the completion offset. `insert_text` should
25 /// start with what `source_range` points to, or VSCode will filter out the
26 /// completion silently.
27 source_range: TextRange,
28 /// What happens when user selects this item.
29 ///
30 /// Typically, replaces `source_range` with new identifier.
31 text_edit: TextEdit,
32 insert_text_format: InsertTextFormat,
33
34 /// What item (struct, function, etc) are we completing.
35 kind: Option<CompletionItemKind>,
36
37 /// Lookup is used to check if completion item indeed can complete current
38 /// ident.
39 ///
40 /// That is, in `foo.bar<|>` lookup of `abracadabra` will be accepted (it
41 /// contains `bar` sub sequence), and `quux` will rejected.
42 lookup: Option<String>,
43
44 /// Additional info to show in the UI pop up.
45 detail: Option<String>,
46 documentation: Option<Documentation>,
47
48 /// Whether this item is marked as deprecated
49 deprecated: bool,
50}
51
52// We use custom debug for CompletionItem to make `insta`'s diffs more readable.
53impl fmt::Debug for CompletionItem {
54 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
55 let mut s = f.debug_struct("CompletionItem");
56 s.field("label", &self.label()).field("source_range", &self.source_range());
57 if self.text_edit().as_atoms().len() == 1 {
58 let atom = &self.text_edit().as_atoms()[0];
59 s.field("delete", &atom.delete);
60 s.field("insert", &atom.insert);
61 } else {
62 s.field("text_edit", &self.text_edit);
63 }
64 if let Some(kind) = self.kind().as_ref() {
65 s.field("kind", kind);
66 }
67 if self.lookup() != self.label() {
68 s.field("lookup", &self.lookup());
69 }
70 if let Some(detail) = self.detail() {
71 s.field("detail", &detail);
72 }
73 if let Some(documentation) = self.documentation() {
74 s.field("documentation", &documentation);
75 }
76 if self.deprecated {
77 s.field("deprecated", &true);
78 }
79 s.finish()
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum CompletionItemKind {
85 Snippet,
86 Keyword,
87 Module,
88 Function,
89 BuiltinType,
90 Struct,
91 Enum,
92 EnumVariant,
93 Binding,
94 Field,
95 Static,
96 Const,
97 Trait,
98 TypeAlias,
99 Method,
100 TypeParam,
101 Macro,
102}
103
104#[derive(Debug, PartialEq, Eq, Copy, Clone)]
105pub(crate) enum CompletionKind {
106 /// Parser-based keyword completion.
107 Keyword,
108 /// Your usual "complete all valid identifiers".
109 Reference,
110 /// "Secret sauce" completions.
111 Magic,
112 Snippet,
113 Postfix,
114 BuiltinType,
115}
116
117#[derive(Debug, PartialEq, Eq, Copy, Clone)]
118pub enum InsertTextFormat {
119 PlainText,
120 Snippet,
121}
122
123impl CompletionItem {
124 pub(crate) fn new(
125 completion_kind: CompletionKind,
126 source_range: TextRange,
127 label: impl Into<String>,
128 ) -> Builder {
129 let label = label.into();
130 Builder {
131 source_range,
132 completion_kind,
133 label,
134 insert_text: None,
135 insert_text_format: InsertTextFormat::PlainText,
136 detail: None,
137 documentation: None,
138 lookup: None,
139 kind: None,
140 text_edit: None,
141 deprecated: None,
142 }
143 }
144 /// What user sees in pop-up in the UI.
145 pub fn label(&self) -> &str {
146 &self.label
147 }
148 pub fn source_range(&self) -> TextRange {
149 self.source_range
150 }
151
152 pub fn insert_text_format(&self) -> InsertTextFormat {
153 self.insert_text_format
154 }
155
156 pub fn text_edit(&self) -> &TextEdit {
157 &self.text_edit
158 }
159
160 /// Short one-line additional information, like a type
161 pub fn detail(&self) -> Option<&str> {
162 self.detail.as_ref().map(|it| it.as_str())
163 }
164 /// A doc-comment
165 pub fn documentation(&self) -> Option<Documentation> {
166 self.documentation.clone()
167 }
168 /// What string is used for filtering.
169 pub fn lookup(&self) -> &str {
170 self.lookup.as_ref().map(|it| it.as_str()).unwrap_or_else(|| self.label())
171 }
172
173 pub fn kind(&self) -> Option<CompletionItemKind> {
174 self.kind
175 }
176
177 pub fn deprecated(&self) -> bool {
178 self.deprecated
179 }
180}
181
182/// A helper to make `CompletionItem`s.
183#[must_use]
184pub(crate) struct Builder {
185 source_range: TextRange,
186 completion_kind: CompletionKind,
187 label: String,
188 insert_text: Option<String>,
189 insert_text_format: InsertTextFormat,
190 detail: Option<String>,
191 documentation: Option<Documentation>,
192 lookup: Option<String>,
193 kind: Option<CompletionItemKind>,
194 text_edit: Option<TextEdit>,
195 deprecated: Option<bool>,
196}
197
198impl Builder {
199 pub(crate) fn add_to(self, acc: &mut Completions) {
200 acc.add(self.build())
201 }
202
203 pub(crate) fn build(self) -> CompletionItem {
204 let label = self.label;
205 let text_edit = match self.text_edit {
206 Some(it) => it,
207 None => TextEdit::replace(
208 self.source_range,
209 self.insert_text.unwrap_or_else(|| label.clone()),
210 ),
211 };
212
213 CompletionItem {
214 source_range: self.source_range,
215 label,
216 insert_text_format: self.insert_text_format,
217 text_edit,
218 detail: self.detail,
219 documentation: self.documentation,
220 lookup: self.lookup,
221 kind: self.kind,
222 completion_kind: self.completion_kind,
223 deprecated: self.deprecated.unwrap_or(false),
224 }
225 }
226 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
227 self.lookup = Some(lookup.into());
228 self
229 }
230 pub(crate) fn label(mut self, label: impl Into<String>) -> Builder {
231 self.label = label.into();
232 self
233 }
234 pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder {
235 self.insert_text = Some(insert_text.into());
236 self
237 }
238 pub(crate) fn insert_snippet(mut self, snippet: impl Into<String>) -> Builder {
239 self.insert_text_format = InsertTextFormat::Snippet;
240 self.insert_text(snippet)
241 }
242 pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder {
243 self.kind = Some(kind);
244 self
245 }
246 pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder {
247 self.text_edit = Some(edit);
248 self
249 }
250 pub(crate) fn snippet_edit(mut self, edit: TextEdit) -> Builder {
251 self.insert_text_format = InsertTextFormat::Snippet;
252 self.text_edit(edit)
253 }
254 #[allow(unused)]
255 pub(crate) fn detail(self, detail: impl Into<String>) -> Builder {
256 self.set_detail(Some(detail))
257 }
258 pub(crate) fn set_detail(mut self, detail: Option<impl Into<String>>) -> Builder {
259 self.detail = detail.map(Into::into);
260 self
261 }
262 #[allow(unused)]
263 pub(crate) fn documentation(self, docs: Documentation) -> Builder {
264 self.set_documentation(Some(docs))
265 }
266 pub(crate) fn set_documentation(mut self, docs: Option<Documentation>) -> Builder {
267 self.documentation = docs.map(Into::into);
268 self
269 }
270 pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder {
271 self.deprecated = Some(deprecated);
272 self
273 }
274}
275
276impl<'a> Into<CompletionItem> for Builder {
277 fn into(self) -> CompletionItem {
278 self.build()
279 }
280}
281
282/// Represents an in-progress set of completions being built.
283#[derive(Debug, Default)]
284pub(crate) struct Completions {
285 buf: Vec<CompletionItem>,
286}
287
288impl Completions {
289 pub(crate) fn add(&mut self, item: impl Into<CompletionItem>) {
290 self.buf.push(item.into())
291 }
292 pub(crate) fn add_all<I>(&mut self, items: I)
293 where
294 I: IntoIterator,
295 I::Item: Into<CompletionItem>,
296 {
297 items.into_iter().for_each(|item| self.add(item.into()))
298 }
299}
300
301impl Into<Vec<CompletionItem>> for Completions {
302 fn into(self) -> Vec<CompletionItem> {
303 self.buf
304 }
305}
306
307#[cfg(test)]
308pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
309 use crate::completion::completions;
310 use crate::mock_analysis::{analysis_and_position, single_file_with_position};
311 let (analysis, position) = if code.contains("//-") {
312 analysis_and_position(code)
313 } else {
314 single_file_with_position(code)
315 };
316 let completions = completions(&analysis.db, position).unwrap();
317 let completion_items: Vec<CompletionItem> = completions.into();
318 let mut kind_completions: Vec<CompletionItem> =
319 completion_items.into_iter().filter(|c| c.completion_kind == kind).collect();
320 kind_completions.sort_by_key(|c| c.label.clone());
321 kind_completions
322}