diff --git a/Cargo.lock b/Cargo.lock index 7111dced54..742469e935 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4300,6 +4300,7 @@ name = "ty_server" version = "0.0.0" dependencies = [ "anyhow", + "bitflags 2.9.1", "crossbeam", "jod-thread", "libc", diff --git a/crates/ty_server/Cargo.toml b/crates/ty_server/Cargo.toml index b85f0aacc1..0a74ebe9f0 100644 --- a/crates/ty_server/Cargo.toml +++ b/crates/ty_server/Cargo.toml @@ -23,6 +23,7 @@ ty_python_semantic = { workspace = true } ty_vendored = { workspace = true } anyhow = { workspace = true } +bitflags = { workspace = true } crossbeam = { workspace = true } jod-thread = { workspace = true } lsp-server = { workspace = true } diff --git a/crates/ty_server/src/server/api/diagnostics.rs b/crates/ty_server/src/server/api/diagnostics.rs index fe585ff105..438dc1c172 100644 --- a/crates/ty_server/src/server/api/diagnostics.rs +++ b/crates/ty_server/src/server/api/diagnostics.rs @@ -64,7 +64,7 @@ pub(super) fn clear_diagnostics(key: &DocumentKey, client: &Client) { /// /// [publish diagnostics notification]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics pub(super) fn publish_diagnostics(session: &Session, key: &DocumentKey, client: &Client) { - if session.client_capabilities().pull_diagnostics { + if session.client_capabilities().supports_pull_diagnostics() { return; } diff --git a/crates/ty_server/src/server/api/notifications/did_change_watched_files.rs b/crates/ty_server/src/server/api/notifications/did_change_watched_files.rs index 2cdaa1e3c6..16fbbe2aee 100644 --- a/crates/ty_server/src/server/api/notifications/did_change_watched_files.rs +++ b/crates/ty_server/src/server/api/notifications/did_change_watched_files.rs @@ -96,7 +96,7 @@ impl SyncNotificationHandler for DidChangeWatchedFiles { let client_capabilities = session.client_capabilities(); if project_changed { - if client_capabilities.diagnostics_refresh { + if client_capabilities.supports_workspace_diagnostic_refresh() { client.send_request::( session, (), @@ -111,7 +111,7 @@ impl SyncNotificationHandler for DidChangeWatchedFiles { // TODO: always publish diagnostics for notebook files (since they don't use pull diagnostics) } - if client_capabilities.inlay_refresh { + if client_capabilities.supports_inlay_hint_refresh() { client.send_request::(session, (), |_, ()| {}); } diff --git a/crates/ty_server/src/server/api/requests/goto_declaration.rs b/crates/ty_server/src/server/api/requests/goto_declaration.rs index 9ab17a4182..27297afef5 100644 --- a/crates/ty_server/src/server/api/requests/goto_declaration.rs +++ b/crates/ty_server/src/server/api/requests/goto_declaration.rs @@ -52,7 +52,7 @@ impl BackgroundDocumentRequestHandler for GotoDeclarationRequestHandler { if snapshot .resolved_client_capabilities() - .type_definition_link_support + .supports_declaration_link() { let src = Some(ranged.range); let links: Vec<_> = ranged diff --git a/crates/ty_server/src/server/api/requests/goto_definition.rs b/crates/ty_server/src/server/api/requests/goto_definition.rs index fe888b2e71..cc41f1dee6 100644 --- a/crates/ty_server/src/server/api/requests/goto_definition.rs +++ b/crates/ty_server/src/server/api/requests/goto_definition.rs @@ -52,7 +52,7 @@ impl BackgroundDocumentRequestHandler for GotoDefinitionRequestHandler { if snapshot .resolved_client_capabilities() - .type_definition_link_support + .supports_definition_link() { let src = Some(ranged.range); let links: Vec<_> = ranged diff --git a/crates/ty_server/src/server/api/requests/goto_type_definition.rs b/crates/ty_server/src/server/api/requests/goto_type_definition.rs index cb18fca66d..6fb901b6d9 100644 --- a/crates/ty_server/src/server/api/requests/goto_type_definition.rs +++ b/crates/ty_server/src/server/api/requests/goto_type_definition.rs @@ -52,7 +52,7 @@ impl BackgroundDocumentRequestHandler for GotoTypeDefinitionRequestHandler { if snapshot .resolved_client_capabilities() - .type_definition_link_support + .supports_type_definition_link() { let src = Some(ranged.range); let links: Vec<_> = ranged diff --git a/crates/ty_server/src/server/api/requests/hover.rs b/crates/ty_server/src/server/api/requests/hover.rs index 4562930b85..cc4a9e15a4 100644 --- a/crates/ty_server/src/server/api/requests/hover.rs +++ b/crates/ty_server/src/server/api/requests/hover.rs @@ -52,7 +52,7 @@ impl BackgroundDocumentRequestHandler for HoverRequestHandler { let (markup_kind, lsp_markup_kind) = if snapshot .resolved_client_capabilities() - .hover_prefer_markdown + .prefers_markdown_in_hover() { (MarkupKind::Markdown, lsp_types::MarkupKind::Markdown) } else { diff --git a/crates/ty_server/src/server/api/requests/semantic_tokens.rs b/crates/ty_server/src/server/api/requests/semantic_tokens.rs index 3871aed50d..8d23ecd054 100644 --- a/crates/ty_server/src/server/api/requests/semantic_tokens.rs +++ b/crates/ty_server/src/server/api/requests/semantic_tokens.rs @@ -41,7 +41,7 @@ impl BackgroundDocumentRequestHandler for SemanticTokensRequestHandler { snapshot.encoding(), snapshot .resolved_client_capabilities() - .semantic_tokens_multiline_support, + .supports_multiline_semantic_tokens(), ); Ok(Some(SemanticTokensResult::Tokens(SemanticTokens { diff --git a/crates/ty_server/src/server/api/requests/semantic_tokens_range.rs b/crates/ty_server/src/server/api/requests/semantic_tokens_range.rs index 3abaab0120..e14aedb925 100644 --- a/crates/ty_server/src/server/api/requests/semantic_tokens_range.rs +++ b/crates/ty_server/src/server/api/requests/semantic_tokens_range.rs @@ -51,7 +51,7 @@ impl BackgroundDocumentRequestHandler for SemanticTokensRangeRequestHandler { snapshot.encoding(), snapshot .resolved_client_capabilities() - .semantic_tokens_multiline_support, + .supports_multiline_semantic_tokens(), ); Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens { diff --git a/crates/ty_server/src/server/api/requests/signature_help.rs b/crates/ty_server/src/server/api/requests/signature_help.rs index 825c352cf1..71363fceed 100644 --- a/crates/ty_server/src/server/api/requests/signature_help.rs +++ b/crates/ty_server/src/server/api/requests/signature_help.rs @@ -71,7 +71,7 @@ impl BackgroundDocumentRequestHandler for SignatureHelpRequestHandler { .parameters .into_iter() .map(|param| { - let label = if resolved_capabilities.signature_label_offset_support { + let label = if resolved_capabilities.supports_signature_label_offset() { // Find the parameter's offset in the signature label if let Some(start) = sig.label.find(¶m.label) { let encoding = snapshot.encoding(); @@ -114,11 +114,12 @@ impl BackgroundDocumentRequestHandler for SignatureHelpRequestHandler { }) .collect(); - let active_parameter = if resolved_capabilities.signature_active_parameter_support { - sig.active_parameter.and_then(|p| u32::try_from(p).ok()) - } else { - None - }; + let active_parameter = + if resolved_capabilities.supports_signature_active_parameter() { + sig.active_parameter.and_then(|p| u32::try_from(p).ok()) + } else { + None + }; SignatureInformation { label: sig.label, diff --git a/crates/ty_server/src/session.rs b/crates/ty_server/src/session.rs index c71e1e7301..e0eac2d046 100644 --- a/crates/ty_server/src/session.rs +++ b/crates/ty_server/src/session.rs @@ -62,7 +62,7 @@ pub(crate) struct Session { position_encoding: PositionEncoding, /// Tracks what LSP features the client supports and doesn't support. - resolved_client_capabilities: Arc, + resolved_client_capabilities: ResolvedClientCapabilities, /// Tracks the pending requests between client and server. request_queue: RequestQueue, @@ -94,9 +94,7 @@ impl Session { index: Some(index), default_project: DefaultProject::new(), projects: BTreeMap::new(), - resolved_client_capabilities: Arc::new(ResolvedClientCapabilities::new( - client_capabilities, - )), + resolved_client_capabilities: ResolvedClientCapabilities::new(client_capabilities), request_queue: RequestQueue::new(), shutdown_requested: false, }) @@ -332,7 +330,7 @@ impl Session { pub(crate) fn take_document_snapshot(&self, url: Url) -> DocumentSnapshot { let index = self.index(); DocumentSnapshot { - resolved_client_capabilities: self.resolved_client_capabilities.clone(), + resolved_client_capabilities: self.resolved_client_capabilities, client_settings: index.global_settings(), position_encoding: self.position_encoding, document_query_result: self @@ -432,8 +430,8 @@ impl Session { } } - pub(crate) fn client_capabilities(&self) -> &ResolvedClientCapabilities { - &self.resolved_client_capabilities + pub(crate) fn client_capabilities(&self) -> ResolvedClientCapabilities { + self.resolved_client_capabilities } pub(crate) fn global_settings(&self) -> Arc { @@ -483,7 +481,7 @@ impl Drop for MutIndexGuard<'_> { /// An immutable snapshot of [`Session`] that references a specific document. #[derive(Debug)] pub(crate) struct DocumentSnapshot { - resolved_client_capabilities: Arc, + resolved_client_capabilities: ResolvedClientCapabilities, client_settings: Arc, position_encoding: PositionEncoding, document_query_result: Result, @@ -491,8 +489,8 @@ pub(crate) struct DocumentSnapshot { impl DocumentSnapshot { /// Returns the resolved client capabilities that were captured during initialization. - pub(crate) fn resolved_client_capabilities(&self) -> &ResolvedClientCapabilities { - &self.resolved_client_capabilities + pub(crate) fn resolved_client_capabilities(&self) -> ResolvedClientCapabilities { + self.resolved_client_capabilities } /// Returns the position encoding that was negotiated during initialization. diff --git a/crates/ty_server/src/session/capabilities.rs b/crates/ty_server/src/session/capabilities.rs index e84212fe79..86fd428123 100644 --- a/crates/ty_server/src/session/capabilities.rs +++ b/crates/ty_server/src/session/capabilities.rs @@ -1,87 +1,121 @@ use lsp_types::{ClientCapabilities, MarkupKind}; -#[derive(Debug, Clone, PartialEq, Eq, Default)] -#[expect(clippy::struct_excessive_bools)] -pub(crate) struct ResolvedClientCapabilities { - pub(crate) code_action_deferred_edit_resolution: bool, - pub(crate) apply_edit: bool, - pub(crate) document_changes: bool, - pub(crate) diagnostics_refresh: bool, - pub(crate) inlay_refresh: bool, - - /// Whether [pull diagnostics] is supported. +bitflags::bitflags! { + /// Represents the resolved client capabilities for the language server. /// - /// [pull diagnostics]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics - pub(crate) pull_diagnostics: bool, - - /// Whether `textDocument.typeDefinition.linkSupport` is `true` - pub(crate) type_definition_link_support: bool, - - /// `true`, if the first markup kind in `textDocument.hover.contentFormat` is `Markdown` - pub(crate) hover_prefer_markdown: bool, - - /// Whether the client supports multiline semantic tokens - pub(crate) semantic_tokens_multiline_support: bool, - - /// Whether the client supports signature label offsets in signature help - pub(crate) signature_label_offset_support: bool, - - /// Whether the client supports per-signature active parameter in signature help - pub(crate) signature_active_parameter_support: bool, + /// This tracks various capabilities that the client supports. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] + pub(crate) struct ResolvedClientCapabilities: u32 { + const WORKSPACE_DIAGNOSTIC_REFRESH = 1 << 0; + const INLAY_HINT_REFRESH = 1 << 1; + const PULL_DIAGNOSTICS = 1 << 2; + const TYPE_DEFINITION_LINK_SUPPORT = 1 << 3; + const DEFINITION_LINK_SUPPORT = 1 << 4; + const DECLARATION_LINK_SUPPORT = 1 << 5; + const PREFER_MARKDOWN_IN_HOVER = 1 << 6; + const MULTILINE_SEMANTIC_TOKENS = 1 << 7; + const SIGNATURE_LABEL_OFFSET_SUPPORT = 1 << 8; + const SIGNATURE_ACTIVE_PARAMETER_SUPPORT = 1 << 9; + } } impl ResolvedClientCapabilities { + /// Returns `true` if the client supports workspace diagnostic refresh. + pub(crate) const fn supports_workspace_diagnostic_refresh(self) -> bool { + self.contains(Self::WORKSPACE_DIAGNOSTIC_REFRESH) + } + + /// Returns `true` if the client supports inlay hint refresh. + pub(crate) const fn supports_inlay_hint_refresh(self) -> bool { + self.contains(Self::INLAY_HINT_REFRESH) + } + + /// Returns `true` if the client supports pull diagnostics. + pub(crate) const fn supports_pull_diagnostics(self) -> bool { + self.contains(Self::PULL_DIAGNOSTICS) + } + + /// Returns `true` if the client supports definition links in goto type definition. + pub(crate) const fn supports_type_definition_link(self) -> bool { + self.contains(Self::TYPE_DEFINITION_LINK_SUPPORT) + } + + /// Returns `true` if the client supports definition links in goto definition. + pub(crate) const fn supports_definition_link(self) -> bool { + self.contains(Self::DEFINITION_LINK_SUPPORT) + } + + /// Returns `true` if the client supports definition links in goto declaration. + pub(crate) const fn supports_declaration_link(self) -> bool { + self.contains(Self::DECLARATION_LINK_SUPPORT) + } + + /// Returns `true` if the client prefers markdown in hover responses. + pub(crate) const fn prefers_markdown_in_hover(self) -> bool { + self.contains(Self::PREFER_MARKDOWN_IN_HOVER) + } + + /// Returns `true` if the client supports multiline semantic tokens. + pub(crate) const fn supports_multiline_semantic_tokens(self) -> bool { + self.contains(Self::MULTILINE_SEMANTIC_TOKENS) + } + + /// Returns `true` if the client supports signature label offsets in signature help. + pub(crate) const fn supports_signature_label_offset(self) -> bool { + self.contains(Self::SIGNATURE_LABEL_OFFSET_SUPPORT) + } + + /// Returns `true` if the client supports per-signature active parameter in signature help. + pub(crate) const fn supports_signature_active_parameter(self) -> bool { + self.contains(Self::SIGNATURE_ACTIVE_PARAMETER_SUPPORT) + } + pub(super) fn new(client_capabilities: &ClientCapabilities) -> Self { - let code_action_settings = client_capabilities - .text_document - .as_ref() - .and_then(|doc_settings| doc_settings.code_action.as_ref()); - let code_action_data_support = code_action_settings - .and_then(|code_action_settings| code_action_settings.data_support) - .unwrap_or_default(); - let code_action_edit_resolution = code_action_settings - .and_then(|code_action_settings| code_action_settings.resolve_support.as_ref()) - .is_some_and(|resolve_support| resolve_support.properties.contains(&"edit".into())); + let mut flags = Self::empty(); - let apply_edit = client_capabilities - .workspace - .as_ref() - .and_then(|workspace| workspace.apply_edit) - .unwrap_or_default(); + let workspace = client_capabilities.workspace.as_ref(); + let text_document = client_capabilities.text_document.as_ref(); - let document_changes = client_capabilities - .workspace - .as_ref() - .and_then(|workspace| workspace.workspace_edit.as_ref()?.document_changes) - .unwrap_or_default(); - - let declaration_link_support = client_capabilities - .text_document - .as_ref() - .and_then(|document| document.type_definition?.link_support) - .unwrap_or_default(); - - let diagnostics_refresh = client_capabilities - .workspace - .as_ref() + if workspace .and_then(|workspace| workspace.diagnostics.as_ref()?.refresh_support) - .unwrap_or_default(); + .unwrap_or_default() + { + flags |= Self::WORKSPACE_DIAGNOSTIC_REFRESH; + } - let inlay_refresh = client_capabilities - .workspace - .as_ref() + if workspace .and_then(|workspace| workspace.inlay_hint.as_ref()?.refresh_support) - .unwrap_or_default(); + .unwrap_or_default() + { + flags |= Self::INLAY_HINT_REFRESH; + } - let pull_diagnostics = client_capabilities - .text_document - .as_ref() - .and_then(|text_document| text_document.diagnostic.as_ref()) - .is_some(); + if text_document.is_some_and(|text_document| text_document.diagnostic.is_some()) { + flags |= Self::PULL_DIAGNOSTICS; + } - let hover_prefer_markdown = client_capabilities - .text_document - .as_ref() + if text_document + .and_then(|text_document| text_document.type_definition?.link_support) + .unwrap_or_default() + { + flags |= Self::TYPE_DEFINITION_LINK_SUPPORT; + } + + if text_document + .and_then(|text_document| text_document.definition?.link_support) + .unwrap_or_default() + { + flags |= Self::DEFINITION_LINK_SUPPORT; + } + + if text_document + .and_then(|text_document| text_document.declaration?.link_support) + .unwrap_or_default() + { + flags |= Self::DECLARATION_LINK_SUPPORT; + } + + if text_document .and_then(|text_document| { Some( text_document @@ -92,18 +126,24 @@ impl ResolvedClientCapabilities { .contains(&MarkupKind::Markdown), ) }) - .unwrap_or_default(); + .unwrap_or_default() + { + flags |= Self::PREFER_MARKDOWN_IN_HOVER; + } - let semantic_tokens_multiline_support = client_capabilities - .text_document - .as_ref() - .and_then(|doc| doc.semantic_tokens.as_ref()) - .and_then(|semantic_tokens| semantic_tokens.multiline_token_support) - .unwrap_or(false); + if text_document + .and_then(|text_document| { + text_document + .semantic_tokens + .as_ref()? + .multiline_token_support + }) + .unwrap_or_default() + { + flags |= Self::MULTILINE_SEMANTIC_TOKENS; + } - let signature_label_offset_support = client_capabilities - .text_document - .as_ref() + if text_document .and_then(|text_document| { text_document .signature_help @@ -114,11 +154,12 @@ impl ResolvedClientCapabilities { .as_ref()? .label_offset_support }) - .unwrap_or_default(); + .unwrap_or_default() + { + flags |= Self::SIGNATURE_LABEL_OFFSET_SUPPORT; + } - let signature_active_parameter_support = client_capabilities - .text_document - .as_ref() + if text_document .and_then(|text_document| { text_document .signature_help @@ -127,21 +168,11 @@ impl ResolvedClientCapabilities { .as_ref()? .active_parameter_support }) - .unwrap_or_default(); - - Self { - code_action_deferred_edit_resolution: code_action_data_support - && code_action_edit_resolution, - apply_edit, - document_changes, - diagnostics_refresh, - inlay_refresh, - pull_diagnostics, - type_definition_link_support: declaration_link_support, - hover_prefer_markdown, - semantic_tokens_multiline_support, - signature_label_offset_support, - signature_active_parameter_support, + .unwrap_or_default() + { + flags |= Self::SIGNATURE_ACTIVE_PARAMETER_SUPPORT; } + + flags } }