diff --git a/CHANGELOG.md b/CHANGELOG.md index 55996ca1..adf005f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,9 +16,11 @@ and this project adheres to [Semantic Versioning][semver]. ### Fixed - `pygls` no longer overrides the event loop for the current thread when given an explicit loop to use. ([#334]) +- Fixed `MethodTypeNotRegisteredError` when registering a `TEXT_DOCUMENT_DID_SAVE` feature with options. ([#338]) [#328]: https://github.com/openlawlibrary/pygls/issues/328 [#334]: https://github.com/openlawlibrary/pygls/issues/334 +[#338]: https://github.com/openlawlibrary/pygls/discussions/338 [#253]: https://github.com/openlawlibrary/pygls/pull/253 diff --git a/pygls/capabilities.py b/pygls/capabilities.py index 0ff7422f..be6db7ba 100644 --- a/pygls/capabilities.py +++ b/pygls/capabilities.py @@ -41,7 +41,7 @@ from lsprotocol.types import ( ClientCapabilities, CodeLensOptions, CompletionOptions, DocumentLinkOptions, ExecuteCommandOptions, ImplementationOptions, - SaveOptions, SemanticTokensOptions, SemanticTokensRegistrationOptions, + SemanticTokensOptions, SemanticTokensRegistrationOptions, SemanticTokensOptionsFullType1, ServerCapabilities, ServerCapabilitiesWorkspaceType, SignatureHelpOptions, TextDocumentSyncOptions, TypeDefinitionOptions, @@ -110,11 +110,7 @@ def _with_text_doc_sync(self): and TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL in self.features ) if TEXT_DOCUMENT_DID_SAVE in self.features: - if TEXT_DOCUMENT_DID_SAVE in self.feature_options: - include_text = self.feature_options[TEXT_DOCUMENT_DID_SAVE].include_text - save = SaveOptions(include_text=include_text) - else: - save = True + save = self.feature_options.get(TEXT_DOCUMENT_DID_SAVE, True) else: save = False diff --git a/pygls/lsp/__init__.py b/pygls/lsp/__init__.py index b012a01a..00acf04d 100644 --- a/pygls/lsp/__init__.py +++ b/pygls/lsp/__init__.py @@ -19,6 +19,7 @@ from lsprotocol.types import ( ALL_TYPES_MAP, METHOD_TO_TYPES, + TEXT_DOCUMENT_DID_SAVE, TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL, TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA, TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE, @@ -29,6 +30,7 @@ WORKSPACE_WILL_DELETE_FILES, WORKSPACE_WILL_RENAME_FILES, FileOperationRegistrationOptions, + SaveOptions, SemanticTokensLegend, SemanticTokensRegistrationOptions, ShowDocumentResult @@ -41,6 +43,7 @@ ShowDocumentCallbackType = Callable[[ShowDocumentResult], None] METHOD_TO_OPTIONS = { + TEXT_DOCUMENT_DID_SAVE: SaveOptions, TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL: Union[SemanticTokensLegend, SemanticTokensRegistrationOptions], TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA: Union[SemanticTokensLegend, SemanticTokensRegistrationOptions], TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE: Union[SemanticTokensLegend, SemanticTokensRegistrationOptions], diff --git a/tests/test_capabilities.py b/tests/test_capabilities.py deleted file mode 100644 index 1b84aa85..00000000 --- a/tests/test_capabilities.py +++ /dev/null @@ -1,433 +0,0 @@ -from typing import Set - -import pytest -from lsprotocol.types import ( - TEXT_DOCUMENT_DID_OPEN, - TEXT_DOCUMENT_DID_CLOSE, - TEXT_DOCUMENT_DID_SAVE, - TEXT_DOCUMENT_WILL_SAVE, - TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL, - WORKSPACE_DID_CREATE_FILES, - WORKSPACE_WILL_CREATE_FILES, - WORKSPACE_DID_DELETE_FILES, - WORKSPACE_WILL_DELETE_FILES, - WORKSPACE_DID_RENAME_FILES, - WORKSPACE_WILL_RENAME_FILES, - ClientCapabilities, - FileOperationClientCapabilities, - FileOperationFilter, - FileOperationOptions, - FileOperationPattern, - FileOperationRegistrationOptions, - SaveOptions, - TextDocumentClientCapabilities, - TextDocumentSaveRegistrationOptions, - TextDocumentSyncClientCapabilities, - TextDocumentSyncKind, - TextDocumentSyncOptions, - WorkspaceClientCapabilities, -) - -from pygls.capabilities import ServerCapabilitiesBuilder - - -@pytest.mark.parametrize("capabilities,features,options,expected", [ - # textDocument/didOpen - ( - ClientCapabilities(), - set(), - {}, - TextDocumentSyncOptions( - open_close=False, - change=TextDocumentSyncKind.Incremental, - save=False, - ) - ), - ( - ClientCapabilities(), - {TEXT_DOCUMENT_DID_OPEN}, - {}, - TextDocumentSyncOptions( - open_close=True, - change=TextDocumentSyncKind.Incremental, - save=False, - ) - ), - ( - ClientCapabilities(), - {TEXT_DOCUMENT_DID_CLOSE}, - {}, - TextDocumentSyncOptions( - open_close=True, - change=TextDocumentSyncKind.Incremental, - save=False, - ) - ), - # textDocument/willSave & textDocument/willSaveWaitUntil - ( - ClientCapabilities(), - {TEXT_DOCUMENT_WILL_SAVE}, - {}, - TextDocumentSyncOptions( - open_close=False, - change=TextDocumentSyncKind.Incremental, - save=False, - ) - ), - ( - ClientCapabilities( - text_document=TextDocumentClientCapabilities( - synchronization=TextDocumentSyncClientCapabilities( - will_save=True - ) - ) - ), - {TEXT_DOCUMENT_WILL_SAVE}, - {}, - TextDocumentSyncOptions( - open_close=False, - change=TextDocumentSyncKind.Incremental, - save=False, - will_save=True - ) - ), - ( - ClientCapabilities( - text_document=TextDocumentClientCapabilities( - synchronization=TextDocumentSyncClientCapabilities( - will_save_wait_until=True - ) - ) - ), - {TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL}, - {}, - TextDocumentSyncOptions( - open_close=False, - change=TextDocumentSyncKind.Incremental, - save=False, - will_save_wait_until=True - ) - ), - # textDocument/didSave - ( - ClientCapabilities(), - {TEXT_DOCUMENT_DID_SAVE}, - {}, - TextDocumentSyncOptions( - open_close=False, - change=TextDocumentSyncKind.Incremental, - save=True, - ) - ), - ( - ClientCapabilities(), - {TEXT_DOCUMENT_DID_SAVE}, - {TEXT_DOCUMENT_DID_SAVE: TextDocumentSaveRegistrationOptions(include_text=True)}, - TextDocumentSyncOptions( - open_close=False, - change=TextDocumentSyncKind.Incremental, - save=SaveOptions(include_text=True), - ) - ) -]) -def test_text_doc_sync_capabilities( - capabilities: ClientCapabilities, - features: Set[str], - options, - expected: TextDocumentSyncOptions -): - """Ensure that `pygls` can correctly construct server capabilities for the - text document sync features.""" - - builder = ServerCapabilitiesBuilder( - capabilities, features, options, [], TextDocumentSyncKind.Incremental - ) - - actual = builder.build().text_document_sync - assert expected == actual - - -@pytest.mark.parametrize("capabilities,features,options,expected", [ - ( - ClientCapabilities(), - set(), - {}, - FileOperationOptions() - ), - # workspace/willCreateFiles - ( - ClientCapabilities(), - {WORKSPACE_WILL_CREATE_FILES}, - { - WORKSPACE_WILL_CREATE_FILES: FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - }, - FileOperationOptions() - ), - ( - ClientCapabilities( - workspace=WorkspaceClientCapabilities( - file_operations=FileOperationClientCapabilities( - will_create=True - ) - ) - ), - {WORKSPACE_WILL_CREATE_FILES}, - { - WORKSPACE_WILL_CREATE_FILES: FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - }, - FileOperationOptions( - will_create=FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - ) - ), - # workspace/didCreateFiles - ( - ClientCapabilities(), - {WORKSPACE_DID_CREATE_FILES}, - { - WORKSPACE_DID_CREATE_FILES: FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - }, - FileOperationOptions() - ), - ( - ClientCapabilities( - workspace=WorkspaceClientCapabilities( - file_operations=FileOperationClientCapabilities( - did_create=True - ) - ) - ), - {WORKSPACE_DID_CREATE_FILES}, - { - WORKSPACE_DID_CREATE_FILES: FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - }, - FileOperationOptions( - did_create=FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - ) - ), - # workspace/willDeleteFiles - ( - ClientCapabilities(), - {WORKSPACE_WILL_DELETE_FILES}, - { - WORKSPACE_WILL_DELETE_FILES: FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - }, - FileOperationOptions() - ), - ( - ClientCapabilities( - workspace=WorkspaceClientCapabilities( - file_operations=FileOperationClientCapabilities( - will_delete=True - ) - ) - ), - {WORKSPACE_WILL_DELETE_FILES}, - { - WORKSPACE_WILL_DELETE_FILES: FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - }, - FileOperationOptions( - will_delete=FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - ) - ), - # workspace/didDeleteFiles - ( - ClientCapabilities(), - {WORKSPACE_DID_DELETE_FILES}, - { - WORKSPACE_DID_DELETE_FILES: FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - }, - FileOperationOptions() - ), - ( - ClientCapabilities( - workspace=WorkspaceClientCapabilities( - file_operations=FileOperationClientCapabilities( - did_delete=True - ) - ) - ), - {WORKSPACE_DID_DELETE_FILES}, - { - WORKSPACE_DID_DELETE_FILES: FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - }, - FileOperationOptions( - did_delete=FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - ) - ), - # workspace/willRenameFiles - ( - ClientCapabilities(), - {WORKSPACE_WILL_RENAME_FILES}, - { - WORKSPACE_WILL_RENAME_FILES: FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - }, - FileOperationOptions() - ), - ( - ClientCapabilities( - workspace=WorkspaceClientCapabilities( - file_operations=FileOperationClientCapabilities( - will_rename=True - ) - ) - ), - {WORKSPACE_WILL_RENAME_FILES}, - { - WORKSPACE_WILL_RENAME_FILES: FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - }, - FileOperationOptions( - will_rename=FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - ) - ), - # workspace/didRenameFiles - ( - ClientCapabilities(), - {WORKSPACE_DID_RENAME_FILES}, - { - WORKSPACE_DID_RENAME_FILES: FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - }, - FileOperationOptions() - ), - ( - ClientCapabilities( - workspace=WorkspaceClientCapabilities( - file_operations=FileOperationClientCapabilities( - did_rename=True - ) - ) - ), - {WORKSPACE_DID_RENAME_FILES}, - { - WORKSPACE_DID_RENAME_FILES: FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - }, - FileOperationOptions( - did_rename=FileOperationRegistrationOptions( - filters=[ - FileOperationFilter( - pattern=FileOperationPattern(glob="**/*.py") - ) - ] - ) - ) - ), -]) -def test_file_operations_capabilities( - capabilities: ClientCapabilities, - features: Set[str], - options, - expected: FileOperationOptions -): - """Ensure that `pygls` can correctly construct server capabilities for the file - operations set of features.""" - - builder = ServerCapabilitiesBuilder( - capabilities, features, options, [], TextDocumentSyncKind.Incremental - ) - - server = builder.build() - assert server.workspace is not None - - actual = server.workspace.file_operations - assert expected == actual diff --git a/tests/test_feature_manager.py b/tests/test_feature_manager.py index 81941af7..03aa5362 100644 --- a/tests/test_feature_manager.py +++ b/tests/test_feature_manager.py @@ -15,15 +15,21 @@ # limitations under the License. # ############################################################################ import asyncio +from typing import Any import pytest +from pygls.capabilities import ServerCapabilitiesBuilder from pygls.exceptions import ( CommandAlreadyRegisteredError, FeatureAlreadyRegisteredError, ValidationError, ) -from pygls.feature_manager import has_ls_param_or_annotation, wrap_with_server -from lsprotocol import types +from pygls.feature_manager import ( + FeatureManager, + has_ls_param_or_annotation, + wrap_with_server, +) +from lsprotocol import types as lsp from typeguard import TypeCheckError from typeguard._utils import qualified_name @@ -73,20 +79,20 @@ def cmd2(): def test_register_feature_with_valid_options(feature_manager): - options = types.CompletionOptions(trigger_characters=["!"]) + options = lsp.CompletionOptions(trigger_characters=["!"]) - @feature_manager.feature(types.TEXT_DOCUMENT_COMPLETION, options) + @feature_manager.feature(lsp.TEXT_DOCUMENT_COMPLETION, options) def completions(): pass reg_features = feature_manager.features.keys() reg_feature_options = feature_manager.feature_options.keys() - assert types.TEXT_DOCUMENT_COMPLETION in reg_features - assert types.TEXT_DOCUMENT_COMPLETION in reg_feature_options + assert lsp.TEXT_DOCUMENT_COMPLETION in reg_features + assert lsp.TEXT_DOCUMENT_COMPLETION in reg_feature_options - assert feature_manager.features[types.TEXT_DOCUMENT_COMPLETION] is completions - assert feature_manager.feature_options[types.TEXT_DOCUMENT_COMPLETION] is options + assert feature_manager.features[lsp.TEXT_DOCUMENT_COMPLETION] is completions + assert feature_manager.feature_options[lsp.TEXT_DOCUMENT_COMPLETION] is options def test_register_feature_with_wrong_options(feature_manager): @@ -101,27 +107,27 @@ class Options: ), # noqa ): - @feature_manager.feature(types.TEXT_DOCUMENT_COMPLETION, Options()) + @feature_manager.feature(lsp.TEXT_DOCUMENT_COMPLETION, Options()) def completions(): pass def test_register_features(feature_manager): - @feature_manager.feature(types.TEXT_DOCUMENT_COMPLETION) + @feature_manager.feature(lsp.TEXT_DOCUMENT_COMPLETION) def completions(): pass - @feature_manager.feature(types.TEXT_DOCUMENT_CODE_LENS) + @feature_manager.feature(lsp.TEXT_DOCUMENT_CODE_LENS) def code_lens(): pass reg_features = feature_manager.features.keys() - assert types.TEXT_DOCUMENT_COMPLETION in reg_features - assert types.TEXT_DOCUMENT_CODE_LENS in reg_features + assert lsp.TEXT_DOCUMENT_COMPLETION in reg_features + assert lsp.TEXT_DOCUMENT_CODE_LENS in reg_features - assert feature_manager.features[types.TEXT_DOCUMENT_COMPLETION] is completions - assert feature_manager.features[types.TEXT_DOCUMENT_CODE_LENS] is code_lens + assert feature_manager.features[lsp.TEXT_DOCUMENT_COMPLETION] is completions + assert feature_manager.features[lsp.TEXT_DOCUMENT_CODE_LENS] is code_lens def test_register_same_command_twice_error(feature_manager): @@ -141,11 +147,11 @@ def test_register_same_feature_twice_error(feature_manager): with pytest.raises(FeatureAlreadyRegisteredError): - @feature_manager.feature(types.TEXT_DOCUMENT_CODE_ACTION) + @feature_manager.feature(lsp.TEXT_DOCUMENT_CODE_ACTION) def code_action1(): # pylint: disable=unused-variable pass - @feature_manager.feature(types.TEXT_DOCUMENT_CODE_ACTION) + @feature_manager.feature(lsp.TEXT_DOCUMENT_CODE_ACTION) def code_action2(): # pylint: disable=unused-variable pass @@ -183,3 +189,429 @@ def f(ls): wrapped = wrap_with_server(f, Server()) assert wrapped.execute_in_thread is True + + + +def server_capabilities(**kwargs): + """Helper to reduce the amount of boilerplate required to specify the expected + server capabilities by filling in some fields - unless they are explicitly + overriden.""" + + if "text_document_sync" not in kwargs: + kwargs["text_document_sync"] = lsp.TextDocumentSyncOptions( + open_close=False, save=False, + ) + + if "execute_command_provider" not in kwargs: + kwargs["execute_command_provider" ] = lsp.ExecuteCommandOptions(commands=[]) + + if "workspace" not in kwargs: + kwargs["workspace"] = lsp.ServerCapabilitiesWorkspaceType( + workspace_folders=lsp.WorkspaceFoldersServerCapabilities( + supported=True, change_notifications=True + ), + file_operations=lsp.FileOperationOptions() + ) + + return lsp.ServerCapabilities(**kwargs) + + +@pytest.mark.parametrize( + "method, options, capabilities, expected", + [ + ( + lsp.TEXT_DOCUMENT_DID_SAVE, + lsp.SaveOptions(include_text=True), + lsp.ClientCapabilities(), + server_capabilities( + text_document_sync=lsp.TextDocumentSyncOptions( + open_close=False, + save=lsp.SaveOptions(include_text=True) + ) + ) + ), + ( + lsp.TEXT_DOCUMENT_DID_SAVE, + None, + lsp.ClientCapabilities(), + server_capabilities( + text_document_sync=lsp.TextDocumentSyncOptions( + open_close=False, save=True + ) + ) + ), + ( + lsp.TEXT_DOCUMENT_WILL_SAVE, + None, + lsp.ClientCapabilities(), + server_capabilities( + text_document_sync=lsp.TextDocumentSyncOptions( + open_close=False, save=False + ) + ) + ), + ( + lsp.TEXT_DOCUMENT_WILL_SAVE, + None, + lsp.ClientCapabilities( + text_document=lsp.TextDocumentClientCapabilities( + synchronization=lsp.TextDocumentSyncClientCapabilities( + will_save=True + ) + ) + ), + server_capabilities( + text_document_sync=lsp.TextDocumentSyncOptions( + open_close=False, save=False, will_save=True + ) + ) + ), + ( + lsp.TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL, + None, + lsp.ClientCapabilities( + text_document=lsp.TextDocumentClientCapabilities( + synchronization=lsp.TextDocumentSyncClientCapabilities( + will_save_wait_until=True + ) + ) + ), + server_capabilities( + text_document_sync=lsp.TextDocumentSyncOptions( + open_close=False, save=False, will_save_wait_until=True + ) + ) + ), + ( + lsp.TEXT_DOCUMENT_DID_OPEN, + None, + lsp.ClientCapabilities(), + server_capabilities( + text_document_sync=lsp.TextDocumentSyncOptions( + open_close=True, save=False + ) + ) + ), + ( + lsp.TEXT_DOCUMENT_DID_CLOSE, + None, + lsp.ClientCapabilities(), + server_capabilities( + text_document_sync=lsp.TextDocumentSyncOptions( + open_close=True, save=False + ) + ), + ), + ( + lsp.WORKSPACE_WILL_CREATE_FILES, + lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ), + lsp.ClientCapabilities(), + server_capabilities() + ), + ( + lsp.WORKSPACE_WILL_CREATE_FILES, + lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ), + lsp.ClientCapabilities( + workspace=lsp.WorkspaceClientCapabilities( + file_operations=lsp.FileOperationClientCapabilities( + will_create=True + ) + ) + ), + server_capabilities( + workspace=lsp.ServerCapabilitiesWorkspaceType( + workspace_folders=lsp.WorkspaceFoldersServerCapabilities( + supported=True, change_notifications=True + ), + file_operations=lsp.FileOperationOptions( + will_create=lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ) + ), + ) + ) + ), + ( + lsp.WORKSPACE_DID_CREATE_FILES, + lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ), + lsp.ClientCapabilities(), + server_capabilities() + ), + ( + lsp.WORKSPACE_DID_CREATE_FILES, + lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ), + lsp.ClientCapabilities( + workspace=lsp.WorkspaceClientCapabilities( + file_operations=lsp.FileOperationClientCapabilities( + did_create=True + ) + ) + ), + server_capabilities( + workspace=lsp.ServerCapabilitiesWorkspaceType( + workspace_folders=lsp.WorkspaceFoldersServerCapabilities( + supported=True, change_notifications=True + ), + file_operations=lsp.FileOperationOptions( + did_create=lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ) + ), + ) + ) + ), + ( + lsp.WORKSPACE_WILL_DELETE_FILES, + lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ), + lsp.ClientCapabilities(), + server_capabilities() + ), + ( + lsp.WORKSPACE_WILL_DELETE_FILES, + lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ), + lsp.ClientCapabilities( + workspace=lsp.WorkspaceClientCapabilities( + file_operations=lsp.FileOperationClientCapabilities( + will_delete=True + ) + ) + ), + server_capabilities( + workspace=lsp.ServerCapabilitiesWorkspaceType( + workspace_folders=lsp.WorkspaceFoldersServerCapabilities( + supported=True, change_notifications=True + ), + file_operations=lsp.FileOperationOptions( + will_delete=lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ) + ), + ) + ) + ), + ( + lsp.WORKSPACE_DID_DELETE_FILES, + lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ), + lsp.ClientCapabilities(), + server_capabilities() + ), + ( + lsp.WORKSPACE_DID_DELETE_FILES, + lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ), + lsp.ClientCapabilities( + workspace=lsp.WorkspaceClientCapabilities( + file_operations=lsp.FileOperationClientCapabilities( + did_delete=True + ) + ) + ), + server_capabilities( + workspace=lsp.ServerCapabilitiesWorkspaceType( + workspace_folders=lsp.WorkspaceFoldersServerCapabilities( + supported=True, change_notifications=True + ), + file_operations=lsp.FileOperationOptions( + did_delete=lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ) + ), + ) + ) + ), + ( + lsp.WORKSPACE_WILL_RENAME_FILES, + lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ), + lsp.ClientCapabilities(), + server_capabilities() + ), + ( + lsp.WORKSPACE_WILL_RENAME_FILES, + lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ), + lsp.ClientCapabilities( + workspace=lsp.WorkspaceClientCapabilities( + file_operations=lsp.FileOperationClientCapabilities( + will_rename=True + ) + ) + ), + server_capabilities( + workspace=lsp.ServerCapabilitiesWorkspaceType( + workspace_folders=lsp.WorkspaceFoldersServerCapabilities( + supported=True, change_notifications=True + ), + file_operations=lsp.FileOperationOptions( + will_rename=lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ) + ), + ) + ) + ), + ( + lsp.WORKSPACE_DID_RENAME_FILES, + lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ), + lsp.ClientCapabilities(), + server_capabilities() + ), + ( + lsp.WORKSPACE_DID_RENAME_FILES, + lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ), + lsp.ClientCapabilities( + workspace=lsp.WorkspaceClientCapabilities( + file_operations=lsp.FileOperationClientCapabilities( + did_rename=True + ) + ) + ), + server_capabilities( + workspace=lsp.ServerCapabilitiesWorkspaceType( + workspace_folders=lsp.WorkspaceFoldersServerCapabilities( + supported=True, change_notifications=True + ), + file_operations=lsp.FileOperationOptions( + did_rename=lsp.FileOperationRegistrationOptions( + filters=[ + lsp.FileOperationFilter( + pattern=lsp.FileOperationPattern(glob="**/*.py") + ) + ] + ) + ), + ) + ) + ) + ] +) +def test_register_feature( + feature_manager: FeatureManager, + method: str, + options: Any, + capabilities: lsp.ClientCapabilities, + expected: lsp.ServerCapabilities, +): + """Ensure that we can register features while specifying their associated + options and that `pygls` is able to correctly build the corresponding server + capabilities. + + Parameters + ---------- + feature_manager + The feature manager to use + + method + The method to register the feature handler for. + + options + The method options to use + + capabilities + The client capabilities to use when building the server's capabilities. + + expected + The expected server capabilties we are expecting to see. + """ + @feature_manager.feature(method, options) + def _(): + pass + + actual = ServerCapabilitiesBuilder( + capabilities, + feature_manager.features.keys(), + feature_manager.feature_options, + [], + None, + ).build() + + assert expected == actual