Skip to content

Commit dfe6d50

Browse files
committed
Auto merge of #18167 - SomeoneToIgnore:fat-completions, r=Veykril
internal: Send less data during `textDocument/completion` if possible Similar to #15522, stops sending extra data during `textDocument/completion` if that data was set in the client completions resolve capabilities, and sends those only during `completionItem/resolve` requests. Currently, rust-analyzer sends back all fields (including potentially huge docs) for every completion item which might get large. Same as the other one, this PR aims to keep the changes minimal and does not remove extra computations for such fields — instead, it just filters them out before sending to the client. The PR omits primitive, boolean and integer, types such as `deprecated`, `preselect`, `insertTextFormat`, `insertTextMode`, etc. AND `additionalTextEdits` — this one looks very dangerous to compute for each completion item (as the spec says we ought to if there's no corresponding resolve capabilities provided) due to the diff computations and the fact that this code had been in the resolution for some time. It would be good to resolve this lazily too, please let me know if it's ok to do. When tested with Zed which only defines `documentation` and `additionalTextEdits` in its client completion resolve capabilities, rust-analyzer starts to send almost 3 times less characters: Request: ```json {"jsonrpc":"2.0","id":104,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///Users/someonetoignore/work/rust-analyzer/crates/ide/src/inlay_hints.rs"},"position":{"line":90,"character":14},"context":{"triggerKind":1}}} ``` <img width="1338" alt="image" src="https://github.com/user-attachments/assets/104f19b5-7095-4fc1-b008-5d829623b2e2"> Before: 381944 characters [before.json](https://github.com/user-attachments/files/17092385/before.json) After: 140503 characters [after.json](https://github.com/user-attachments/files/17092386/after.json) After Zed's [patch](zed-industries/zed#18212) to enable all resolving possible: 84452 characters [after-after.json](https://github.com/user-attachments/files/17092755/after-after.json)
2 parents 7b60339 + c03f5b6 commit dfe6d50

File tree

11 files changed

+226
-75
lines changed

11 files changed

+226
-75
lines changed

crates/ide-completion/src/config.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use hir::ImportPathConfig;
88
use ide_db::{imports::insert_use::InsertUseConfig, SnippetCap};
99

10-
use crate::snippet::Snippet;
10+
use crate::{snippet::Snippet, CompletionFieldsToResolve};
1111

1212
#[derive(Clone, Debug, PartialEq, Eq)]
1313
pub struct CompletionConfig {
@@ -27,6 +27,7 @@ pub struct CompletionConfig {
2727
pub prefer_absolute: bool,
2828
pub snippets: Vec<Snippet>,
2929
pub limit: Option<usize>,
30+
pub fields_to_resolve: CompletionFieldsToResolve,
3031
}
3132

3233
#[derive(Clone, Debug, PartialEq, Eq)]

crates/ide-completion/src/lib.rs

+25
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,31 @@ pub use crate::{
3737
snippet::{Snippet, SnippetScope},
3838
};
3939

40+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
41+
pub struct CompletionFieldsToResolve {
42+
pub resolve_label_details: bool,
43+
pub resolve_tags: bool,
44+
pub resolve_detail: bool,
45+
pub resolve_documentation: bool,
46+
pub resolve_filter_text: bool,
47+
pub resolve_text_edit: bool,
48+
pub resolve_command: bool,
49+
}
50+
51+
impl CompletionFieldsToResolve {
52+
pub const fn empty() -> Self {
53+
Self {
54+
resolve_label_details: false,
55+
resolve_tags: false,
56+
resolve_detail: false,
57+
resolve_documentation: false,
58+
resolve_filter_text: false,
59+
resolve_text_edit: false,
60+
resolve_command: false,
61+
}
62+
}
63+
}
64+
4065
//FIXME: split the following feature into fine-grained features.
4166

4267
// Feature: Magic Completions

crates/ide-completion/src/tests.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ use test_fixture::ChangeFixture;
3737
use test_utils::assert_eq_text;
3838

3939
use crate::{
40-
resolve_completion_edits, CallableSnippets, CompletionConfig, CompletionItem,
41-
CompletionItemKind,
40+
resolve_completion_edits, CallableSnippets, CompletionConfig, CompletionFieldsToResolve,
41+
CompletionItem, CompletionItemKind,
4242
};
4343

4444
/// Lots of basic item definitions
@@ -84,6 +84,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
8484
prefer_absolute: false,
8585
snippets: Vec::new(),
8686
limit: None,
87+
fields_to_resolve: CompletionFieldsToResolve::empty(),
8788
};
8889

8990
pub(crate) fn completion_list(ra_fixture: &str) -> String {

crates/ide/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ pub use ide_assists::{
119119
Assist, AssistConfig, AssistId, AssistKind, AssistResolveStrategy, SingleResolve,
120120
};
121121
pub use ide_completion::{
122-
CallableSnippets, CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance,
123-
Snippet, SnippetScope,
122+
CallableSnippets, CompletionConfig, CompletionFieldsToResolve, CompletionItem,
123+
CompletionItemKind, CompletionRelevance, Snippet, SnippetScope,
124124
};
125125
pub use ide_db::{
126126
base_db::{Cancelled, CrateGraph, CrateId, FileChange, SourceRoot, SourceRootId},

crates/rust-analyzer/src/config.rs

+14-4
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ use std::{
1212
use cfg::{CfgAtom, CfgDiff};
1313
use hir::Symbol;
1414
use ide::{
15-
AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
16-
GenericParameterHints, HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat,
17-
InlayFieldsToResolve, InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig,
18-
MemoryLayoutHoverRenderKind, Snippet, SnippetScope, SourceRootId,
15+
AssistConfig, CallableSnippets, CompletionConfig, CompletionFieldsToResolve, DiagnosticsConfig,
16+
ExprFillDefaultMode, GenericParameterHints, HighlightConfig, HighlightRelatedConfig,
17+
HoverConfig, HoverDocFormat, InlayFieldsToResolve, InlayHintsConfig, JoinLinesConfig,
18+
MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, Snippet, SnippetScope, SourceRootId,
1919
};
2020
use ide_db::{
2121
imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
@@ -1393,6 +1393,7 @@ impl Config {
13931393
}
13941394

13951395
pub fn completion(&self, source_root: Option<SourceRootId>) -> CompletionConfig {
1396+
let client_capability_fields = self.completion_resolve_support_properties();
13961397
CompletionConfig {
13971398
enable_postfix_completions: self.completion_postfix_enable(source_root).to_owned(),
13981399
enable_imports_on_the_fly: self.completion_autoimport_enable(source_root).to_owned()
@@ -1417,6 +1418,15 @@ impl Config {
14171418
limit: self.completion_limit(source_root).to_owned(),
14181419
enable_term_search: self.completion_termSearch_enable(source_root).to_owned(),
14191420
term_search_fuel: self.completion_termSearch_fuel(source_root).to_owned() as u64,
1421+
fields_to_resolve: CompletionFieldsToResolve {
1422+
resolve_label_details: client_capability_fields.contains("labelDetails"),
1423+
resolve_tags: client_capability_fields.contains("tags"),
1424+
resolve_detail: client_capability_fields.contains("detail"),
1425+
resolve_documentation: client_capability_fields.contains("documentation"),
1426+
resolve_filter_text: client_capability_fields.contains("filterText"),
1427+
resolve_text_edit: client_capability_fields.contains("textEdit"),
1428+
resolve_command: client_capability_fields.contains("command"),
1429+
},
14201430
}
14211431
}
14221432

crates/rust-analyzer/src/handlers/request.rs

+65-29
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ use std::{
1010
use anyhow::Context;
1111

1212
use ide::{
13-
AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FilePosition, FileRange,
14-
HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, ReferenceCategory,
15-
Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit,
13+
AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, CompletionFieldsToResolve,
14+
FilePosition, FileRange, HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query,
15+
RangeInfo, ReferenceCategory, Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit,
1616
};
1717
use ide_db::SymbolKind;
1818
use itertools::Itertools;
@@ -1019,9 +1019,11 @@ pub(crate) fn handle_completion(
10191019

10201020
let items = to_proto::completion_items(
10211021
&snap.config,
1022+
&completion_config.fields_to_resolve,
10221023
&line_index,
10231024
snap.file_version(position.file_id),
10241025
text_document_position,
1026+
completion_trigger_character,
10251027
items,
10261028
);
10271029

@@ -1054,36 +1056,70 @@ pub(crate) fn handle_completion_resolve(
10541056
};
10551057
let source_root = snap.analysis.source_root_id(file_id)?;
10561058

1057-
let additional_edits = snap
1058-
.analysis
1059-
.resolve_completion_edits(
1060-
&snap.config.completion(Some(source_root)),
1061-
FilePosition { file_id, offset },
1062-
resolve_data
1063-
.imports
1064-
.into_iter()
1065-
.map(|import| (import.full_import_path, import.imported_name)),
1066-
)?
1067-
.into_iter()
1068-
.flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel)))
1069-
.collect::<Vec<_>>();
1059+
let mut forced_resolve_completions_config = snap.config.completion(Some(source_root));
1060+
forced_resolve_completions_config.fields_to_resolve = CompletionFieldsToResolve::empty();
10701061

1071-
if !all_edits_are_disjoint(&original_completion, &additional_edits) {
1072-
return Err(LspError::new(
1073-
ErrorCode::InternalError as i32,
1074-
"Import edit overlaps with the original completion edits, this is not LSP-compliant"
1075-
.into(),
1076-
)
1077-
.into());
1078-
}
1062+
let position = FilePosition { file_id, offset };
1063+
let Some(resolved_completions) = snap.analysis.completions(
1064+
&forced_resolve_completions_config,
1065+
position,
1066+
resolve_data.trigger_character,
1067+
)?
1068+
else {
1069+
return Ok(original_completion);
1070+
};
1071+
let resolved_completions = to_proto::completion_items(
1072+
&snap.config,
1073+
&forced_resolve_completions_config.fields_to_resolve,
1074+
&line_index,
1075+
snap.file_version(position.file_id),
1076+
resolve_data.position,
1077+
resolve_data.trigger_character,
1078+
resolved_completions,
1079+
);
1080+
let Some(mut resolved_completion) = resolved_completions.into_iter().find(|completion| {
1081+
completion.label == original_completion.label
1082+
&& completion.kind == original_completion.kind
1083+
&& completion.deprecated == original_completion.deprecated
1084+
&& completion.preselect == original_completion.preselect
1085+
&& completion.sort_text == original_completion.sort_text
1086+
}) else {
1087+
return Ok(original_completion);
1088+
};
10791089

1080-
if let Some(original_additional_edits) = original_completion.additional_text_edits.as_mut() {
1081-
original_additional_edits.extend(additional_edits)
1082-
} else {
1083-
original_completion.additional_text_edits = Some(additional_edits);
1090+
if !resolve_data.imports.is_empty() {
1091+
let additional_edits = snap
1092+
.analysis
1093+
.resolve_completion_edits(
1094+
&forced_resolve_completions_config,
1095+
position,
1096+
resolve_data
1097+
.imports
1098+
.into_iter()
1099+
.map(|import| (import.full_import_path, import.imported_name)),
1100+
)?
1101+
.into_iter()
1102+
.flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel)))
1103+
.collect::<Vec<_>>();
1104+
1105+
if !all_edits_are_disjoint(&resolved_completion, &additional_edits) {
1106+
return Err(LspError::new(
1107+
ErrorCode::InternalError as i32,
1108+
"Import edit overlaps with the original completion edits, this is not LSP-compliant"
1109+
.into(),
1110+
)
1111+
.into());
1112+
}
1113+
1114+
if let Some(original_additional_edits) = resolved_completion.additional_text_edits.as_mut()
1115+
{
1116+
original_additional_edits.extend(additional_edits)
1117+
} else {
1118+
resolved_completion.additional_text_edits = Some(additional_edits);
1119+
}
10841120
}
10851121

1086-
Ok(original_completion)
1122+
Ok(resolved_completion)
10871123
}
10881124

10891125
pub(crate) fn handle_folding_range(

crates/rust-analyzer/src/integrated_benchmarks.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
1313
use hir::ChangeWithProcMacros;
1414
use ide::{
15-
AnalysisHost, CallableSnippets, CompletionConfig, DiagnosticsConfig, FilePosition, TextSize,
15+
AnalysisHost, CallableSnippets, CompletionConfig, CompletionFieldsToResolve, DiagnosticsConfig,
16+
FilePosition, TextSize,
1617
};
1718
use ide_db::{
1819
imports::insert_use::{ImportGranularity, InsertUseConfig},
@@ -172,6 +173,7 @@ fn integrated_completion_benchmark() {
172173
snippets: Vec::new(),
173174
limit: None,
174175
add_semicolon_to_unit: true,
176+
fields_to_resolve: CompletionFieldsToResolve::empty(),
175177
};
176178
let position =
177179
FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
@@ -219,6 +221,7 @@ fn integrated_completion_benchmark() {
219221
snippets: Vec::new(),
220222
limit: None,
221223
add_semicolon_to_unit: true,
224+
fields_to_resolve: CompletionFieldsToResolve::empty(),
222225
};
223226
let position =
224227
FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
@@ -264,6 +267,7 @@ fn integrated_completion_benchmark() {
264267
snippets: Vec::new(),
265268
limit: None,
266269
add_semicolon_to_unit: true,
270+
fields_to_resolve: CompletionFieldsToResolve::empty(),
267271
};
268272
let position =
269273
FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };

crates/rust-analyzer/src/lsp/capabilities.rs

+17-3
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ impl ClientCapabilities {
448448
.unwrap_or_default()
449449
}
450450

451-
pub fn inlay_hint_resolve_support_properties(&self) -> FxHashSet<String> {
451+
pub fn inlay_hint_resolve_support_properties(&self) -> FxHashSet<&str> {
452452
self.0
453453
.text_document
454454
.as_ref()
@@ -457,8 +457,22 @@ impl ClientCapabilities {
457457
.map(|inlay_resolve| inlay_resolve.properties.iter())
458458
.into_iter()
459459
.flatten()
460-
.cloned()
461-
.collect::<FxHashSet<_>>()
460+
.map(|s| s.as_str())
461+
.collect()
462+
}
463+
464+
pub fn completion_resolve_support_properties(&self) -> FxHashSet<&str> {
465+
self.0
466+
.text_document
467+
.as_ref()
468+
.and_then(|text| text.completion.as_ref())
469+
.and_then(|completion_caps| completion_caps.completion_item.as_ref())
470+
.and_then(|completion_item_caps| completion_item_caps.resolve_support.as_ref())
471+
.map(|resolve_support| resolve_support.properties.iter())
472+
.into_iter()
473+
.flatten()
474+
.map(|s| s.as_str())
475+
.collect()
462476
}
463477

464478
pub fn hover_markdown_support(&self) -> bool {

crates/rust-analyzer/src/lsp/ext.rs

+1
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,7 @@ pub struct CompletionResolveData {
825825
pub position: lsp_types::TextDocumentPositionParams,
826826
pub imports: Vec<CompletionImport>,
827827
pub version: Option<i32>,
828+
pub trigger_character: Option<char>,
828829
}
829830

830831
#[derive(Debug, Serialize, Deserialize)]

0 commit comments

Comments
 (0)