diff options
Diffstat (limited to 'crates/ra_analysis/src/completion/completion_item.rs')
-rw-r--r-- | crates/ra_analysis/src/completion/completion_item.rs | 80 |
1 files changed, 76 insertions, 4 deletions
diff --git a/crates/ra_analysis/src/completion/completion_item.rs b/crates/ra_analysis/src/completion/completion_item.rs index 8aa9da005..d5d751759 100644 --- a/crates/ra_analysis/src/completion/completion_item.rs +++ b/crates/ra_analysis/src/completion/completion_item.rs | |||
@@ -6,6 +6,8 @@ pub struct CompletionItem { | |||
6 | label: String, | 6 | label: String, |
7 | lookup: Option<String>, | 7 | lookup: Option<String>, |
8 | snippet: Option<String>, | 8 | snippet: Option<String>, |
9 | /// Used only internally in test, to check only specific kind of completion. | ||
10 | kind: CompletionKind, | ||
9 | } | 11 | } |
10 | 12 | ||
11 | pub enum InsertText { | 13 | pub enum InsertText { |
@@ -13,6 +15,18 @@ pub enum InsertText { | |||
13 | Snippet { text: String }, | 15 | Snippet { text: String }, |
14 | } | 16 | } |
15 | 17 | ||
18 | #[derive(Debug, PartialEq, Eq)] | ||
19 | pub(crate) enum CompletionKind { | ||
20 | /// Parser-based keyword completion. | ||
21 | Keyword, | ||
22 | /// Your usual "complete all valid identifiers". | ||
23 | Reference, | ||
24 | /// "Secret sauce" completions. | ||
25 | Magic, | ||
26 | Snippet, | ||
27 | Unspecified, | ||
28 | } | ||
29 | |||
16 | impl CompletionItem { | 30 | impl CompletionItem { |
17 | pub(crate) fn new(label: impl Into<String>) -> Builder { | 31 | pub(crate) fn new(label: impl Into<String>) -> Builder { |
18 | let label = label.into(); | 32 | let label = label.into(); |
@@ -20,6 +34,7 @@ impl CompletionItem { | |||
20 | label, | 34 | label, |
21 | lookup: None, | 35 | lookup: None, |
22 | snippet: None, | 36 | snippet: None, |
37 | kind: CompletionKind::Unspecified, | ||
23 | } | 38 | } |
24 | } | 39 | } |
25 | /// What user sees in pop-up in the UI. | 40 | /// What user sees in pop-up in the UI. |
@@ -50,28 +65,34 @@ pub(crate) struct Builder { | |||
50 | label: String, | 65 | label: String, |
51 | lookup: Option<String>, | 66 | lookup: Option<String>, |
52 | snippet: Option<String>, | 67 | snippet: Option<String>, |
68 | kind: CompletionKind, | ||
53 | } | 69 | } |
54 | 70 | ||
55 | impl Builder { | 71 | impl Builder { |
56 | pub fn add_to(self, acc: &mut Completions) { | 72 | pub(crate) fn add_to(self, acc: &mut Completions) { |
57 | acc.add(self.build()) | 73 | acc.add(self.build()) |
58 | } | 74 | } |
59 | 75 | ||
60 | pub fn build(self) -> CompletionItem { | 76 | pub(crate) fn build(self) -> CompletionItem { |
61 | CompletionItem { | 77 | CompletionItem { |
62 | label: self.label, | 78 | label: self.label, |
63 | lookup: self.lookup, | 79 | lookup: self.lookup, |
64 | snippet: self.snippet, | 80 | snippet: self.snippet, |
81 | kind: self.kind, | ||
65 | } | 82 | } |
66 | } | 83 | } |
67 | pub fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { | 84 | pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { |
68 | self.lookup = Some(lookup.into()); | 85 | self.lookup = Some(lookup.into()); |
69 | self | 86 | self |
70 | } | 87 | } |
71 | pub fn snippet(mut self, snippet: impl Into<String>) -> Builder { | 88 | pub(crate) fn snippet(mut self, snippet: impl Into<String>) -> Builder { |
72 | self.snippet = Some(snippet.into()); | 89 | self.snippet = Some(snippet.into()); |
73 | self | 90 | self |
74 | } | 91 | } |
92 | pub(crate) fn kind(mut self, kind: CompletionKind) -> Builder { | ||
93 | self.kind = kind; | ||
94 | self | ||
95 | } | ||
75 | } | 96 | } |
76 | 97 | ||
77 | impl Into<CompletionItem> for Builder { | 98 | impl Into<CompletionItem> for Builder { |
@@ -97,6 +118,57 @@ impl Completions { | |||
97 | { | 118 | { |
98 | items.into_iter().for_each(|item| self.add(item.into())) | 119 | items.into_iter().for_each(|item| self.add(item.into())) |
99 | } | 120 | } |
121 | |||
122 | #[cfg(test)] | ||
123 | pub(crate) fn assert_match(&self, expected: &str, kind: CompletionKind) { | ||
124 | let expected = normalize(expected); | ||
125 | let actual = self.debug_render(kind); | ||
126 | test_utils::assert_eq_text!(expected.as_str(), actual.as_str(),); | ||
127 | |||
128 | /// Normalize the textual representation of `Completions`: | ||
129 | /// replace `;` with newlines, normalize whitespace | ||
130 | fn normalize(expected: &str) -> String { | ||
131 | use ra_syntax::{tokenize, TextUnit, TextRange, SyntaxKind::SEMI}; | ||
132 | let mut res = String::new(); | ||
133 | for line in expected.trim().lines() { | ||
134 | let line = line.trim(); | ||
135 | let mut start_offset: TextUnit = 0.into(); | ||
136 | // Yep, we use rust tokenize in completion tests :-) | ||
137 | for token in tokenize(line) { | ||
138 | let range = TextRange::offset_len(start_offset, token.len); | ||
139 | start_offset += token.len; | ||
140 | if token.kind == SEMI { | ||
141 | res.push('\n'); | ||
142 | } else { | ||
143 | res.push_str(&line[range]); | ||
144 | } | ||
145 | } | ||
146 | |||
147 | res.push('\n'); | ||
148 | } | ||
149 | res | ||
150 | } | ||
151 | } | ||
152 | |||
153 | #[cfg(test)] | ||
154 | fn debug_render(&self, kind: CompletionKind) -> String { | ||
155 | let mut res = String::new(); | ||
156 | for c in self.buf.iter() { | ||
157 | if c.kind == kind { | ||
158 | if let Some(lookup) = &c.lookup { | ||
159 | res.push_str(lookup); | ||
160 | res.push_str(&format!(" {:?}", c.label)); | ||
161 | } else { | ||
162 | res.push_str(&c.label); | ||
163 | } | ||
164 | if let Some(snippet) = &c.snippet { | ||
165 | res.push_str(&format!(" {:?}", snippet)); | ||
166 | } | ||
167 | res.push('\n'); | ||
168 | } | ||
169 | } | ||
170 | res | ||
171 | } | ||
100 | } | 172 | } |
101 | 173 | ||
102 | impl Into<Vec<CompletionItem>> for Completions { | 174 | impl Into<Vec<CompletionItem>> for Completions { |