Skip to content

Commit 404a51f

Browse files
committed
internal: Make CompletionItem more POD-like
1 parent 1f2d33f commit 404a51f

File tree

4 files changed

+70
-106
lines changed

4 files changed

+70
-106
lines changed

crates/ide-completion/src/item.rs

+25-65
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ use crate::{
1818
/// editor pop-up. It is basically a POD with various properties. To construct a
1919
/// [`CompletionItem`], use [`Builder::new`] method and the [`Builder`] struct.
2020
#[derive(Clone)]
21+
#[non_exhaustive]
2122
pub struct CompletionItem {
2223
/// Label in the completion pop up which identifies completion.
23-
label: SmolStr,
24+
pub label: SmolStr,
2425
/// Range of identifier that is being completed.
2526
///
2627
/// It should be used primarily for UI, but we also use this to convert
@@ -29,33 +30,33 @@ pub struct CompletionItem {
2930
/// `source_range` must contain the completion offset. `text_edit` should
3031
/// start with what `source_range` points to, or VSCode will filter out the
3132
/// completion silently.
32-
source_range: TextRange,
33+
pub source_range: TextRange,
3334
/// What happens when user selects this item.
3435
///
3536
/// Typically, replaces `source_range` with new identifier.
36-
text_edit: TextEdit,
37-
is_snippet: bool,
37+
pub text_edit: TextEdit,
38+
pub is_snippet: bool,
3839

3940
/// What item (struct, function, etc) are we completing.
40-
kind: CompletionItemKind,
41+
pub kind: CompletionItemKind,
4142

4243
/// Lookup is used to check if completion item indeed can complete current
4344
/// ident.
4445
///
4546
/// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it
4647
/// contains `bar` sub sequence), and `quux` will rejected.
47-
lookup: Option<SmolStr>,
48+
pub lookup: Option<SmolStr>,
4849

4950
/// Additional info to show in the UI pop up.
50-
detail: Option<String>,
51-
documentation: Option<Documentation>,
51+
pub detail: Option<String>,
52+
pub documentation: Option<Documentation>,
5253

5354
/// Whether this item is marked as deprecated
54-
deprecated: bool,
55+
pub deprecated: bool,
5556

5657
/// If completing a function call, ask the editor to show parameter popup
5758
/// after completion.
58-
trigger_call_info: bool,
59+
pub trigger_call_info: bool,
5960

6061
/// We use this to sort completion. Relevance records facts like "do the
6162
/// types align precisely?". We can't sort by relevances directly, they are
@@ -64,36 +65,39 @@ pub struct CompletionItem {
6465
/// Note that Relevance ignores fuzzy match score. We compute Relevance for
6566
/// all possible items, and then separately build an ordered completion list
6667
/// based on relevance and fuzzy matching with the already typed identifier.
67-
relevance: CompletionRelevance,
68+
pub relevance: CompletionRelevance,
6869

6970
/// Indicates that a reference or mutable reference to this variable is a
7071
/// possible match.
71-
ref_match: Option<(Mutability, TextSize)>,
72+
// FIXME: We shouldn't expose Mutability here (that is HIR types at all), its fine for now though
73+
// until we have more splitting completions in which case we should think about
74+
// generalizing this. See https://github.com/rust-lang/rust-analyzer/issues/12571
75+
pub ref_match: Option<(Mutability, TextSize)>,
7276

7377
/// The import data to add to completion's edits.
74-
import_to_add: SmallVec<[LocatedImport; 1]>,
78+
pub import_to_add: SmallVec<[LocatedImport; 1]>,
7579
}
7680

7781
// We use custom debug for CompletionItem to make snapshot tests more readable.
7882
impl fmt::Debug for CompletionItem {
7983
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8084
let mut s = f.debug_struct("CompletionItem");
81-
s.field("label", &self.label()).field("source_range", &self.source_range());
82-
if self.text_edit().len() == 1 {
83-
let atom = &self.text_edit().iter().next().unwrap();
85+
s.field("label", &self.label).field("source_range", &self.source_range);
86+
if self.text_edit.len() == 1 {
87+
let atom = &self.text_edit.iter().next().unwrap();
8488
s.field("delete", &atom.delete);
8589
s.field("insert", &atom.insert);
8690
} else {
8791
s.field("text_edit", &self.text_edit);
8892
}
89-
s.field("kind", &self.kind());
90-
if self.lookup() != self.label() {
93+
s.field("kind", &self.kind);
94+
if self.lookup() != self.label {
9195
s.field("lookup", &self.lookup());
9296
}
93-
if let Some(detail) = self.detail() {
97+
if let Some(detail) = &self.detail {
9498
s.field("detail", &detail);
9599
}
96-
if let Some(documentation) = self.documentation() {
100+
if let Some(documentation) = &self.documentation {
97101
s.field("documentation", &documentation);
98102
}
99103
if self.deprecated {
@@ -351,51 +355,11 @@ impl CompletionItem {
351355
}
352356
}
353357

354-
/// What user sees in pop-up in the UI.
355-
pub fn label(&self) -> &str {
356-
&self.label
357-
}
358-
pub fn source_range(&self) -> TextRange {
359-
self.source_range
360-
}
361-
362-
pub fn text_edit(&self) -> &TextEdit {
363-
&self.text_edit
364-
}
365-
/// Whether `text_edit` is a snippet (contains `$0` markers).
366-
pub fn is_snippet(&self) -> bool {
367-
self.is_snippet
368-
}
369-
370-
/// Short one-line additional information, like a type
371-
pub fn detail(&self) -> Option<&str> {
372-
self.detail.as_deref()
373-
}
374-
/// A doc-comment
375-
pub fn documentation(&self) -> Option<Documentation> {
376-
self.documentation.clone()
377-
}
378358
/// What string is used for filtering.
379359
pub fn lookup(&self) -> &str {
380360
self.lookup.as_deref().unwrap_or(&self.label)
381361
}
382362

383-
pub fn kind(&self) -> CompletionItemKind {
384-
self.kind
385-
}
386-
387-
pub fn deprecated(&self) -> bool {
388-
self.deprecated
389-
}
390-
391-
pub fn relevance(&self) -> CompletionRelevance {
392-
self.relevance
393-
}
394-
395-
pub fn trigger_call_info(&self) -> bool {
396-
self.trigger_call_info
397-
}
398-
399363
pub fn ref_match(&self) -> Option<(String, text_edit::Indel, CompletionRelevance)> {
400364
// Relevance of the ref match should be the same as the original
401365
// match, but with exact type match set because self.ref_match
@@ -405,16 +369,12 @@ impl CompletionItem {
405369

406370
self.ref_match.map(|(mutability, offset)| {
407371
(
408-
format!("&{}{}", mutability.as_keyword_for_ref(), self.label()),
372+
format!("&{}{}", mutability.as_keyword_for_ref(), self.label),
409373
text_edit::Indel::insert(offset, format!("&{}", mutability.as_keyword_for_ref())),
410374
relevance,
411375
)
412376
})
413377
}
414-
415-
pub fn imports_to_add(&self) -> &[LocatedImport] {
416-
&self.import_to_add
417-
}
418378
}
419379

420380
/// A helper to make `CompletionItem`s.

crates/ide-completion/src/render.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -503,18 +503,18 @@ mod tests {
503503
#[track_caller]
504504
fn check_relevance_for_kinds(ra_fixture: &str, kinds: &[CompletionItemKind], expect: Expect) {
505505
let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None);
506-
actual.retain(|it| kinds.contains(&it.kind()));
507-
actual.sort_by_key(|it| cmp::Reverse(it.relevance().score()));
506+
actual.retain(|it| kinds.contains(&it.kind));
507+
actual.sort_by_key(|it| cmp::Reverse(it.relevance.score()));
508508
check_relevance_(actual, expect);
509509
}
510510

511511
#[track_caller]
512512
fn check_relevance(ra_fixture: &str, expect: Expect) {
513513
let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None);
514-
actual.retain(|it| it.kind() != CompletionItemKind::Snippet);
515-
actual.retain(|it| it.kind() != CompletionItemKind::Keyword);
516-
actual.retain(|it| it.kind() != CompletionItemKind::BuiltinType);
517-
actual.sort_by_key(|it| cmp::Reverse(it.relevance().score()));
514+
actual.retain(|it| it.kind != CompletionItemKind::Snippet);
515+
actual.retain(|it| it.kind != CompletionItemKind::Keyword);
516+
actual.retain(|it| it.kind != CompletionItemKind::BuiltinType);
517+
actual.sort_by_key(|it| cmp::Reverse(it.relevance.score()));
518518
check_relevance_(actual, expect);
519519
}
520520

@@ -525,9 +525,9 @@ mod tests {
525525
.flat_map(|it| {
526526
let mut items = vec![];
527527

528-
let tag = it.kind().tag();
529-
let relevance = display_relevance(it.relevance());
530-
items.push(format!("{tag} {} {relevance}\n", it.label()));
528+
let tag = it.kind.tag();
529+
let relevance = display_relevance(it.relevance);
530+
items.push(format!("{tag} {} {relevance}\n", it.label));
531531

532532
if let Some((label, _indel, relevance)) = it.ref_match() {
533533
let relevance = display_relevance(relevance);

crates/ide-completion/src/tests.rs

+15-15
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ fn completion_list_with_config(
108108
let items = get_all_items(config, ra_fixture, trigger_character);
109109
let items = items
110110
.into_iter()
111-
.filter(|it| it.kind() != CompletionItemKind::BuiltinType || it.label() == "u32")
112-
.filter(|it| include_keywords || it.kind() != CompletionItemKind::Keyword)
113-
.filter(|it| include_keywords || it.kind() != CompletionItemKind::Snippet)
114-
.sorted_by_key(|it| (it.kind(), it.label().to_owned(), it.detail().map(ToOwned::to_owned)))
111+
.filter(|it| it.kind != CompletionItemKind::BuiltinType || it.label == "u32")
112+
.filter(|it| include_keywords || it.kind != CompletionItemKind::Keyword)
113+
.filter(|it| include_keywords || it.kind != CompletionItemKind::Snippet)
114+
.sorted_by_key(|it| (it.kind, it.label.clone(), it.detail.as_ref().map(ToOwned::to_owned)))
115115
.collect();
116116
render_completion_list(items)
117117
}
@@ -138,8 +138,8 @@ pub(crate) fn do_completion_with_config(
138138
) -> Vec<CompletionItem> {
139139
get_all_items(config, code, None)
140140
.into_iter()
141-
.filter(|c| c.kind() == kind)
142-
.sorted_by(|l, r| l.label().cmp(r.label()))
141+
.filter(|c| c.kind == kind)
142+
.sorted_by(|l, r| l.label.cmp(&r.label))
143143
.collect()
144144
}
145145

@@ -148,18 +148,18 @@ fn render_completion_list(completions: Vec<CompletionItem>) -> String {
148148
s.chars().count()
149149
}
150150
let label_width =
151-
completions.iter().map(|it| monospace_width(it.label())).max().unwrap_or_default().min(22);
151+
completions.iter().map(|it| monospace_width(&it.label)).max().unwrap_or_default().min(22);
152152
completions
153153
.into_iter()
154154
.map(|it| {
155-
let tag = it.kind().tag();
156-
let var_name = format!("{tag} {}", it.label());
155+
let tag = it.kind.tag();
156+
let var_name = format!("{tag} {}", it.label);
157157
let mut buf = var_name;
158-
if let Some(detail) = it.detail() {
159-
let width = label_width.saturating_sub(monospace_width(it.label()));
158+
if let Some(detail) = it.detail {
159+
let width = label_width.saturating_sub(monospace_width(&it.label));
160160
format_to!(buf, "{:width$} {}", "", detail, width = width);
161161
}
162-
if it.deprecated() {
162+
if it.deprecated {
163163
format_to!(buf, " DEPRECATED");
164164
}
165165
format_to!(buf, "\n");
@@ -191,13 +191,13 @@ pub(crate) fn check_edit_with_config(
191191
.unwrap_or_else(|| panic!("can't find {what:?} completion in {completions:#?}"));
192192
let mut actual = db.file_text(position.file_id).to_string();
193193

194-
let mut combined_edit = completion.text_edit().to_owned();
194+
let mut combined_edit = completion.text_edit.clone();
195195

196196
resolve_completion_edits(
197197
&db,
198198
&config,
199199
position,
200-
completion.imports_to_add().iter().filter_map(|import_edit| {
200+
completion.import_to_add.iter().filter_map(|import_edit| {
201201
let import_path = &import_edit.import_path;
202202
let import_name = import_path.segments().last()?;
203203
Some((import_path.to_string(), import_name.to_string()))
@@ -225,7 +225,7 @@ pub(crate) fn get_all_items(
225225
.map_or_else(Vec::default, Into::into);
226226
// validate
227227
res.iter().for_each(|it| {
228-
let sr = it.source_range();
228+
let sr = it.source_range;
229229
assert!(
230230
sr.contains_inclusive(position.offset),
231231
"source range {sr:?} does not contain the offset {:?} of the completion request: {it:?}",

crates/rust-analyzer/src/to_proto.rs

+21-17
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ pub(crate) fn completion_items(
212212
tdpp: lsp_types::TextDocumentPositionParams,
213213
items: Vec<CompletionItem>,
214214
) -> Vec<lsp_types::CompletionItem> {
215-
let max_relevance = items.iter().map(|it| it.relevance().score()).max().unwrap_or_default();
215+
let max_relevance = items.iter().map(|it| it.relevance.score()).max().unwrap_or_default();
216216
let mut res = Vec::with_capacity(items.len());
217217
for item in items {
218218
completion_item(&mut res, config, line_index, &tdpp, max_relevance, item);
@@ -235,14 +235,17 @@ fn completion_item(
235235
item: CompletionItem,
236236
) {
237237
let insert_replace_support = config.insert_replace_support().then_some(tdpp.position);
238+
let ref_match = item.ref_match();
239+
let lookup = item.lookup().to_string();
240+
238241
let mut additional_text_edits = Vec::new();
239242

240243
// LSP does not allow arbitrary edits in completion, so we have to do a
241244
// non-trivial mapping here.
242245
let text_edit = {
243246
let mut text_edit = None;
244-
let source_range = item.source_range();
245-
for indel in item.text_edit() {
247+
let source_range = item.source_range;
248+
for indel in item.text_edit {
246249
if indel.delete.contains_range(source_range) {
247250
// Extract this indel as the main edit
248251
text_edit = Some(if indel.delete == source_range {
@@ -265,23 +268,23 @@ fn completion_item(
265268
text_edit.unwrap()
266269
};
267270

268-
let insert_text_format = item.is_snippet().then_some(lsp_types::InsertTextFormat::SNIPPET);
269-
let tags = item.deprecated().then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]);
270-
let command = if item.trigger_call_info() && config.client_commands().trigger_parameter_hints {
271+
let insert_text_format = item.is_snippet.then_some(lsp_types::InsertTextFormat::SNIPPET);
272+
let tags = item.deprecated.then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]);
273+
let command = if item.trigger_call_info && config.client_commands().trigger_parameter_hints {
271274
Some(command::trigger_parameter_hints())
272275
} else {
273276
None
274277
};
275278

276279
let mut lsp_item = lsp_types::CompletionItem {
277-
label: item.label().to_string(),
278-
detail: item.detail().map(|it| it.to_string()),
279-
filter_text: Some(item.lookup().to_string()),
280-
kind: Some(completion_item_kind(item.kind())),
280+
label: item.label.to_string(),
281+
detail: item.detail.map(|it| it.to_string()),
282+
filter_text: Some(lookup),
283+
kind: Some(completion_item_kind(item.kind)),
281284
text_edit: Some(text_edit),
282285
additional_text_edits: Some(additional_text_edits),
283-
documentation: item.documentation().map(documentation),
284-
deprecated: Some(item.deprecated()),
286+
documentation: item.documentation.map(documentation),
287+
deprecated: Some(item.deprecated),
285288
tags,
286289
command,
287290
insert_text_format,
@@ -295,12 +298,13 @@ fn completion_item(
295298
});
296299
}
297300

298-
set_score(&mut lsp_item, max_relevance, item.relevance());
301+
set_score(&mut lsp_item, max_relevance, item.relevance);
299302

300303
if config.completion().enable_imports_on_the_fly {
301-
if let imports @ [_, ..] = item.imports_to_add() {
302-
let imports: Vec<_> = imports
303-
.iter()
304+
if !item.import_to_add.is_empty() {
305+
let imports: Vec<_> = item
306+
.import_to_add
307+
.into_iter()
304308
.filter_map(|import_edit| {
305309
let import_path = &import_edit.import_path;
306310
let import_name = import_path.segments().last()?;
@@ -317,7 +321,7 @@ fn completion_item(
317321
}
318322
}
319323

320-
if let Some((label, indel, relevance)) = item.ref_match() {
324+
if let Some((label, indel, relevance)) = ref_match {
321325
let mut lsp_item_with_ref = lsp_types::CompletionItem { label, ..lsp_item.clone() };
322326
lsp_item_with_ref
323327
.additional_text_edits

0 commit comments

Comments
 (0)