Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #17527 - Veykril:caps-config, r=Veykril
internal: Move capability querying out of the config module
bors 2024-07-07
parent 058c88d · parent e4604c6 · commit 8c2adec
-rw-r--r--crates/rust-analyzer/src/capabilities.rs493
-rw-r--r--crates/rust-analyzer/src/caps.rs230
-rw-r--r--crates/rust-analyzer/src/config.rs266
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs2
-rw-r--r--crates/rust-analyzer/src/global_state.rs2
-rw-r--r--crates/rust-analyzer/src/handlers/notification.rs2
-rw-r--r--crates/rust-analyzer/src/handlers/request.rs15
-rw-r--r--crates/rust-analyzer/src/lib.rs5
-rw-r--r--crates/rust-analyzer/src/lsp/ext.rs23
-rw-r--r--docs/dev/lsp-extensions.md2
10 files changed, 525 insertions, 515 deletions
diff --git a/crates/rust-analyzer/src/capabilities.rs b/crates/rust-analyzer/src/capabilities.rs
new file mode 100644
index 0000000000..212294b5d3
--- /dev/null
+++ b/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/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
deleted file mode 100644
index a207be3cac..0000000000
--- a/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/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index ef80d83837..948bc2c6c7 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/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/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index 4832e8cab4..defa464f2b 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/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/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 717d8a632c..de4c9586df 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/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/crates/rust-analyzer/src/handlers/notification.rs b/crates/rust-analyzer/src/handlers/notification.rs
index 2dbc297ea6..095d7c941c 100644
--- a/crates/rust-analyzer/src/handlers/notification.rs
+++ b/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/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs
index 2e00287683..40ca9c3fa9 100644
--- a/crates/rust-analyzer/src/handlers/request.rs
+++ b/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/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs
index a8e6657c24..174979eded 100644
--- a/crates/rust-analyzer/src/lib.rs
+++ b/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/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs
index f1d08c25d8..9a852067f2 100644
--- a/crates/rust-analyzer/src/lsp/ext.rs
+++ b/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/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index a1470fc567..74acb6f994 100644
--- a/docs/dev/lsp-extensions.md
+++ b/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: