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 { + 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(&self, index: &'static str) -> Option { + 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 { + 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 { + 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 { + 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::>() + } + + 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 { + 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 { - 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 { - 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 { - 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, /// The workspace roots as registered by the LSP client workspace_roots: Vec, - caps: lsp_types::ClientCapabilities, + caps: ClientCapabilities, root_path: AbsPathBuf, snippets: Vec, visual_studio_code_version: Option, @@ -698,6 +696,15 @@ pub struct Config { detached_files: Vec, } +// 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, visual_studio_code_version: Option, user_config_path: Option, @@ -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::>(); + 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 { // 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 = - 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 { 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 { } 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( 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 @@