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