about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-09-30 08:36:54 +0000
committerbors <bors@rust-lang.org>2024-09-30 08:36:54 +0000
commit4800a0eef7547674b96c8462846aac4fb655c1d8 (patch)
treecdc9923cfdbdb6caf91845ad73fb6330519a2d08
parent6938084f7e7333b4f3f5c2bc9add20062d381a24 (diff)
parent536ac471c47f06fd53daa651424ea5a78189491c (diff)
downloadrust-4800a0eef7547674b96c8462846aac4fb655c1d8.tar.gz
rust-4800a0eef7547674b96c8462846aac4fb655c1d8.zip
Auto merge of #18167 - SomeoneToIgnore:fat-completions, r=Veykril
internal: Send less data during `textDocument/completion` if possible

Similar to https://github.com/rust-lang/rust-analyzer/pull/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](https://github.com/zed-industries/zed/pull/18212) to enable all resolving possible: 84452 characters
[after-after.json](https://github.com/user-attachments/files/17092755/after-after.json)
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/config.rs3
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/lib.rs25
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests.rs5
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/lib.rs4
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs18
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs94
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs6
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs20
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs1
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs123
-rw-r--r--src/tools/rust-analyzer/docs/dev/lsp-extensions.md2
11 files changed, 226 insertions, 75 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/config.rs b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs
index 0d403f49b7a..1d05419c96d 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/config.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs
@@ -7,7 +7,7 @@
 use hir::ImportPathConfig;
 use ide_db::{imports::insert_use::InsertUseConfig, SnippetCap};
 
-use crate::snippet::Snippet;
+use crate::{snippet::Snippet, CompletionFieldsToResolve};
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct CompletionConfig {
@@ -27,6 +27,7 @@ pub struct CompletionConfig {
     pub prefer_absolute: bool,
     pub snippets: Vec<Snippet>,
     pub limit: Option<usize>,
+    pub fields_to_resolve: CompletionFieldsToResolve,
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs
index 58d1fad0950..a78976d3fd8 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs
@@ -37,6 +37,31 @@ pub use crate::{
     snippet::{Snippet, SnippetScope},
 };
 
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct CompletionFieldsToResolve {
+    pub resolve_label_details: bool,
+    pub resolve_tags: bool,
+    pub resolve_detail: bool,
+    pub resolve_documentation: bool,
+    pub resolve_filter_text: bool,
+    pub resolve_text_edit: bool,
+    pub resolve_command: bool,
+}
+
+impl CompletionFieldsToResolve {
+    pub const fn empty() -> Self {
+        Self {
+            resolve_label_details: false,
+            resolve_tags: false,
+            resolve_detail: false,
+            resolve_documentation: false,
+            resolve_filter_text: false,
+            resolve_text_edit: false,
+            resolve_command: false,
+        }
+    }
+}
+
 //FIXME: split the following feature into fine-grained features.
 
 // Feature: Magic Completions
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs
index 9d77d970071..f371012de3f 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs
@@ -37,8 +37,8 @@ use test_fixture::ChangeFixture;
 use test_utils::assert_eq_text;
 
 use crate::{
-    resolve_completion_edits, CallableSnippets, CompletionConfig, CompletionItem,
-    CompletionItemKind,
+    resolve_completion_edits, CallableSnippets, CompletionConfig, CompletionFieldsToResolve,
+    CompletionItem, CompletionItemKind,
 };
 
 /// Lots of basic item definitions
@@ -84,6 +84,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
     prefer_absolute: false,
     snippets: Vec::new(),
     limit: None,
+    fields_to_resolve: CompletionFieldsToResolve::empty(),
 };
 
 pub(crate) fn completion_list(ra_fixture: &str) -> String {
diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs
index 547286c3f4d..c46c4c8ce94 100644
--- a/src/tools/rust-analyzer/crates/ide/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs
@@ -119,8 +119,8 @@ pub use ide_assists::{
     Assist, AssistConfig, AssistId, AssistKind, AssistResolveStrategy, SingleResolve,
 };
 pub use ide_completion::{
-    CallableSnippets, CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance,
-    Snippet, SnippetScope,
+    CallableSnippets, CompletionConfig, CompletionFieldsToResolve, CompletionItem,
+    CompletionItemKind, CompletionRelevance, Snippet, SnippetScope,
 };
 pub use ide_db::{
     base_db::{Cancelled, CrateGraph, CrateId, FileChange, SourceRoot, SourceRootId},
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
index 2be71ef92a3..ef2e542cf22 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
@@ -12,10 +12,10 @@ use std::{
 use cfg::{CfgAtom, CfgDiff};
 use hir::Symbol;
 use ide::{
-    AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
-    GenericParameterHints, HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat,
-    InlayFieldsToResolve, InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig,
-    MemoryLayoutHoverRenderKind, Snippet, SnippetScope, SourceRootId,
+    AssistConfig, CallableSnippets, CompletionConfig, CompletionFieldsToResolve, DiagnosticsConfig,
+    ExprFillDefaultMode, GenericParameterHints, HighlightConfig, HighlightRelatedConfig,
+    HoverConfig, HoverDocFormat, InlayFieldsToResolve, InlayHintsConfig, JoinLinesConfig,
+    MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, Snippet, SnippetScope, SourceRootId,
 };
 use ide_db::{
     imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
@@ -1393,6 +1393,7 @@ impl Config {
     }
 
     pub fn completion(&self, source_root: Option<SourceRootId>) -> CompletionConfig {
+        let client_capability_fields = self.completion_resolve_support_properties();
         CompletionConfig {
             enable_postfix_completions: self.completion_postfix_enable(source_root).to_owned(),
             enable_imports_on_the_fly: self.completion_autoimport_enable(source_root).to_owned()
@@ -1417,6 +1418,15 @@ impl Config {
             limit: self.completion_limit(source_root).to_owned(),
             enable_term_search: self.completion_termSearch_enable(source_root).to_owned(),
             term_search_fuel: self.completion_termSearch_fuel(source_root).to_owned() as u64,
+            fields_to_resolve: CompletionFieldsToResolve {
+                resolve_label_details: client_capability_fields.contains("labelDetails"),
+                resolve_tags: client_capability_fields.contains("tags"),
+                resolve_detail: client_capability_fields.contains("detail"),
+                resolve_documentation: client_capability_fields.contains("documentation"),
+                resolve_filter_text: client_capability_fields.contains("filterText"),
+                resolve_text_edit: client_capability_fields.contains("textEdit"),
+                resolve_command: client_capability_fields.contains("command"),
+            },
         }
     }
 
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
index bcbd970a0d2..9773d8dbce0 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
@@ -10,9 +10,9 @@ use std::{
 use anyhow::Context;
 
 use ide::{
-    AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FilePosition, FileRange,
-    HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, ReferenceCategory,
-    Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit,
+    AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, CompletionFieldsToResolve,
+    FilePosition, FileRange, HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query,
+    RangeInfo, ReferenceCategory, Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit,
 };
 use ide_db::SymbolKind;
 use itertools::Itertools;
@@ -1019,9 +1019,11 @@ pub(crate) fn handle_completion(
 
     let items = to_proto::completion_items(
         &snap.config,
+        &completion_config.fields_to_resolve,
         &line_index,
         snap.file_version(position.file_id),
         text_document_position,
+        completion_trigger_character,
         items,
     );
 
@@ -1054,36 +1056,70 @@ pub(crate) fn handle_completion_resolve(
     };
     let source_root = snap.analysis.source_root_id(file_id)?;
 
-    let additional_edits = snap
-        .analysis
-        .resolve_completion_edits(
-            &snap.config.completion(Some(source_root)),
-            FilePosition { file_id, offset },
-            resolve_data
-                .imports
-                .into_iter()
-                .map(|import| (import.full_import_path, import.imported_name)),
-        )?
-        .into_iter()
-        .flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel)))
-        .collect::<Vec<_>>();
+    let mut forced_resolve_completions_config = snap.config.completion(Some(source_root));
+    forced_resolve_completions_config.fields_to_resolve = CompletionFieldsToResolve::empty();
 
-    if !all_edits_are_disjoint(&original_completion, &additional_edits) {
-        return Err(LspError::new(
-            ErrorCode::InternalError as i32,
-            "Import edit overlaps with the original completion edits, this is not LSP-compliant"
-                .into(),
-        )
-        .into());
-    }
+    let position = FilePosition { file_id, offset };
+    let Some(resolved_completions) = snap.analysis.completions(
+        &forced_resolve_completions_config,
+        position,
+        resolve_data.trigger_character,
+    )?
+    else {
+        return Ok(original_completion);
+    };
+    let resolved_completions = to_proto::completion_items(
+        &snap.config,
+        &forced_resolve_completions_config.fields_to_resolve,
+        &line_index,
+        snap.file_version(position.file_id),
+        resolve_data.position,
+        resolve_data.trigger_character,
+        resolved_completions,
+    );
+    let Some(mut resolved_completion) = resolved_completions.into_iter().find(|completion| {
+        completion.label == original_completion.label
+            && completion.kind == original_completion.kind
+            && completion.deprecated == original_completion.deprecated
+            && completion.preselect == original_completion.preselect
+            && completion.sort_text == original_completion.sort_text
+    }) else {
+        return Ok(original_completion);
+    };
 
-    if let Some(original_additional_edits) = original_completion.additional_text_edits.as_mut() {
-        original_additional_edits.extend(additional_edits)
-    } else {
-        original_completion.additional_text_edits = Some(additional_edits);
+    if !resolve_data.imports.is_empty() {
+        let additional_edits = snap
+            .analysis
+            .resolve_completion_edits(
+                &forced_resolve_completions_config,
+                position,
+                resolve_data
+                    .imports
+                    .into_iter()
+                    .map(|import| (import.full_import_path, import.imported_name)),
+            )?
+            .into_iter()
+            .flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel)))
+            .collect::<Vec<_>>();
+
+        if !all_edits_are_disjoint(&resolved_completion, &additional_edits) {
+            return Err(LspError::new(
+                ErrorCode::InternalError as i32,
+                "Import edit overlaps with the original completion edits, this is not LSP-compliant"
+                    .into(),
+            )
+            .into());
+        }
+
+        if let Some(original_additional_edits) = resolved_completion.additional_text_edits.as_mut()
+        {
+            original_additional_edits.extend(additional_edits)
+        } else {
+            resolved_completion.additional_text_edits = Some(additional_edits);
+        }
     }
 
-    Ok(original_completion)
+    Ok(resolved_completion)
 }
 
 pub(crate) fn handle_folding_range(
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs
index 8a4f9d49fef..8946c7acb93 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -12,7 +12,8 @@
 
 use hir::ChangeWithProcMacros;
 use ide::{
-    AnalysisHost, CallableSnippets, CompletionConfig, DiagnosticsConfig, FilePosition, TextSize,
+    AnalysisHost, CallableSnippets, CompletionConfig, CompletionFieldsToResolve, DiagnosticsConfig,
+    FilePosition, TextSize,
 };
 use ide_db::{
     imports::insert_use::{ImportGranularity, InsertUseConfig},
@@ -172,6 +173,7 @@ fn integrated_completion_benchmark() {
             snippets: Vec::new(),
             limit: None,
             add_semicolon_to_unit: true,
+            fields_to_resolve: CompletionFieldsToResolve::empty(),
         };
         let position =
             FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
@@ -219,6 +221,7 @@ fn integrated_completion_benchmark() {
             snippets: Vec::new(),
             limit: None,
             add_semicolon_to_unit: true,
+            fields_to_resolve: CompletionFieldsToResolve::empty(),
         };
         let position =
             FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
@@ -264,6 +267,7 @@ fn integrated_completion_benchmark() {
             snippets: Vec::new(),
             limit: None,
             add_semicolon_to_unit: true,
+            fields_to_resolve: CompletionFieldsToResolve::empty(),
         };
         let position =
             FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs
index 9610808c27e..3b19284f241 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs
@@ -448,7 +448,7 @@ impl ClientCapabilities {
             .unwrap_or_default()
     }
 
-    pub fn inlay_hint_resolve_support_properties(&self) -> FxHashSet<String> {
+    pub fn inlay_hint_resolve_support_properties(&self) -> FxHashSet<&str> {
         self.0
             .text_document
             .as_ref()
@@ -457,8 +457,22 @@ impl ClientCapabilities {
             .map(|inlay_resolve| inlay_resolve.properties.iter())
             .into_iter()
             .flatten()
-            .cloned()
-            .collect::<FxHashSet<_>>()
+            .map(|s| s.as_str())
+            .collect()
+    }
+
+    pub fn completion_resolve_support_properties(&self) -> FxHashSet<&str> {
+        self.0
+            .text_document
+            .as_ref()
+            .and_then(|text| text.completion.as_ref())
+            .and_then(|completion_caps| completion_caps.completion_item.as_ref())
+            .and_then(|completion_item_caps| completion_item_caps.resolve_support.as_ref())
+            .map(|resolve_support| resolve_support.properties.iter())
+            .into_iter()
+            .flatten()
+            .map(|s| s.as_str())
+            .collect()
     }
 
     pub fn hover_markdown_support(&self) -> bool {
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs
index 618481bbc66..8039f0644ee 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs
@@ -825,6 +825,7 @@ pub struct CompletionResolveData {
     pub position: lsp_types::TextDocumentPositionParams,
     pub imports: Vec<CompletionImport>,
     pub version: Option<i32>,
+    pub trigger_character: Option<char>,
 }
 
 #[derive(Debug, Serialize, Deserialize)]
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs
index 4902c9f88c1..375b7428c2d 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs
@@ -6,9 +6,9 @@ use std::{
 };
 
 use ide::{
-    Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionItem,
-    CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit,
-    Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel,
+    Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionFieldsToResolve,
+    CompletionItem, CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange,
+    FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel,
     InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayKind, Markup,
     NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity, SignatureHelp,
     SnippetEdit, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize,
@@ -227,9 +227,11 @@ pub(crate) fn snippet_text_edit_vec(
 
 pub(crate) fn completion_items(
     config: &Config,
+    fields_to_resolve: &CompletionFieldsToResolve,
     line_index: &LineIndex,
     version: Option<i32>,
     tdpp: lsp_types::TextDocumentPositionParams,
+    completion_trigger_character: Option<char>,
     mut items: Vec<CompletionItem>,
 ) -> Vec<lsp_types::CompletionItem> {
     if config.completion_hide_deprecated() {
@@ -239,7 +241,17 @@ pub(crate) fn completion_items(
     let max_relevance = items.iter().map(|it| it.relevance.score()).max().unwrap_or_default();
     let mut res = Vec::with_capacity(items.len());
     for item in items {
-        completion_item(&mut res, config, line_index, version, &tdpp, max_relevance, item);
+        completion_item(
+            &mut res,
+            config,
+            fields_to_resolve,
+            line_index,
+            version,
+            &tdpp,
+            max_relevance,
+            completion_trigger_character,
+            item,
+        );
     }
 
     if let Some(limit) = config.completion(None).limit {
@@ -253,21 +265,33 @@ pub(crate) fn completion_items(
 fn completion_item(
     acc: &mut Vec<lsp_types::CompletionItem>,
     config: &Config,
+    fields_to_resolve: &CompletionFieldsToResolve,
     line_index: &LineIndex,
     version: Option<i32>,
     tdpp: &lsp_types::TextDocumentPositionParams,
     max_relevance: u32,
+    completion_trigger_character: Option<char>,
     item: CompletionItem,
 ) {
     let insert_replace_support = config.insert_replace_support().then_some(tdpp.position);
     let ref_match = item.ref_match();
-    let lookup = item.lookup().to_owned();
 
     let mut additional_text_edits = Vec::new();
+    let mut something_to_resolve = false;
 
-    // LSP does not allow arbitrary edits in completion, so we have to do a
-    // non-trivial mapping here.
-    let text_edit = {
+    let filter_text = if fields_to_resolve.resolve_filter_text {
+        something_to_resolve = !item.lookup().is_empty();
+        None
+    } else {
+        Some(item.lookup().to_owned())
+    };
+
+    let text_edit = if fields_to_resolve.resolve_text_edit {
+        something_to_resolve = true;
+        None
+    } else {
+        // LSP does not allow arbitrary edits in completion, so we have to do a
+        // non-trivial mapping here.
         let mut text_edit = None;
         let source_range = item.source_range;
         for indel in item.text_edit {
@@ -290,25 +314,49 @@ fn completion_item(
                 additional_text_edits.push(text_edit);
             }
         }
-        text_edit.unwrap()
+        Some(text_edit.unwrap())
     };
 
     let insert_text_format = item.is_snippet.then_some(lsp_types::InsertTextFormat::SNIPPET);
-    let tags = item.deprecated.then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]);
+    let tags = if fields_to_resolve.resolve_tags {
+        something_to_resolve = item.deprecated;
+        None
+    } else {
+        item.deprecated.then(|| vec![lsp_types::CompletionItemTag::DEPRECATED])
+    };
     let command = if item.trigger_call_info && config.client_commands().trigger_parameter_hints {
-        Some(command::trigger_parameter_hints())
+        if fields_to_resolve.resolve_command {
+            something_to_resolve = true;
+            None
+        } else {
+            Some(command::trigger_parameter_hints())
+        }
+    } else {
+        None
+    };
+
+    let detail = if fields_to_resolve.resolve_detail {
+        something_to_resolve = item.detail.is_some();
+        None
     } else {
+        item.detail
+    };
+
+    let documentation = if fields_to_resolve.resolve_documentation {
+        something_to_resolve = item.documentation.is_some();
         None
+    } else {
+        item.documentation.map(documentation)
     };
 
     let mut lsp_item = lsp_types::CompletionItem {
         label: item.label.to_string(),
-        detail: item.detail,
-        filter_text: Some(lookup),
+        detail,
+        filter_text,
         kind: Some(completion_item_kind(item.kind)),
-        text_edit: Some(text_edit),
+        text_edit,
         additional_text_edits: Some(additional_text_edits),
-        documentation: item.documentation.map(documentation),
+        documentation,
         deprecated: Some(item.deprecated),
         tags,
         command,
@@ -317,29 +365,40 @@ fn completion_item(
     };
 
     if config.completion_label_details_support() {
-        lsp_item.label_details = Some(lsp_types::CompletionItemLabelDetails {
-            detail: item.label_detail.as_ref().map(ToString::to_string),
-            description: lsp_item.detail.clone(),
-        });
+        if fields_to_resolve.resolve_label_details {
+            something_to_resolve = true;
+        } else {
+            lsp_item.label_details = Some(lsp_types::CompletionItemLabelDetails {
+                detail: item.label_detail.as_ref().map(ToString::to_string),
+                description: lsp_item.detail.clone(),
+            });
+        }
     } else if let Some(label_detail) = item.label_detail {
         lsp_item.label.push_str(label_detail.as_str());
     }
 
     set_score(&mut lsp_item, max_relevance, item.relevance);
 
-    if config.completion(None).enable_imports_on_the_fly && !item.import_to_add.is_empty() {
-        let imports = item
-            .import_to_add
-            .into_iter()
-            .map(|(import_path, import_name)| lsp_ext::CompletionImport {
-                full_import_path: import_path,
-                imported_name: import_name,
-            })
-            .collect::<Vec<_>>();
-        if !imports.is_empty() {
-            let data = lsp_ext::CompletionResolveData { position: tdpp.clone(), imports, version };
-            lsp_item.data = Some(to_value(data).unwrap());
-        }
+    let imports =
+        if config.completion(None).enable_imports_on_the_fly && !item.import_to_add.is_empty() {
+            item.import_to_add
+                .into_iter()
+                .map(|(import_path, import_name)| lsp_ext::CompletionImport {
+                    full_import_path: import_path,
+                    imported_name: import_name,
+                })
+                .collect()
+        } else {
+            Vec::new()
+        };
+    if something_to_resolve || !imports.is_empty() {
+        let data = lsp_ext::CompletionResolveData {
+            position: tdpp.clone(),
+            imports,
+            version,
+            trigger_character: completion_trigger_character,
+        };
+        lsp_item.data = Some(to_value(data).unwrap());
     }
 
     if let Some((label, indel, relevance)) = ref_match {
diff --git a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md
index b7bac4d29fa..7764f7843a0 100644
--- a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md
+++ b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
 <!---
-lsp/ext.rs hash: 6292ee8d88d4c9ec
+lsp/ext.rs hash: 90cf7718d54fe3c2
 
 If you need to change the above hash to make the test pass, please check if you
 need to adjust this doc as well and ping this issue: