aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis/src/completion/completion_item.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_analysis/src/completion/completion_item.rs')
-rw-r--r--crates/ra_analysis/src/completion/completion_item.rs214
1 files changed, 214 insertions, 0 deletions
diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs
new file mode 100644
index 000000000..911f08468
--- /dev/null
+++ b/crates/ra_analysis/src/completion/completion_item.rs
@@ -0,0 +1,214 @@
1use crate::db;
2
3/// `CompletionItem` describes a single completion variant in the editor pop-up.
4/// It is basically a POD with various properties. To construct a
5/// `CompletionItem`, use `new` method and the `Builder` struct.
6#[derive(Debug)]
7pub struct CompletionItem {
8 /// Used only internally in tests, to check only specific kind of
9 /// completion.
10 completion_kind: CompletionKind,
11 label: String,
12 lookup: Option<String>,
13 snippet: Option<String>,
14 kind: Option<CompletionItemKind>,
15}
16
17pub enum InsertText {
18 PlainText { text: String },
19 Snippet { text: String },
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum CompletionItemKind {
24 Snippet,
25 Keyword,
26 Module,
27 Function,
28 Binding,
29}
30
31#[derive(Debug, PartialEq, Eq)]
32pub(crate) enum CompletionKind {
33 /// Parser-based keyword completion.
34 Keyword,
35 /// Your usual "complete all valid identifiers".
36 Reference,
37 /// "Secret sauce" completions.
38 Magic,
39 Snippet,
40}
41
42impl CompletionItem {
43 pub(crate) fn new(completion_kind: CompletionKind, label: impl Into<String>) -> Builder {
44 let label = label.into();
45 Builder {
46 completion_kind,
47 label,
48 lookup: None,
49 snippet: None,
50 kind: None,
51 }
52 }
53 /// What user sees in pop-up in the UI.
54 pub fn label(&self) -> &str {
55 &self.label
56 }
57 /// What string is used for filtering.
58 pub fn lookup(&self) -> &str {
59 self.lookup
60 .as_ref()
61 .map(|it| it.as_str())
62 .unwrap_or(self.label())
63 }
64 /// What is inserted.
65 pub fn insert_text(&self) -> InsertText {
66 match &self.snippet {
67 None => InsertText::PlainText {
68 text: self.label.clone(),
69 },
70 Some(it) => InsertText::Snippet { text: it.clone() },
71 }
72 }
73
74 pub fn kind(&self) -> Option<CompletionItemKind> {
75 self.kind
76 }
77}
78
79/// A helper to make `CompletionItem`s.
80#[must_use]
81pub(crate) struct Builder {
82 completion_kind: CompletionKind,
83 label: String,
84 lookup: Option<String>,
85 snippet: Option<String>,
86 kind: Option<CompletionItemKind>,
87}
88
89impl Builder {
90 pub(crate) fn add_to(self, acc: &mut Completions) {
91 acc.add(self.build())
92 }
93
94 pub(crate) fn build(self) -> CompletionItem {
95 CompletionItem {
96 label: self.label,
97 lookup: self.lookup,
98 snippet: self.snippet,
99 kind: self.kind,
100 completion_kind: self.completion_kind,
101 }
102 }
103 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
104 self.lookup = Some(lookup.into());
105 self
106 }
107 pub(crate) fn snippet(mut self, snippet: impl Into<String>) -> Builder {
108 self.snippet = Some(snippet.into());
109 self
110 }
111 pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder {
112 self.kind = Some(kind);
113 self
114 }
115 pub(crate) fn from_resolution(
116 mut self,
117 db: &db::RootDatabase,
118 resolution: &hir::Resolution,
119 ) -> Builder {
120 if let Some(def_id) = resolution.def_id {
121 if let Ok(def) = def_id.resolve(db) {
122 let kind = match def {
123 hir::Def::Module(..) => CompletionItemKind::Module,
124 hir::Def::Function(..) => CompletionItemKind::Function,
125 _ => return self,
126 };
127 self.kind = Some(kind);
128 }
129 }
130 self
131 }
132}
133
134impl Into<CompletionItem> for Builder {
135 fn into(self) -> CompletionItem {
136 self.build()
137 }
138}
139
140/// Represents an in-progress set of completions being built.
141#[derive(Debug, Default)]
142pub(crate) struct Completions {
143 buf: Vec<CompletionItem>,
144}
145
146impl Completions {
147 pub(crate) fn add(&mut self, item: impl Into<CompletionItem>) {
148 self.buf.push(item.into())
149 }
150 pub(crate) fn add_all<I>(&mut self, items: I)
151 where
152 I: IntoIterator,
153 I::Item: Into<CompletionItem>,
154 {
155 items.into_iter().for_each(|item| self.add(item.into()))
156 }
157
158 #[cfg(test)]
159 pub(crate) fn assert_match(&self, expected: &str, kind: CompletionKind) {
160 let expected = normalize(expected);
161 let actual = self.debug_render(kind);
162 test_utils::assert_eq_text!(expected.as_str(), actual.as_str(),);
163
164 /// Normalize the textual representation of `Completions`:
165 /// replace `;` with newlines, normalize whitespace
166 fn normalize(expected: &str) -> String {
167 use ra_syntax::{tokenize, TextUnit, TextRange, SyntaxKind::SEMI};
168 let mut res = String::new();
169 for line in expected.trim().lines() {
170 let line = line.trim();
171 let mut start_offset: TextUnit = 0.into();
172 // Yep, we use rust tokenize in completion tests :-)
173 for token in tokenize(line) {
174 let range = TextRange::offset_len(start_offset, token.len);
175 start_offset += token.len;
176 if token.kind == SEMI {
177 res.push('\n');
178 } else {
179 res.push_str(&line[range]);
180 }
181 }
182
183 res.push('\n');
184 }
185 res
186 }
187 }
188
189 #[cfg(test)]
190 fn debug_render(&self, kind: CompletionKind) -> String {
191 let mut res = String::new();
192 for c in self.buf.iter() {
193 if c.completion_kind == kind {
194 if let Some(lookup) = &c.lookup {
195 res.push_str(lookup);
196 res.push_str(&format!(" {:?}", c.label));
197 } else {
198 res.push_str(&c.label);
199 }
200 if let Some(snippet) = &c.snippet {
201 res.push_str(&format!(" {:?}", snippet));
202 }
203 res.push('\n');
204 }
205 }
206 res
207 }
208}
209
210impl Into<Vec<CompletionItem>> for Completions {
211 fn into(self) -> Vec<CompletionItem> {
212 self.buf
213 }
214}