aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src/completion/completion_item.rs
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-01-20 18:27:23 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-01-20 18:27:23 +0000
commitf133702f723203a60a1b4dade51418261cdbc133 (patch)
tree4d6d38d2e6185bb39b08ccfd5b89cdb5740d5ef8 /crates/ra_ide_api/src/completion/completion_item.rs
parentb89f8b6b4929ec09be4f9b13f87ad56b3235bd39 (diff)
parentfd8db14c2fcec7801edbdbb8e7f4d4c982a3da09 (diff)
Merge #574
574: refactor completions to use TextEdit instead of InsertText r=matklad a=gfreezy 1. migrate from `insertText` to `TextEdit` from `CompleteItem` 2. use `insta` to test completions Co-authored-by: gfreezy <[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.rs165
1 files changed, 85 insertions, 80 deletions
diff --git a/crates/ra_ide_api/src/completion/completion_item.rs b/crates/ra_ide_api/src/completion/completion_item.rs
index 11d00f78c..f46d9e581 100644
--- a/crates/ra_ide_api/src/completion/completion_item.rs
+++ b/crates/ra_ide_api/src/completion/completion_item.rs
@@ -1,6 +1,8 @@
1use hir::PerNs; 1use hir::PerNs;
2 2
3use crate::completion::CompletionContext; 3use crate::completion::completion_context::CompletionContext;
4use ra_syntax::TextRange;
5use ra_text_edit::TextEdit;
4 6
5/// `CompletionItem` describes a single completion variant in the editor pop-up. 7/// `CompletionItem` describes a single completion variant in the editor pop-up.
6/// It is basically a POD with various properties. To construct a 8/// It is basically a POD with various properties. To construct a
@@ -11,15 +13,13 @@ pub struct CompletionItem {
11 /// completion. 13 /// completion.
12 completion_kind: CompletionKind, 14 completion_kind: CompletionKind,
13 label: String, 15 label: String,
16 kind: Option<CompletionItemKind>,
14 detail: Option<String>, 17 detail: Option<String>,
15 lookup: Option<String>, 18 lookup: Option<String>,
16 snippet: Option<String>, 19 insert_text: Option<String>,
17 kind: Option<CompletionItemKind>, 20 insert_text_format: InsertTextFormat,
18} 21 source_range: TextRange,
19 22 text_edit: Option<TextEdit>,
20pub enum InsertText {
21 PlainText { text: String },
22 Snippet { text: String },
23} 23}
24 24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)] 25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -40,7 +40,7 @@ pub enum CompletionItemKind {
40 Method, 40 Method,
41} 41}
42 42
43#[derive(Debug, PartialEq, Eq)] 43#[derive(Debug, PartialEq, Eq, Copy, Clone)]
44pub(crate) enum CompletionKind { 44pub(crate) enum CompletionKind {
45 /// Parser-based keyword completion. 45 /// Parser-based keyword completion.
46 Keyword, 46 Keyword,
@@ -51,16 +51,29 @@ pub(crate) enum CompletionKind {
51 Snippet, 51 Snippet,
52} 52}
53 53
54#[derive(Debug, PartialEq, Eq, Copy, Clone)]
55pub enum InsertTextFormat {
56 PlainText,
57 Snippet,
58}
59
54impl CompletionItem { 60impl CompletionItem {
55 pub(crate) fn new(completion_kind: CompletionKind, label: impl Into<String>) -> Builder { 61 pub(crate) fn new(
62 completion_kind: CompletionKind,
63 replace_range: TextRange,
64 label: impl Into<String>,
65 ) -> Builder {
56 let label = label.into(); 66 let label = label.into();
57 Builder { 67 Builder {
68 source_range: replace_range,
58 completion_kind, 69 completion_kind,
59 label, 70 label,
71 insert_text: None,
72 insert_text_format: InsertTextFormat::PlainText,
60 detail: None, 73 detail: None,
61 lookup: None, 74 lookup: None,
62 snippet: None,
63 kind: None, 75 kind: None,
76 text_edit: None,
64 } 77 }
65 } 78 }
66 /// What user sees in pop-up in the UI. 79 /// What user sees in pop-up in the UI.
@@ -78,30 +91,39 @@ impl CompletionItem {
78 .map(|it| it.as_str()) 91 .map(|it| it.as_str())
79 .unwrap_or(self.label()) 92 .unwrap_or(self.label())
80 } 93 }
81 /// What is inserted. 94
82 pub fn insert_text(&self) -> InsertText { 95 pub fn insert_text_format(&self) -> InsertTextFormat {
83 match &self.snippet { 96 self.insert_text_format.clone()
84 None => InsertText::PlainText { 97 }
85 text: self.label.clone(), 98 pub fn insert_text(&self) -> String {
86 }, 99 match &self.insert_text {
87 Some(it) => InsertText::Snippet { text: it.clone() }, 100 Some(t) => t.clone(),
101 None => self.label.clone(),
88 } 102 }
89 } 103 }
90
91 pub fn kind(&self) -> Option<CompletionItemKind> { 104 pub fn kind(&self) -> Option<CompletionItemKind> {
92 self.kind 105 self.kind
93 } 106 }
107 pub fn take_text_edit(&mut self) -> Option<TextEdit> {
108 self.text_edit.take()
109 }
110 pub fn source_range(&self) -> TextRange {
111 self.source_range
112 }
94} 113}
95 114
96/// A helper to make `CompletionItem`s. 115/// A helper to make `CompletionItem`s.
97#[must_use] 116#[must_use]
98pub(crate) struct Builder { 117pub(crate) struct Builder {
118 source_range: TextRange,
99 completion_kind: CompletionKind, 119 completion_kind: CompletionKind,
100 label: String, 120 label: String,
121 insert_text: Option<String>,
122 insert_text_format: InsertTextFormat,
101 detail: Option<String>, 123 detail: Option<String>,
102 lookup: Option<String>, 124 lookup: Option<String>,
103 snippet: Option<String>,
104 kind: Option<CompletionItemKind>, 125 kind: Option<CompletionItemKind>,
126 text_edit: Option<TextEdit>,
105} 127}
106 128
107impl Builder { 129impl Builder {
@@ -111,27 +133,44 @@ impl Builder {
111 133
112 pub(crate) fn build(self) -> CompletionItem { 134 pub(crate) fn build(self) -> CompletionItem {
113 CompletionItem { 135 CompletionItem {
136 source_range: self.source_range,
114 label: self.label, 137 label: self.label,
115 detail: self.detail, 138 detail: self.detail,
139 insert_text_format: self.insert_text_format,
116 lookup: self.lookup, 140 lookup: self.lookup,
117 snippet: self.snippet,
118 kind: self.kind, 141 kind: self.kind,
119 completion_kind: self.completion_kind, 142 completion_kind: self.completion_kind,
143 text_edit: self.text_edit,
144 insert_text: self.insert_text,
120 } 145 }
121 } 146 }
122 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { 147 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
123 self.lookup = Some(lookup.into()); 148 self.lookup = Some(lookup.into());
124 self 149 self
125 } 150 }
126 pub(crate) fn snippet(mut self, snippet: impl Into<String>) -> Builder { 151 pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder {
127 self.snippet = Some(snippet.into()); 152 self.insert_text = Some(insert_text.into());
153 self
154 }
155 #[allow(unused)]
156 pub(crate) fn insert_text_format(mut self, insert_text_format: InsertTextFormat) -> Builder {
157 self.insert_text_format = insert_text_format;
128 self 158 self
129 } 159 }
160 pub(crate) fn snippet(mut self, snippet: impl Into<String>) -> Builder {
161 self.insert_text_format = InsertTextFormat::Snippet;
162 self.insert_text(snippet)
163 }
130 pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder { 164 pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder {
131 self.kind = Some(kind); 165 self.kind = Some(kind);
132 self 166 self
133 } 167 }
134 #[allow(unused)] 168 #[allow(unused)]
169 pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder {
170 self.text_edit = Some(edit);
171 self
172 }
173 #[allow(unused)]
135 pub(crate) fn detail(self, detail: impl Into<String>) -> Builder { 174 pub(crate) fn detail(self, detail: impl Into<String>) -> Builder {
136 self.set_detail(Some(detail)) 175 self.set_detail(Some(detail))
137 } 176 }
@@ -192,17 +231,18 @@ impl Builder {
192 // If not an import, add parenthesis automatically. 231 // If not an import, add parenthesis automatically.
193 if ctx.use_item_syntax.is_none() && !ctx.is_call { 232 if ctx.use_item_syntax.is_none() && !ctx.is_call {
194 if function.signature(ctx.db).params().is_empty() { 233 if function.signature(ctx.db).params().is_empty() {
195 self.snippet = Some(format!("{}()$0", self.label)); 234 self.insert_text = Some(format!("{}()$0", self.label));
196 } else { 235 } else {
197 self.snippet = Some(format!("{}($0)", self.label)); 236 self.insert_text = Some(format!("{}($0)", self.label));
198 } 237 }
238 self.insert_text_format = InsertTextFormat::Snippet;
199 } 239 }
200 self.kind = Some(CompletionItemKind::Function); 240 self.kind = Some(CompletionItemKind::Function);
201 self 241 self
202 } 242 }
203} 243}
204 244
205impl Into<CompletionItem> for Builder { 245impl<'a> Into<CompletionItem> for Builder {
206 fn into(self) -> CompletionItem { 246 fn into(self) -> CompletionItem {
207 self.build() 247 self.build()
208 } 248 }
@@ -225,60 +265,6 @@ impl Completions {
225 { 265 {
226 items.into_iter().for_each(|item| self.add(item.into())) 266 items.into_iter().for_each(|item| self.add(item.into()))
227 } 267 }
228
229 #[cfg(test)]
230 pub(crate) fn assert_match(&self, expected: &str, kind: CompletionKind) {
231 let expected = normalize(expected);
232 let actual = self.debug_render(kind);
233 test_utils::assert_eq_text!(expected.as_str(), actual.as_str(),);
234
235 /// Normalize the textual representation of `Completions`:
236 /// replace `;` with newlines, normalize whitespace
237 fn normalize(expected: &str) -> String {
238 use ra_syntax::{tokenize, TextUnit, TextRange, SyntaxKind::SEMI};
239 let mut res = String::new();
240 for line in expected.trim().lines() {
241 let line = line.trim();
242 let mut start_offset: TextUnit = 0.into();
243 // Yep, we use rust tokenize in completion tests :-)
244 for token in tokenize(line) {
245 let range = TextRange::offset_len(start_offset, token.len);
246 start_offset += token.len;
247 if token.kind == SEMI {
248 res.push('\n');
249 } else {
250 res.push_str(&line[range]);
251 }
252 }
253
254 res.push('\n');
255 }
256 res
257 }
258 }
259
260 #[cfg(test)]
261 fn debug_render(&self, kind: CompletionKind) -> String {
262 let mut res = String::new();
263 for c in self.buf.iter() {
264 if c.completion_kind == kind {
265 if let Some(lookup) = &c.lookup {
266 res.push_str(lookup);
267 res.push_str(&format!(" {:?}", c.label));
268 } else {
269 res.push_str(&c.label);
270 }
271 if let Some(detail) = &c.detail {
272 res.push_str(&format!(" {:?}", detail));
273 }
274 if let Some(snippet) = &c.snippet {
275 res.push_str(&format!(" {:?}", snippet));
276 }
277 res.push('\n');
278 }
279 }
280 res
281 }
282} 268}
283 269
284impl Into<Vec<CompletionItem>> for Completions { 270impl Into<Vec<CompletionItem>> for Completions {
@@ -286,3 +272,22 @@ impl Into<Vec<CompletionItem>> for Completions {
286 self.buf 272 self.buf
287 } 273 }
288} 274}
275
276#[cfg(test)]
277pub(crate) fn check_completion(test_name: &str, code: &str, kind: CompletionKind) {
278 use crate::mock_analysis::{single_file_with_position, analysis_and_position};
279 use crate::completion::completions;
280 use insta::assert_debug_snapshot_matches;
281 let (analysis, position) = if code.contains("//-") {
282 analysis_and_position(code)
283 } else {
284 single_file_with_position(code)
285 };
286 let completions = completions(&analysis.db, position).unwrap();
287 let completion_items: Vec<CompletionItem> = completions.into();
288 let kind_completions: Vec<CompletionItem> = completion_items
289 .into_iter()
290 .filter(|c| c.completion_kind == kind)
291 .collect();
292 assert_debug_snapshot_matches!(test_name, kind_completions);
293}