about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/capabilities.rs493
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/caps.rs230
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs266
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs15
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs5
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs23
-rw-r--r--src/tools/rust-analyzer/docs/dev/lsp-extensions.md2
10 files changed, 525 insertions, 515 deletions
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/capabilities.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/capabilities.rs
new file mode 100644
index 00000000000..212294b5d32
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/capabilities.rs
@@ -0,0 +1,493 @@
+//! Advertises the capabilities of the LSP Server.
+use ide_db::{line_index::WideEncoding, FxHashSet};
+use lsp_types::{
+    CallHierarchyServerCapability, CodeActionKind, CodeActionOptions, CodeActionProviderCapability,
+    CodeLensOptions, CompletionOptions, CompletionOptionsCompletionItem, DeclarationCapability,
+    DocumentOnTypeFormattingOptions, FileOperationFilter, FileOperationPattern,
+    FileOperationPatternKind, FileOperationRegistrationOptions, FoldingRangeProviderCapability,
+    HoverProviderCapability, ImplementationProviderCapability, InlayHintOptions,
+    InlayHintServerCapabilities, OneOf, PositionEncodingKind, RenameOptions, SaveOptions,
+    SelectionRangeProviderCapability, SemanticTokensFullOptions, SemanticTokensLegend,
+    SemanticTokensOptions, ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability,
+    TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability,
+    WorkDoneProgressOptions, WorkspaceFileOperationsServerCapabilities,
+    WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
+};
+use serde_json::json;
+
+use crate::{
+    config::{Config, RustfmtConfig},
+    line_index::PositionEncoding,
+    lsp::{ext, semantic_tokens},
+};
+
+pub fn server_capabilities(config: &Config) -> ServerCapabilities {
+    ServerCapabilities {
+        position_encoding: match config.caps().negotiated_encoding() {
+            PositionEncoding::Utf8 => Some(PositionEncodingKind::UTF8),
+            PositionEncoding::Wide(wide) => match wide {
+                WideEncoding::Utf16 => Some(PositionEncodingKind::UTF16),
+                WideEncoding::Utf32 => Some(PositionEncodingKind::UTF32),
+                _ => None,
+            },
+        },
+        text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
+            open_close: Some(true),
+            change: Some(TextDocumentSyncKind::INCREMENTAL),
+            will_save: None,
+            will_save_wait_until: None,
+            save: Some(SaveOptions::default().into()),
+        })),
+        hover_provider: Some(HoverProviderCapability::Simple(true)),
+        completion_provider: Some(CompletionOptions {
+            resolve_provider: config.caps().completions_resolve_provider(),
+            trigger_characters: Some(vec![
+                ":".to_owned(),
+                ".".to_owned(),
+                "'".to_owned(),
+                "(".to_owned(),
+            ]),
+            all_commit_characters: None,
+            completion_item: config.caps().completion_item(),
+            work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
+        }),
+        signature_help_provider: Some(SignatureHelpOptions {
+            trigger_characters: Some(vec!["(".to_owned(), ",".to_owned(), "<".to_owned()]),
+            retrigger_characters: None,
+            work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
+        }),
+        declaration_provider: Some(DeclarationCapability::Simple(true)),
+        definition_provider: Some(OneOf::Left(true)),
+        type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)),
+        implementation_provider: Some(ImplementationProviderCapability::Simple(true)),
+        references_provider: Some(OneOf::Left(true)),
+        document_highlight_provider: Some(OneOf::Left(true)),
+        document_symbol_provider: Some(OneOf::Left(true)),
+        workspace_symbol_provider: Some(OneOf::Left(true)),
+        code_action_provider: Some(config.caps().code_action_capabilities()),
+        code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
+        document_formatting_provider: Some(OneOf::Left(true)),
+        document_range_formatting_provider: match config.rustfmt() {
+            RustfmtConfig::Rustfmt { enable_range_formatting: true, .. } => Some(OneOf::Left(true)),
+            _ => Some(OneOf::Left(false)),
+        },
+        document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions {
+            first_trigger_character: "=".to_owned(),
+            more_trigger_character: Some(more_trigger_character(config)),
+        }),
+        selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)),
+        folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
+        rename_provider: Some(OneOf::Right(RenameOptions {
+            prepare_provider: Some(true),
+            work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
+        })),
+        linked_editing_range_provider: None,
+        document_link_provider: None,
+        color_provider: None,
+        execute_command_provider: None,
+        workspace: Some(WorkspaceServerCapabilities {
+            workspace_folders: Some(WorkspaceFoldersServerCapabilities {
+                supported: Some(true),
+                change_notifications: Some(OneOf::Left(true)),
+            }),
+            file_operations: Some(WorkspaceFileOperationsServerCapabilities {
+                did_create: None,
+                will_create: None,
+                did_rename: None,
+                will_rename: Some(FileOperationRegistrationOptions {
+                    filters: vec![
+                        FileOperationFilter {
+                            scheme: Some(String::from("file")),
+                            pattern: FileOperationPattern {
+                                glob: String::from("**/*.rs"),
+                                matches: Some(FileOperationPatternKind::File),
+                                options: None,
+                            },
+                        },
+                        FileOperationFilter {
+                            scheme: Some(String::from("file")),
+                            pattern: FileOperationPattern {
+                                glob: String::from("**"),
+                                matches: Some(FileOperationPatternKind::Folder),
+                                options: None,
+                            },
+                        },
+                    ],
+                }),
+                did_delete: None,
+                will_delete: None,
+            }),
+        }),
+        call_hierarchy_provider: Some(CallHierarchyServerCapability::Simple(true)),
+        semantic_tokens_provider: Some(
+            SemanticTokensOptions {
+                legend: SemanticTokensLegend {
+                    token_types: semantic_tokens::SUPPORTED_TYPES.to_vec(),
+                    token_modifiers: semantic_tokens::SUPPORTED_MODIFIERS.to_vec(),
+                },
+
+                full: Some(SemanticTokensFullOptions::Delta { delta: Some(true) }),
+                range: Some(true),
+                work_done_progress_options: Default::default(),
+            }
+            .into(),
+        ),
+        moniker_provider: None,
+        inlay_hint_provider: Some(OneOf::Right(InlayHintServerCapabilities::Options(
+            InlayHintOptions {
+                work_done_progress_options: Default::default(),
+                resolve_provider: Some(true),
+            },
+        ))),
+        inline_value_provider: None,
+        experimental: Some(json!({
+            "externalDocs": true,
+            "hoverRange": true,
+            "joinLines": true,
+            "matchingBrace": true,
+            "moveItem": true,
+            "onEnter": true,
+            "openCargoToml": true,
+            "parentModule": true,
+            "runnables": {
+                "kinds": [ "cargo" ],
+            },
+            "ssr": true,
+            "workspaceSymbolScopeKindFiltering": true,
+        })),
+        diagnostic_provider: None,
+        inline_completion_provider: None,
+    }
+}
+
+#[derive(Debug, PartialEq, Clone, Default)]
+pub struct ClientCapabilities(lsp_types::ClientCapabilities);
+
+impl ClientCapabilities {
+    pub fn new(caps: lsp_types::ClientCapabilities) -> Self {
+        Self(caps)
+    }
+
+    fn completions_resolve_provider(&self) -> Option<bool> {
+        self.completion_item_edit_resolve().then_some(true)
+    }
+
+    fn experimental_bool(&self, index: &'static str) -> bool {
+        || -> _ { self.0.experimental.as_ref()?.get(index)?.as_bool() }().unwrap_or_default()
+    }
+
+    fn experimental<T: serde::de::DeserializeOwned>(&self, index: &'static str) -> Option<T> {
+        serde_json::from_value(self.0.experimental.as_ref()?.get(index)?.clone()).ok()
+    }
+
+    /// Parses client capabilities and returns all completion resolve capabilities rust-analyzer supports.
+    pub fn completion_item_edit_resolve(&self) -> bool {
+        (|| {
+            Some(
+                self.0
+                    .text_document
+                    .as_ref()?
+                    .completion
+                    .as_ref()?
+                    .completion_item
+                    .as_ref()?
+                    .resolve_support
+                    .as_ref()?
+                    .properties
+                    .iter()
+                    .any(|cap_string| cap_string.as_str() == "additionalTextEdits"),
+            )
+        })() == Some(true)
+    }
+
+    pub fn completion_label_details_support(&self) -> bool {
+        (|| -> _ {
+            self.0
+                .text_document
+                .as_ref()?
+                .completion
+                .as_ref()?
+                .completion_item
+                .as_ref()?
+                .label_details_support
+                .as_ref()
+        })()
+        .is_some()
+    }
+
+    fn completion_item(&self) -> Option<CompletionOptionsCompletionItem> {
+        Some(CompletionOptionsCompletionItem {
+            label_details_support: Some(self.completion_label_details_support()),
+        })
+    }
+
+    fn code_action_capabilities(&self) -> CodeActionProviderCapability {
+        self.0
+            .text_document
+            .as_ref()
+            .and_then(|it| it.code_action.as_ref())
+            .and_then(|it| it.code_action_literal_support.as_ref())
+            .map_or(CodeActionProviderCapability::Simple(true), |_| {
+                CodeActionProviderCapability::Options(CodeActionOptions {
+                    // Advertise support for all built-in CodeActionKinds.
+                    // Ideally we would base this off of the client capabilities
+                    // but the client is supposed to fall back gracefully for unknown values.
+                    code_action_kinds: Some(vec![
+                        CodeActionKind::EMPTY,
+                        CodeActionKind::QUICKFIX,
+                        CodeActionKind::REFACTOR,
+                        CodeActionKind::REFACTOR_EXTRACT,
+                        CodeActionKind::REFACTOR_INLINE,
+                        CodeActionKind::REFACTOR_REWRITE,
+                    ]),
+                    resolve_provider: Some(true),
+                    work_done_progress_options: Default::default(),
+                })
+            })
+    }
+
+    pub fn negotiated_encoding(&self) -> PositionEncoding {
+        let client_encodings = match &self.0.general {
+            Some(general) => general.position_encodings.as_deref().unwrap_or_default(),
+            None => &[],
+        };
+
+        for enc in client_encodings {
+            if enc == &PositionEncodingKind::UTF8 {
+                return PositionEncoding::Utf8;
+            } else if enc == &PositionEncodingKind::UTF32 {
+                return PositionEncoding::Wide(WideEncoding::Utf32);
+            }
+            // NB: intentionally prefer just about anything else to utf-16.
+        }
+
+        PositionEncoding::Wide(WideEncoding::Utf16)
+    }
+
+    pub fn workspace_edit_resource_operations(
+        &self,
+    ) -> Option<&[lsp_types::ResourceOperationKind]> {
+        self.0.workspace.as_ref()?.workspace_edit.as_ref()?.resource_operations.as_deref()
+    }
+
+    pub fn semantics_tokens_augments_syntax_tokens(&self) -> bool {
+        (|| -> _ {
+            self.0.text_document.as_ref()?.semantic_tokens.as_ref()?.augments_syntax_tokens
+        })()
+        .unwrap_or(false)
+    }
+
+    pub fn did_save_text_document_dynamic_registration(&self) -> bool {
+        let caps = (|| -> _ { self.0.text_document.as_ref()?.synchronization.clone() })()
+            .unwrap_or_default();
+        caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
+    }
+
+    pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
+        (|| -> _ {
+            self.0.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration
+        })()
+        .unwrap_or_default()
+    }
+
+    pub fn did_change_watched_files_relative_pattern_support(&self) -> bool {
+        (|| -> _ {
+            self.0.workspace.as_ref()?.did_change_watched_files.as_ref()?.relative_pattern_support
+        })()
+        .unwrap_or_default()
+    }
+
+    pub fn location_link(&self) -> bool {
+        (|| -> _ { self.0.text_document.as_ref()?.definition?.link_support })().unwrap_or_default()
+    }
+
+    pub fn line_folding_only(&self) -> bool {
+        (|| -> _ { self.0.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only })()
+            .unwrap_or_default()
+    }
+
+    pub fn hierarchical_symbols(&self) -> bool {
+        (|| -> _ {
+            self.0
+                .text_document
+                .as_ref()?
+                .document_symbol
+                .as_ref()?
+                .hierarchical_document_symbol_support
+        })()
+        .unwrap_or_default()
+    }
+
+    pub fn code_action_literals(&self) -> bool {
+        (|| -> _ {
+            self.0
+                .text_document
+                .as_ref()?
+                .code_action
+                .as_ref()?
+                .code_action_literal_support
+                .as_ref()
+        })()
+        .is_some()
+    }
+
+    pub fn work_done_progress(&self) -> bool {
+        (|| -> _ { self.0.window.as_ref()?.work_done_progress })().unwrap_or_default()
+    }
+
+    pub fn will_rename(&self) -> bool {
+        (|| -> _ { self.0.workspace.as_ref()?.file_operations.as_ref()?.will_rename })()
+            .unwrap_or_default()
+    }
+
+    pub fn change_annotation_support(&self) -> bool {
+        (|| -> _ {
+            self.0.workspace.as_ref()?.workspace_edit.as_ref()?.change_annotation_support.as_ref()
+        })()
+        .is_some()
+    }
+
+    pub fn code_action_resolve(&self) -> bool {
+        (|| -> _ {
+            Some(
+                self.0
+                    .text_document
+                    .as_ref()?
+                    .code_action
+                    .as_ref()?
+                    .resolve_support
+                    .as_ref()?
+                    .properties
+                    .as_slice(),
+            )
+        })()
+        .unwrap_or_default()
+        .iter()
+        .any(|it| it == "edit")
+    }
+
+    pub fn signature_help_label_offsets(&self) -> bool {
+        (|| -> _ {
+            self.0
+                .text_document
+                .as_ref()?
+                .signature_help
+                .as_ref()?
+                .signature_information
+                .as_ref()?
+                .parameter_information
+                .as_ref()?
+                .label_offset_support
+        })()
+        .unwrap_or_default()
+    }
+
+    pub fn code_action_group(&self) -> bool {
+        self.experimental_bool("codeActionGroup")
+    }
+
+    pub fn commands(&self) -> Option<ext::ClientCommandOptions> {
+        self.experimental("commands")
+    }
+
+    pub fn local_docs(&self) -> bool {
+        self.experimental_bool("localDocs")
+    }
+
+    pub fn open_server_logs(&self) -> bool {
+        self.experimental_bool("openServerLogs")
+    }
+
+    pub fn server_status_notification(&self) -> bool {
+        self.experimental_bool("serverStatusNotification")
+    }
+
+    pub fn snippet_text_edit(&self) -> bool {
+        self.experimental_bool("snippetTextEdit")
+    }
+
+    pub fn hover_actions(&self) -> bool {
+        self.experimental_bool("hoverActions")
+    }
+
+    /// Whether the client supports colored output for full diagnostics from `checkOnSave`.
+    pub fn color_diagnostic_output(&self) -> bool {
+        self.experimental_bool("colorDiagnosticOutput")
+    }
+
+    pub fn test_explorer(&self) -> bool {
+        self.experimental_bool("testExplorer")
+    }
+
+    pub fn completion_snippet(&self) -> bool {
+        (|| -> _ {
+            self.0
+                .text_document
+                .as_ref()?
+                .completion
+                .as_ref()?
+                .completion_item
+                .as_ref()?
+                .snippet_support
+        })()
+        .unwrap_or_default()
+    }
+
+    pub fn semantic_tokens_refresh(&self) -> bool {
+        (|| -> _ { self.0.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support })()
+            .unwrap_or_default()
+    }
+
+    pub fn code_lens_refresh(&self) -> bool {
+        (|| -> _ { self.0.workspace.as_ref()?.code_lens.as_ref()?.refresh_support })()
+            .unwrap_or_default()
+    }
+
+    pub fn inlay_hints_refresh(&self) -> bool {
+        (|| -> _ { self.0.workspace.as_ref()?.inlay_hint.as_ref()?.refresh_support })()
+            .unwrap_or_default()
+    }
+
+    pub fn inlay_hint_resolve_support_properties(&self) -> FxHashSet<String> {
+        self.0
+            .text_document
+            .as_ref()
+            .and_then(|text| text.inlay_hint.as_ref())
+            .and_then(|inlay_hint_caps| inlay_hint_caps.resolve_support.as_ref())
+            .map(|inlay_resolve| inlay_resolve.properties.iter())
+            .into_iter()
+            .flatten()
+            .cloned()
+            .collect::<FxHashSet<_>>()
+    }
+
+    pub fn hover_markdown_support(&self) -> bool {
+        (|| -> _ {
+            Some(self.0.text_document.as_ref()?.hover.as_ref()?.content_format.as_ref()?.as_slice())
+        })()
+        .unwrap_or_default()
+        .contains(&lsp_types::MarkupKind::Markdown)
+    }
+
+    pub fn insert_replace_support(&self) -> bool {
+        (|| -> _ {
+            self.0
+                .text_document
+                .as_ref()?
+                .completion
+                .as_ref()?
+                .completion_item
+                .as_ref()?
+                .insert_replace_support
+        })()
+        .unwrap_or_default()
+    }
+}
+
+fn more_trigger_character(config: &Config) -> Vec<String> {
+    let mut res = vec![".".to_owned(), ">".to_owned(), "{".to_owned(), "(".to_owned()];
+    if config.snippet_cap().is_some() {
+        res.push("<".to_owned());
+    }
+    res
+}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/caps.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/caps.rs
deleted file mode 100644
index a207be3cac3..00000000000
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/caps.rs
+++ /dev/null
@@ -1,230 +0,0 @@
-//! Advertises the capabilities of the LSP Server.
-use ide_db::line_index::WideEncoding;
-use lsp_types::{
-    CallHierarchyServerCapability, ClientCapabilities, CodeActionKind, CodeActionOptions,
-    CodeActionProviderCapability, CodeLensOptions, CompletionOptions,
-    CompletionOptionsCompletionItem, DeclarationCapability, DocumentOnTypeFormattingOptions,
-    FileOperationFilter, FileOperationPattern, FileOperationPatternKind,
-    FileOperationRegistrationOptions, FoldingRangeProviderCapability, HoverProviderCapability,
-    ImplementationProviderCapability, InlayHintOptions, InlayHintServerCapabilities, OneOf,
-    PositionEncodingKind, RenameOptions, SaveOptions, SelectionRangeProviderCapability,
-    SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions, ServerCapabilities,
-    SignatureHelpOptions, TextDocumentSyncCapability, TextDocumentSyncKind,
-    TextDocumentSyncOptions, TypeDefinitionProviderCapability, WorkDoneProgressOptions,
-    WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities,
-    WorkspaceServerCapabilities,
-};
-use serde_json::json;
-
-use crate::{
-    config::{Config, RustfmtConfig},
-    line_index::PositionEncoding,
-    lsp::semantic_tokens,
-    lsp_ext::negotiated_encoding,
-};
-
-pub fn server_capabilities(config: &Config) -> ServerCapabilities {
-    ServerCapabilities {
-        position_encoding: match negotiated_encoding(config.caps()) {
-            PositionEncoding::Utf8 => Some(PositionEncodingKind::UTF8),
-            PositionEncoding::Wide(wide) => match wide {
-                WideEncoding::Utf16 => Some(PositionEncodingKind::UTF16),
-                WideEncoding::Utf32 => Some(PositionEncodingKind::UTF32),
-                _ => None,
-            },
-        },
-        text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
-            open_close: Some(true),
-            change: Some(TextDocumentSyncKind::INCREMENTAL),
-            will_save: None,
-            will_save_wait_until: None,
-            save: Some(SaveOptions::default().into()),
-        })),
-        hover_provider: Some(HoverProviderCapability::Simple(true)),
-        completion_provider: Some(CompletionOptions {
-            resolve_provider: completions_resolve_provider(config.caps()),
-            trigger_characters: Some(vec![
-                ":".to_owned(),
-                ".".to_owned(),
-                "'".to_owned(),
-                "(".to_owned(),
-            ]),
-            all_commit_characters: None,
-            completion_item: completion_item(config),
-            work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
-        }),
-        signature_help_provider: Some(SignatureHelpOptions {
-            trigger_characters: Some(vec!["(".to_owned(), ",".to_owned(), "<".to_owned()]),
-            retrigger_characters: None,
-            work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
-        }),
-        declaration_provider: Some(DeclarationCapability::Simple(true)),
-        definition_provider: Some(OneOf::Left(true)),
-        type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)),
-        implementation_provider: Some(ImplementationProviderCapability::Simple(true)),
-        references_provider: Some(OneOf::Left(true)),
-        document_highlight_provider: Some(OneOf::Left(true)),
-        document_symbol_provider: Some(OneOf::Left(true)),
-        workspace_symbol_provider: Some(OneOf::Left(true)),
-        code_action_provider: Some(code_action_capabilities(config.caps())),
-        code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
-        document_formatting_provider: Some(OneOf::Left(true)),
-        document_range_formatting_provider: match config.rustfmt() {
-            RustfmtConfig::Rustfmt { enable_range_formatting: true, .. } => Some(OneOf::Left(true)),
-            _ => Some(OneOf::Left(false)),
-        },
-        document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions {
-            first_trigger_character: "=".to_owned(),
-            more_trigger_character: Some(more_trigger_character(config)),
-        }),
-        selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)),
-        folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
-        rename_provider: Some(OneOf::Right(RenameOptions {
-            prepare_provider: Some(true),
-            work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
-        })),
-        linked_editing_range_provider: None,
-        document_link_provider: None,
-        color_provider: None,
-        execute_command_provider: None,
-        workspace: Some(WorkspaceServerCapabilities {
-            workspace_folders: Some(WorkspaceFoldersServerCapabilities {
-                supported: Some(true),
-                change_notifications: Some(OneOf::Left(true)),
-            }),
-            file_operations: Some(WorkspaceFileOperationsServerCapabilities {
-                did_create: None,
-                will_create: None,
-                did_rename: None,
-                will_rename: Some(FileOperationRegistrationOptions {
-                    filters: vec![
-                        FileOperationFilter {
-                            scheme: Some(String::from("file")),
-                            pattern: FileOperationPattern {
-                                glob: String::from("**/*.rs"),
-                                matches: Some(FileOperationPatternKind::File),
-                                options: None,
-                            },
-                        },
-                        FileOperationFilter {
-                            scheme: Some(String::from("file")),
-                            pattern: FileOperationPattern {
-                                glob: String::from("**"),
-                                matches: Some(FileOperationPatternKind::Folder),
-                                options: None,
-                            },
-                        },
-                    ],
-                }),
-                did_delete: None,
-                will_delete: None,
-            }),
-        }),
-        call_hierarchy_provider: Some(CallHierarchyServerCapability::Simple(true)),
-        semantic_tokens_provider: Some(
-            SemanticTokensOptions {
-                legend: SemanticTokensLegend {
-                    token_types: semantic_tokens::SUPPORTED_TYPES.to_vec(),
-                    token_modifiers: semantic_tokens::SUPPORTED_MODIFIERS.to_vec(),
-                },
-
-                full: Some(SemanticTokensFullOptions::Delta { delta: Some(true) }),
-                range: Some(true),
-                work_done_progress_options: Default::default(),
-            }
-            .into(),
-        ),
-        moniker_provider: None,
-        inlay_hint_provider: Some(OneOf::Right(InlayHintServerCapabilities::Options(
-            InlayHintOptions {
-                work_done_progress_options: Default::default(),
-                resolve_provider: Some(true),
-            },
-        ))),
-        inline_value_provider: None,
-        experimental: Some(json!({
-            "externalDocs": true,
-            "hoverRange": true,
-            "joinLines": true,
-            "matchingBrace": true,
-            "moveItem": true,
-            "onEnter": true,
-            "openCargoToml": true,
-            "parentModule": true,
-            "runnables": {
-                "kinds": [ "cargo" ],
-            },
-            "ssr": true,
-            "workspaceSymbolScopeKindFiltering": true,
-        })),
-        diagnostic_provider: None,
-        inline_completion_provider: None,
-    }
-}
-
-fn completions_resolve_provider(client_caps: &ClientCapabilities) -> Option<bool> {
-    if completion_item_edit_resolve(client_caps) {
-        Some(true)
-    } else {
-        tracing::info!("No `additionalTextEdits` completion resolve capability was found in the client capabilities, autoimport completion is disabled");
-        None
-    }
-}
-
-/// Parses client capabilities and returns all completion resolve capabilities rust-analyzer supports.
-pub(crate) fn completion_item_edit_resolve(caps: &ClientCapabilities) -> bool {
-    (|| {
-        Some(
-            caps.text_document
-                .as_ref()?
-                .completion
-                .as_ref()?
-                .completion_item
-                .as_ref()?
-                .resolve_support
-                .as_ref()?
-                .properties
-                .iter()
-                .any(|cap_string| cap_string.as_str() == "additionalTextEdits"),
-        )
-    })() == Some(true)
-}
-
-fn completion_item(config: &Config) -> Option<CompletionOptionsCompletionItem> {
-    Some(CompletionOptionsCompletionItem {
-        label_details_support: Some(config.completion_label_details_support()),
-    })
-}
-
-fn code_action_capabilities(client_caps: &ClientCapabilities) -> CodeActionProviderCapability {
-    client_caps
-        .text_document
-        .as_ref()
-        .and_then(|it| it.code_action.as_ref())
-        .and_then(|it| it.code_action_literal_support.as_ref())
-        .map_or(CodeActionProviderCapability::Simple(true), |_| {
-            CodeActionProviderCapability::Options(CodeActionOptions {
-                // Advertise support for all built-in CodeActionKinds.
-                // Ideally we would base this off of the client capabilities
-                // but the client is supposed to fall back gracefully for unknown values.
-                code_action_kinds: Some(vec![
-                    CodeActionKind::EMPTY,
-                    CodeActionKind::QUICKFIX,
-                    CodeActionKind::REFACTOR,
-                    CodeActionKind::REFACTOR_EXTRACT,
-                    CodeActionKind::REFACTOR_INLINE,
-                    CodeActionKind::REFACTOR_REWRITE,
-                ]),
-                resolve_provider: Some(true),
-                work_done_progress_options: Default::default(),
-            })
-        })
-}
-
-fn more_trigger_character(config: &Config) -> Vec<String> {
-    let mut res = vec![".".to_owned(), ">".to_owned(), "{".to_owned(), "(".to_owned()];
-    if config.snippet_cap().is_some() {
-        res.push("<".to_owned());
-    }
-    res
-}
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 ef80d83837d..948bc2c6c77 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
@@ -19,7 +19,6 @@ use ide_db::{
     SnippetCap,
 };
 use itertools::Itertools;
-use lsp_types::{ClientCapabilities, MarkupKind};
 use paths::{Utf8Path, Utf8PathBuf};
 use project_model::{
     CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustLibSource,
@@ -35,10 +34,9 @@ use triomphe::Arc;
 use vfs::{AbsPath, AbsPathBuf, VfsPath};
 
 use crate::{
-    caps::completion_item_edit_resolve,
+    capabilities::ClientCapabilities,
     diagnostics::DiagnosticsMapConfig,
-    line_index::PositionEncoding,
-    lsp_ext::{self, negotiated_encoding, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
+    lsp_ext::{WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
 };
 
 mod patch_old_style;
@@ -659,7 +657,7 @@ pub struct Config {
     discovered_projects: Vec<ProjectManifest>,
     /// The workspace roots as registered by the LSP client
     workspace_roots: Vec<AbsPathBuf>,
-    caps: lsp_types::ClientCapabilities,
+    caps: ClientCapabilities,
     root_path: AbsPathBuf,
     snippets: Vec<Snippet>,
     visual_studio_code_version: Option<Version>,
@@ -698,6 +696,15 @@ pub struct Config {
     detached_files: Vec<AbsPathBuf>,
 }
 
+// Delegate capability fetching methods
+impl std::ops::Deref for Config {
+    type Target = ClientCapabilities;
+
+    fn deref(&self) -> &Self::Target {
+        &self.caps
+    }
+}
+
 impl Config {
     pub fn user_config_path(&self) -> &VfsPath {
         &self.user_config_path
@@ -954,23 +961,6 @@ impl ConfigChange {
     }
 }
 
-macro_rules! try_ {
-    ($expr:expr) => {
-        || -> _ { Some($expr) }()
-    };
-}
-macro_rules! try_or {
-    ($expr:expr, $or:expr) => {
-        try_!($expr).unwrap_or($or)
-    };
-}
-
-macro_rules! try_or_def {
-    ($expr:expr) => {
-        try_!($expr).unwrap_or_default()
-    };
-}
-
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub enum LinkedProject {
     ProjectManifest(ProjectManifest),
@@ -1177,7 +1167,7 @@ impl std::error::Error for ConfigErrors {}
 impl Config {
     pub fn new(
         root_path: AbsPathBuf,
-        caps: ClientCapabilities,
+        caps: lsp_types::ClientCapabilities,
         workspace_roots: Vec<AbsPathBuf>,
         visual_studio_code_version: Option<Version>,
         user_config_path: Option<Utf8PathBuf>,
@@ -1205,7 +1195,7 @@ impl Config {
         };
 
         Config {
-            caps,
+            caps: ClientCapabilities::new(caps),
             discovered_projects: Vec::new(),
             root_path,
             snippets: Default::default(),
@@ -1266,7 +1256,7 @@ impl Config {
         &self.root_ratoml_path
     }
 
-    pub fn caps(&self) -> &lsp_types::ClientCapabilities {
+    pub fn caps(&self) -> &ClientCapabilities {
         &self.caps
     }
 }
@@ -1289,7 +1279,7 @@ impl Config {
         CompletionConfig {
             enable_postfix_completions: self.completion_postfix_enable().to_owned(),
             enable_imports_on_the_fly: self.completion_autoimport_enable().to_owned()
-                && completion_item_edit_resolve(&self.caps),
+                && self.caps.completion_item_edit_resolve(),
             enable_self_on_the_fly: self.completion_autoself_enable().to_owned(),
             enable_private_editable: self.completion_privateEditable_enable().to_owned(),
             full_function_signatures: self.completion_fullFunctionSignatures_enable().to_owned(),
@@ -1298,16 +1288,7 @@ impl Config {
                 CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
                 CallableCompletionDef::None => None,
             },
-            snippet_cap: SnippetCap::new(try_or_def!(
-                self.caps
-                    .text_document
-                    .as_ref()?
-                    .completion
-                    .as_ref()?
-                    .completion_item
-                    .as_ref()?
-                    .snippet_support?
-            )),
+            snippet_cap: SnippetCap::new(self.completion_snippet()),
             insert_use: self.insert_use_config(source_root),
             prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
             prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
@@ -1359,7 +1340,7 @@ impl Config {
     }
 
     pub fn hover_actions(&self) -> HoverActionsConfig {
-        let enable = self.experimental("hoverActions") && self.hover_actions_enable().to_owned();
+        let enable = self.caps.hover_actions() && self.hover_actions_enable().to_owned();
         HoverActionsConfig {
             implementations: enable && self.hover_actions_implementations_enable().to_owned(),
             references: enable && self.hover_actions_references_enable().to_owned(),
@@ -1385,17 +1366,7 @@ impl Config {
             }),
             documentation: self.hover_documentation_enable().to_owned(),
             format: {
-                let is_markdown = try_or_def!(self
-                    .caps
-                    .text_document
-                    .as_ref()?
-                    .hover
-                    .as_ref()?
-                    .content_format
-                    .as_ref()?
-                    .as_slice())
-                .contains(&MarkupKind::Markdown);
-                if is_markdown {
+                if self.caps.hover_markdown_support() {
                     HoverDocFormat::Markdown
                 } else {
                     HoverDocFormat::PlainText
@@ -1409,17 +1380,7 @@ impl Config {
     }
 
     pub fn inlay_hints(&self) -> InlayHintsConfig {
-        let client_capability_fields = self
-            .caps
-            .text_document
-            .as_ref()
-            .and_then(|text| text.inlay_hint.as_ref())
-            .and_then(|inlay_hint_caps| inlay_hint_caps.resolve_support.as_ref())
-            .map(|inlay_resolve| inlay_resolve.properties.iter())
-            .into_iter()
-            .flatten()
-            .cloned()
-            .collect::<FxHashSet<_>>();
+        let client_capability_fields = self.inlay_hint_resolve_support_properties();
 
         InlayHintsConfig {
             render_colons: self.inlayHints_renderColons().to_owned(),
@@ -1590,165 +1551,10 @@ impl Config {
         }
     }
 
-    pub fn did_save_text_document_dynamic_registration(&self) -> bool {
-        let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?);
-        caps.did_save == Some(true) && caps.dynamic_registration == Some(true)
-    }
-
-    pub fn did_change_watched_files_dynamic_registration(&self) -> bool {
-        try_or_def!(
-            self.caps.workspace.as_ref()?.did_change_watched_files.as_ref()?.dynamic_registration?
-        )
-    }
-
-    pub fn did_change_watched_files_relative_pattern_support(&self) -> bool {
-        try_or_def!(
-            self.caps
-                .workspace
-                .as_ref()?
-                .did_change_watched_files
-                .as_ref()?
-                .relative_pattern_support?
-        )
-    }
-
     pub fn prefill_caches(&self) -> bool {
         self.cachePriming_enable().to_owned()
     }
 
-    pub fn location_link(&self) -> bool {
-        try_or_def!(self.caps.text_document.as_ref()?.definition?.link_support?)
-    }
-
-    pub fn line_folding_only(&self) -> bool {
-        try_or_def!(self.caps.text_document.as_ref()?.folding_range.as_ref()?.line_folding_only?)
-    }
-
-    pub fn hierarchical_symbols(&self) -> bool {
-        try_or_def!(
-            self.caps
-                .text_document
-                .as_ref()?
-                .document_symbol
-                .as_ref()?
-                .hierarchical_document_symbol_support?
-        )
-    }
-
-    pub fn code_action_literals(&self) -> bool {
-        try_!(self
-            .caps
-            .text_document
-            .as_ref()?
-            .code_action
-            .as_ref()?
-            .code_action_literal_support
-            .as_ref()?)
-        .is_some()
-    }
-
-    pub fn work_done_progress(&self) -> bool {
-        try_or_def!(self.caps.window.as_ref()?.work_done_progress?)
-    }
-
-    pub fn will_rename(&self) -> bool {
-        try_or_def!(self.caps.workspace.as_ref()?.file_operations.as_ref()?.will_rename?)
-    }
-
-    pub fn change_annotation_support(&self) -> bool {
-        try_!(self
-            .caps
-            .workspace
-            .as_ref()?
-            .workspace_edit
-            .as_ref()?
-            .change_annotation_support
-            .as_ref()?)
-        .is_some()
-    }
-
-    pub fn code_action_resolve(&self) -> bool {
-        try_or_def!(self
-            .caps
-            .text_document
-            .as_ref()?
-            .code_action
-            .as_ref()?
-            .resolve_support
-            .as_ref()?
-            .properties
-            .as_slice())
-        .iter()
-        .any(|it| it == "edit")
-    }
-
-    pub fn signature_help_label_offsets(&self) -> bool {
-        try_or_def!(
-            self.caps
-                .text_document
-                .as_ref()?
-                .signature_help
-                .as_ref()?
-                .signature_information
-                .as_ref()?
-                .parameter_information
-                .as_ref()?
-                .label_offset_support?
-        )
-    }
-
-    pub fn completion_label_details_support(&self) -> bool {
-        try_!(self
-            .caps
-            .text_document
-            .as_ref()?
-            .completion
-            .as_ref()?
-            .completion_item
-            .as_ref()?
-            .label_details_support
-            .as_ref()?)
-        .is_some()
-    }
-
-    pub fn semantics_tokens_augments_syntax_tokens(&self) -> bool {
-        try_!(self.caps.text_document.as_ref()?.semantic_tokens.as_ref()?.augments_syntax_tokens?)
-            .unwrap_or(false)
-    }
-
-    pub fn position_encoding(&self) -> PositionEncoding {
-        negotiated_encoding(&self.caps)
-    }
-
-    fn experimental(&self, index: &'static str) -> bool {
-        try_or_def!(self.caps.experimental.as_ref()?.get(index)?.as_bool()?)
-    }
-
-    pub fn code_action_group(&self) -> bool {
-        self.experimental("codeActionGroup")
-    }
-
-    pub fn local_docs(&self) -> bool {
-        self.experimental("localDocs")
-    }
-
-    pub fn open_server_logs(&self) -> bool {
-        self.experimental("openServerLogs")
-    }
-
-    pub fn server_status_notification(&self) -> bool {
-        self.experimental("serverStatusNotification")
-    }
-
-    /// Whether the client supports colored output for full diagnostics from `checkOnSave`.
-    pub fn color_diagnostic_output(&self) -> bool {
-        self.experimental("colorDiagnosticOutput")
-    }
-
-    pub fn test_explorer(&self) -> bool {
-        self.experimental("testExplorer")
-    }
-
     pub fn publish_diagnostics(&self) -> bool {
         self.diagnostics_enable().to_owned()
     }
@@ -2026,7 +1832,7 @@ impl Config {
     pub fn snippet_cap(&self) -> Option<SnippetCap> {
         // FIXME: Also detect the proposed lsp version at caps.workspace.workspaceEdit.snippetEditSupport
         // once lsp-types has it.
-        SnippetCap::new(self.experimental("snippetTextEdit"))
+        SnippetCap::new(self.snippet_text_edit())
     }
 
     pub fn call_info(&self) -> CallInfoConfig {
@@ -2066,36 +1872,8 @@ impl Config {
         }
     }
 
-    pub fn semantic_tokens_refresh(&self) -> bool {
-        try_or_def!(self.caps.workspace.as_ref()?.semantic_tokens.as_ref()?.refresh_support?)
-    }
-
-    pub fn code_lens_refresh(&self) -> bool {
-        try_or_def!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?)
-    }
-
-    pub fn inlay_hints_refresh(&self) -> bool {
-        try_or_def!(self.caps.workspace.as_ref()?.inlay_hint.as_ref()?.refresh_support?)
-    }
-
-    pub fn insert_replace_support(&self) -> bool {
-        try_or_def!(
-            self.caps
-                .text_document
-                .as_ref()?
-                .completion
-                .as_ref()?
-                .completion_item
-                .as_ref()?
-                .insert_replace_support?
-        )
-    }
-
     pub fn client_commands(&self) -> ClientCommandsConfig {
-        let commands =
-            try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null);
-        let commands: Option<lsp_ext::ClientCommandOptions> =
-            serde_json::from_value(commands.clone()).ok();
+        let commands = self.commands();
         let force = commands.is_none() && *self.lens_forceCustomCommands();
         let commands = commands.map(|it| it.commands).unwrap_or_default();
 
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs
index 4832e8cab43..defa464f2ba 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -66,7 +66,7 @@ fn location(
     let uri = url_from_abs_path(&file_name);
 
     let range = {
-        let position_encoding = snap.config.position_encoding();
+        let position_encoding = snap.config.negotiated_encoding();
         lsp_types::Range::new(
             position(
                 &position_encoding,
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs
index 717d8a632c3..de4c9586dfd 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs
@@ -529,7 +529,7 @@ impl GlobalStateSnapshot {
     pub(crate) fn file_line_index(&self, file_id: FileId) -> Cancellable<LineIndex> {
         let endings = self.vfs.read().1[&file_id];
         let index = self.analysis.file_line_index(file_id)?;
-        let res = LineIndex { index, endings, encoding: self.config.position_encoding() };
+        let res = LineIndex { index, endings, encoding: self.config.caps().negotiated_encoding() };
         Ok(res)
     }
 
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs
index 2dbc297ea6c..095d7c941c1 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs
@@ -100,7 +100,7 @@ pub(crate) fn handle_did_change_text_document(
         *version = params.text_document.version;
 
         let new_contents = apply_document_changes(
-            state.config.position_encoding(),
+            state.config.negotiated_encoding(),
             std::str::from_utf8(data).unwrap(),
             params.content_changes,
         )
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 2e002876832..40ca9c3fa9e 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
@@ -2294,19 +2294,8 @@ fn to_url(path: VfsPath) -> Option<Url> {
 }
 
 fn resource_ops_supported(config: &Config, kind: ResourceOperationKind) -> anyhow::Result<()> {
-    #[rustfmt::skip]
-    let resops = (|| {
-        config
-            .caps()
-            .workspace
-            .as_ref()?
-            .workspace_edit
-            .as_ref()?
-            .resource_operations
-            .as_ref()
-    })();
-
-    if !matches!(resops, Some(resops) if resops.contains(&kind)) {
+    if !matches!(config.workspace_edit_resource_operations(), Some(resops) if resops.contains(&kind))
+    {
         return Err(LspError::new(
             ErrorCode::RequestFailed as i32,
             format!(
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs
index a8e6657c24c..174979edede 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs
@@ -11,7 +11,7 @@
 
 pub mod cli;
 
-mod caps;
+mod capabilities;
 mod diagnostics;
 mod diff;
 mod dispatch;
@@ -47,7 +47,8 @@ mod integrated_benchmarks;
 use serde::de::DeserializeOwned;
 
 pub use crate::{
-    caps::server_capabilities, main_loop::main_loop, reload::ws_to_crate_graph, version::version,
+    capabilities::server_capabilities, main_loop::main_loop, reload::ws_to_crate_graph,
+    version::version,
 };
 
 pub fn from_json<T: DeserializeOwned>(
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 f1d08c25d8c..9a852067f2e 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
@@ -4,19 +4,16 @@
 
 use std::ops;
 
-use ide_db::line_index::WideEncoding;
 use lsp_types::request::Request;
+use lsp_types::Url;
 use lsp_types::{
     notification::Notification, CodeActionKind, DocumentOnTypeFormattingParams,
     PartialResultParams, Position, Range, TextDocumentIdentifier, WorkDoneProgressParams,
 };
-use lsp_types::{PositionEncodingKind, Url};
 use paths::Utf8PathBuf;
 use rustc_hash::FxHashMap;
 use serde::{Deserialize, Serialize};
 
-use crate::line_index::PositionEncoding;
-
 pub enum InternalTestingFetchConfig {}
 
 impl Request for InternalTestingFetchConfig {
@@ -737,24 +734,6 @@ pub enum CodeLensResolveDataKind {
     References(lsp_types::TextDocumentPositionParams),
 }
 
-pub fn negotiated_encoding(caps: &lsp_types::ClientCapabilities) -> PositionEncoding {
-    let client_encodings = match &caps.general {
-        Some(general) => general.position_encodings.as_deref().unwrap_or_default(),
-        None => &[],
-    };
-
-    for enc in client_encodings {
-        if enc == &PositionEncodingKind::UTF8 {
-            return PositionEncoding::Utf8;
-        } else if enc == &PositionEncodingKind::UTF32 {
-            return PositionEncoding::Wide(WideEncoding::Utf32);
-        }
-        // NB: intentionally prefer just about anything else to utf-16.
-    }
-
-    PositionEncoding::Wide(WideEncoding::Utf16)
-}
-
 pub enum MoveItem {}
 
 impl Request for MoveItem {
diff --git a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md
index a1470fc567b..74acb6f9940 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: a0867710490bf8da
+lsp/ext.rs hash: 39b47906286ad9c
 
 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: