diff --git a/pygls/capabilities.py b/pygls/capabilities.py index 852b5cf2..e552fc52 100644 --- a/pygls/capabilities.py +++ b/pygls/capabilities.py @@ -18,81 +18,14 @@ from typing import Any, Dict, List, Optional, Set, Union import logging -from lsprotocol.types import ( - INLAY_HINT_RESOLVE, - TEXT_DOCUMENT_CODE_ACTION, - TEXT_DOCUMENT_CODE_LENS, - TEXT_DOCUMENT_COMPLETION, - TEXT_DOCUMENT_DECLARATION, - TEXT_DOCUMENT_DEFINITION, - TEXT_DOCUMENT_DIAGNOSTIC, - TEXT_DOCUMENT_DOCUMENT_COLOR, - TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT, - TEXT_DOCUMENT_DOCUMENT_LINK, - TEXT_DOCUMENT_DOCUMENT_SYMBOL, - TEXT_DOCUMENT_FOLDING_RANGE, - TEXT_DOCUMENT_FORMATTING, - TEXT_DOCUMENT_HOVER, - TEXT_DOCUMENT_IMPLEMENTATION, - TEXT_DOCUMENT_INLAY_HINT, - TEXT_DOCUMENT_INLINE_VALUE, - TEXT_DOCUMENT_ON_TYPE_FORMATTING, - TEXT_DOCUMENT_RANGE_FORMATTING, - TEXT_DOCUMENT_REFERENCES, - TEXT_DOCUMENT_RENAME, - TEXT_DOCUMENT_SELECTION_RANGE, - TEXT_DOCUMENT_SIGNATURE_HELP, - TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY, - TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY, - TEXT_DOCUMENT_DID_CLOSE, - TEXT_DOCUMENT_DID_OPEN, - TEXT_DOCUMENT_DID_SAVE, - TEXT_DOCUMENT_LINKED_EDITING_RANGE, - TEXT_DOCUMENT_MONIKER, - TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL, - TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA, - TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE, - TEXT_DOCUMENT_WILL_SAVE, - TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL, - TEXT_DOCUMENT_TYPE_DEFINITION, - WORKSPACE_DIAGNOSTIC, - WORKSPACE_DID_CREATE_FILES, - WORKSPACE_DID_DELETE_FILES, - WORKSPACE_DID_RENAME_FILES, - WORKSPACE_SYMBOL, - WORKSPACE_SYMBOL_RESOLVE, - WORKSPACE_WILL_CREATE_FILES, - WORKSPACE_WILL_DELETE_FILES, - WORKSPACE_WILL_RENAME_FILES, - InlayHintOptions, - PositionEncodingKind, -) -from lsprotocol.types import ( - ClientCapabilities, - CodeLensOptions, - CompletionOptions, - DocumentLinkOptions, - ExecuteCommandOptions, - ImplementationOptions, - NotebookDocumentSyncOptions, - SemanticTokensOptions, - SemanticTokensRegistrationOptions, - SemanticTokensOptionsFullType1, - ServerCapabilities, - ServerCapabilitiesWorkspaceType, - SignatureHelpOptions, - TextDocumentSyncKind, - TextDocumentSyncOptions, - TypeDefinitionOptions, - FileOperationOptions, - WorkspaceFoldersServerCapabilities, -) +from lsprotocol import types + logger = logging.getLogger(__name__) def get_capability( - client_capabilities: ClientCapabilities, field: str, default: Any = None + client_capabilities: types.ClientCapabilities, field: str, default: Any = None ) -> Any: """Check if ClientCapabilities has some nested value without raising AttributeError. @@ -114,12 +47,12 @@ class ServerCapabilitiesBuilder: def __init__( self, - client_capabilities: ClientCapabilities, + client_capabilities: types.ClientCapabilities, features: Set[str], feature_options: Dict[str, Any], commands: List[str], - text_document_sync_kind: TextDocumentSyncKind, - notebook_document_sync: Optional[NotebookDocumentSyncOptions] = None, + text_document_sync_kind: types.TextDocumentSyncKind, + notebook_document_sync: Optional[types.NotebookDocumentSyncOptions] = None, ): self.client_capabilities = client_capabilities self.features = features @@ -128,7 +61,7 @@ def __init__( self.text_document_sync_kind = text_document_sync_kind self.notebook_document_sync = notebook_document_sync - self.server_cap = ServerCapabilities() + self.server_cap = types.ServerCapabilities() def _provider_options(self, feature, default=True): if feature in self.features: @@ -137,28 +70,28 @@ def _provider_options(self, feature, default=True): def _with_text_document_sync(self): open_close = ( - TEXT_DOCUMENT_DID_OPEN in self.features - or TEXT_DOCUMENT_DID_CLOSE in self.features + types.TEXT_DOCUMENT_DID_OPEN in self.features + or types.TEXT_DOCUMENT_DID_CLOSE in self.features ) will_save = ( get_capability( self.client_capabilities, "text_document.synchronization.will_save" ) - and TEXT_DOCUMENT_WILL_SAVE in self.features + and types.TEXT_DOCUMENT_WILL_SAVE in self.features ) will_save_wait_until = ( get_capability( self.client_capabilities, "text_document.synchronization.will_save_wait_until", ) - and TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL in self.features + and types.TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL in self.features ) - if TEXT_DOCUMENT_DID_SAVE in self.features: - save = self.feature_options.get(TEXT_DOCUMENT_DID_SAVE, True) + if types.TEXT_DOCUMENT_DID_SAVE in self.features: + save = self.feature_options.get(types.TEXT_DOCUMENT_DID_SAVE, True) else: save = False - self.server_cap.text_document_sync = TextDocumentSyncOptions( + self.server_cap.text_document_sync = types.TextDocumentSyncOptions( open_close=open_close, change=self.text_document_sync_kind, will_save=will_save, @@ -177,41 +110,41 @@ def _with_notebook_document_sync(self): def _with_completion(self): value = self._provider_options( - TEXT_DOCUMENT_COMPLETION, default=CompletionOptions() + types.TEXT_DOCUMENT_COMPLETION, default=types.CompletionOptions() ) if value is not None: self.server_cap.completion_provider = value return self def _with_hover(self): - value = self._provider_options(TEXT_DOCUMENT_HOVER) + value = self._provider_options(types.TEXT_DOCUMENT_HOVER) if value is not None: self.server_cap.hover_provider = value return self def _with_signature_help(self): value = self._provider_options( - TEXT_DOCUMENT_SIGNATURE_HELP, default=SignatureHelpOptions() + types.TEXT_DOCUMENT_SIGNATURE_HELP, default=types.SignatureHelpOptions() ) if value is not None: self.server_cap.signature_help_provider = value return self def _with_declaration(self): - value = self._provider_options(TEXT_DOCUMENT_DECLARATION) + value = self._provider_options(types.TEXT_DOCUMENT_DECLARATION) if value is not None: self.server_cap.declaration_provider = value return self def _with_definition(self): - value = self._provider_options(TEXT_DOCUMENT_DEFINITION) + value = self._provider_options(types.TEXT_DOCUMENT_DEFINITION) if value is not None: self.server_cap.definition_provider = value return self def _with_type_definition(self): value = self._provider_options( - TEXT_DOCUMENT_TYPE_DEFINITION, default=TypeDefinitionOptions() + types.TEXT_DOCUMENT_TYPE_DEFINITION, default=types.TypeDefinitionOptions() ) if value is not None: self.server_cap.type_definition_provider = value @@ -219,48 +152,48 @@ def _with_type_definition(self): def _with_inlay_hints(self): value = self._provider_options( - TEXT_DOCUMENT_INLAY_HINT, default=InlayHintOptions() + types.TEXT_DOCUMENT_INLAY_HINT, default=types.InlayHintOptions() ) if value is not None: - value.resolve_provider = INLAY_HINT_RESOLVE in self.features + value.resolve_provider = types.INLAY_HINT_RESOLVE in self.features self.server_cap.inlay_hint_provider = value return self def _with_implementation(self): value = self._provider_options( - TEXT_DOCUMENT_IMPLEMENTATION, default=ImplementationOptions() + types.TEXT_DOCUMENT_IMPLEMENTATION, default=types.ImplementationOptions() ) if value is not None: self.server_cap.implementation_provider = value return self def _with_references(self): - value = self._provider_options(TEXT_DOCUMENT_REFERENCES) + value = self._provider_options(types.TEXT_DOCUMENT_REFERENCES) if value is not None: self.server_cap.references_provider = value return self def _with_document_highlight(self): - value = self._provider_options(TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT) + value = self._provider_options(types.TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT) if value is not None: self.server_cap.document_highlight_provider = value return self def _with_document_symbol(self): - value = self._provider_options(TEXT_DOCUMENT_DOCUMENT_SYMBOL) + value = self._provider_options(types.TEXT_DOCUMENT_DOCUMENT_SYMBOL) if value is not None: self.server_cap.document_symbol_provider = value return self def _with_code_action(self): - value = self._provider_options(TEXT_DOCUMENT_CODE_ACTION) + value = self._provider_options(types.TEXT_DOCUMENT_CODE_ACTION) if value is not None: self.server_cap.code_action_provider = value return self def _with_code_lens(self): value = self._provider_options( - TEXT_DOCUMENT_CODE_LENS, default=CodeLensOptions() + types.TEXT_DOCUMENT_CODE_LENS, default=types.CodeLensOptions() ) if value is not None: self.server_cap.code_lens_provider = value @@ -268,77 +201,77 @@ def _with_code_lens(self): def _with_document_link(self): value = self._provider_options( - TEXT_DOCUMENT_DOCUMENT_LINK, default=DocumentLinkOptions() + types.TEXT_DOCUMENT_DOCUMENT_LINK, default=types.DocumentLinkOptions() ) if value is not None: self.server_cap.document_link_provider = value return self def _with_color(self): - value = self._provider_options(TEXT_DOCUMENT_DOCUMENT_COLOR) + value = self._provider_options(types.TEXT_DOCUMENT_DOCUMENT_COLOR) if value is not None: self.server_cap.color_provider = value return self def _with_document_formatting(self): - value = self._provider_options(TEXT_DOCUMENT_FORMATTING) + value = self._provider_options(types.TEXT_DOCUMENT_FORMATTING) if value is not None: self.server_cap.document_formatting_provider = value return self def _with_document_range_formatting(self): - value = self._provider_options(TEXT_DOCUMENT_RANGE_FORMATTING) + value = self._provider_options(types.TEXT_DOCUMENT_RANGE_FORMATTING) if value is not None: self.server_cap.document_range_formatting_provider = value return self def _with_document_on_type_formatting(self): - value = self._provider_options(TEXT_DOCUMENT_ON_TYPE_FORMATTING) + value = self._provider_options(types.TEXT_DOCUMENT_ON_TYPE_FORMATTING) if value is not None: self.server_cap.document_on_type_formatting_provider = value return self def _with_rename(self): - value = self._provider_options(TEXT_DOCUMENT_RENAME) + value = self._provider_options(types.TEXT_DOCUMENT_RENAME) if value is not None: self.server_cap.rename_provider = value return self def _with_folding_range(self): - value = self._provider_options(TEXT_DOCUMENT_FOLDING_RANGE) + value = self._provider_options(types.TEXT_DOCUMENT_FOLDING_RANGE) if value is not None: self.server_cap.folding_range_provider = value return self def _with_execute_command(self): - self.server_cap.execute_command_provider = ExecuteCommandOptions( + self.server_cap.execute_command_provider = types.ExecuteCommandOptions( commands=self.commands ) return self def _with_selection_range(self): - value = self._provider_options(TEXT_DOCUMENT_SELECTION_RANGE) + value = self._provider_options(types.TEXT_DOCUMENT_SELECTION_RANGE) if value is not None: self.server_cap.selection_range_provider = value return self def _with_call_hierarchy(self): - value = self._provider_options(TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY) + value = self._provider_options(types.TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY) if value is not None: self.server_cap.call_hierarchy_provider = value return self def _with_type_hierarchy(self): - value = self._provider_options(TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY) + value = self._provider_options(types.TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY) if value is not None: self.server_cap.type_hierarchy_provider = value return self def _with_semantic_tokens(self): providers = [ - TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL, - TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA, - TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE, + types.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL, + types.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA, + types.TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE, ] for provider in providers: @@ -349,21 +282,21 @@ def _with_semantic_tokens(self): if value is None: return self - if isinstance(value, SemanticTokensRegistrationOptions): + if isinstance(value, types.SemanticTokensRegistrationOptions): self.server_cap.semantic_tokens_provider = value return self - full_support: Union[bool, SemanticTokensOptionsFullType1] = ( - TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL in self.features + full_support: Union[bool, types.SemanticTokensOptionsFullType1] = ( + types.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL in self.features ) - if TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA in self.features: - full_support = SemanticTokensOptionsFullType1(delta=True) + if types.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA in self.features: + full_support = types.SemanticTokensOptionsFullType1(delta=True) - options = SemanticTokensOptions( + options = types.SemanticTokensOptions( legend=value, full=full_support or None, - range=TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE in self.features or None, + range=types.TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE in self.features or None, ) if options.full or options.range: @@ -372,34 +305,36 @@ def _with_semantic_tokens(self): return self def _with_linked_editing_range(self): - value = self._provider_options(TEXT_DOCUMENT_LINKED_EDITING_RANGE) + value = self._provider_options(types.TEXT_DOCUMENT_LINKED_EDITING_RANGE) if value is not None: self.server_cap.linked_editing_range_provider = value return self def _with_moniker(self): - value = self._provider_options(TEXT_DOCUMENT_MONIKER) + value = self._provider_options(types.TEXT_DOCUMENT_MONIKER) if value is not None: self.server_cap.moniker_provider = value return self def _with_workspace_symbol(self): - value = self._provider_options(WORKSPACE_SYMBOL) + value = self._provider_options( + types.WORKSPACE_SYMBOL, default=types.WorkspaceSymbolOptions() + ) if value is not None: - value.resolve_provider = self._provider_options(WORKSPACE_SYMBOL_RESOLVE) + value.resolve_provider = types.WORKSPACE_SYMBOL_RESOLVE in self.features self.server_cap.workspace_symbol_provider = value return self def _with_workspace_capabilities(self): # File operations - file_operations = FileOperationOptions() + file_operations = types.FileOperationOptions() operations = [ - (WORKSPACE_WILL_CREATE_FILES, "will_create"), - (WORKSPACE_DID_CREATE_FILES, "did_create"), - (WORKSPACE_WILL_DELETE_FILES, "will_delete"), - (WORKSPACE_DID_DELETE_FILES, "did_delete"), - (WORKSPACE_WILL_RENAME_FILES, "will_rename"), - (WORKSPACE_DID_RENAME_FILES, "did_rename"), + (types.WORKSPACE_WILL_CREATE_FILES, "will_create"), + (types.WORKSPACE_DID_CREATE_FILES, "did_create"), + (types.WORKSPACE_WILL_DELETE_FILES, "will_delete"), + (types.WORKSPACE_DID_DELETE_FILES, "did_delete"), + (types.WORKSPACE_WILL_RENAME_FILES, "will_rename"), + (types.WORKSPACE_DID_RENAME_FILES, "did_rename"), ] for method_name, capability_name in operations: @@ -411,8 +346,8 @@ def _with_workspace_capabilities(self): value = self._provider_options(method_name, None) setattr(file_operations, capability_name, value) - self.server_cap.workspace = ServerCapabilitiesWorkspaceType( - workspace_folders=WorkspaceFoldersServerCapabilities( + self.server_cap.workspace = types.ServerCapabilitiesWorkspaceType( + workspace_folders=types.WorkspaceFoldersServerCapabilities( supported=True, change_notifications=True, ), @@ -421,20 +356,20 @@ def _with_workspace_capabilities(self): return self def _with_diagnostic_provider(self): - value = self._provider_options(TEXT_DOCUMENT_DIAGNOSTIC) + value = self._provider_options(types.TEXT_DOCUMENT_DIAGNOSTIC) if value is not None: - value.workspace_diagnostics = self._provider_options(WORKSPACE_DIAGNOSTIC) + value.workspace_diagnostics = self._provider_options(types.WORKSPACE_DIAGNOSTIC) self.server_cap.diagnostic_provider = value return self def _with_inline_value_provider(self): - value = self._provider_options(TEXT_DOCUMENT_INLINE_VALUE) + value = self._provider_options(types.TEXT_DOCUMENT_INLINE_VALUE) if value is not None: self.server_cap.inline_value_provider = value return self def _with_position_encodings(self): - self.server_cap.position_encoding = PositionEncodingKind.Utf16 + self.server_cap.position_encoding = types.PositionEncodingKind.Utf16 general = self.client_capabilities.general if general is None: @@ -444,15 +379,15 @@ def _with_position_encodings(self): if encodings is None: return self - if PositionEncodingKind.Utf16 in encodings: + if types.PositionEncodingKind.Utf16 in encodings: return self - if PositionEncodingKind.Utf32 in encodings: - self.server_cap.position_encoding = PositionEncodingKind.Utf32 + if types.PositionEncodingKind.Utf32 in encodings: + self.server_cap.position_encoding = types.PositionEncodingKind.Utf32 return self - if PositionEncodingKind.Utf8 in encodings: - self.server_cap.position_encoding = PositionEncodingKind.Utf8 + if types.PositionEncodingKind.Utf8 in encodings: + self.server_cap.position_encoding = types.PositionEncodingKind.Utf8 return self logger.warning(f"Unknown `PositionEncoding`s: {encodings}") diff --git a/tests/test_feature_manager.py b/tests/test_feature_manager.py index 4e5c852b..2fbb3872 100644 --- a/tests/test_feature_manager.py +++ b/tests/test_feature_manager.py @@ -601,6 +601,16 @@ def server_capabilities(**kwargs): ) ), ), + ( + lsp.WORKSPACE_SYMBOL, + None, + lsp.ClientCapabilities(), + server_capabilities( + workspace_symbol_provider=lsp.WorkspaceSymbolOptions( + resolve_provider=False, + ), + ), + ) ], ) def test_register_feature( @@ -673,3 +683,32 @@ def _(): ).build() assert expected == actual + + +def test_register_workspace_symbol_resolve( + feature_manager: FeatureManager, +): + + @feature_manager.feature(lsp.WORKSPACE_SYMBOL) + def _(): + pass + + + @feature_manager.feature(lsp.WORKSPACE_SYMBOL_RESOLVE) + def _(): + pass + + expected = server_capabilities( + workspace_symbol_provider=lsp.WorkspaceSymbolOptions(resolve_provider=True), + ) + + actual = ServerCapabilitiesBuilder( + lsp.ClientCapabilities(), + feature_manager.features.keys(), + feature_manager.feature_options, + [], + None, + None, + ).build() + + assert expected == actual