Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/rust-analyzer/src/capabilities.rs')
| -rw-r--r-- | crates/rust-analyzer/src/capabilities.rs | 493 |
1 files changed, 493 insertions, 0 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 +} |