diff --git a/lsp_client/__init__.py b/lsp_client/__init__.py index f69be8d..fb203bf 100644 --- a/lsp_client/__init__.py +++ b/lsp_client/__init__.py @@ -19,6 +19,7 @@ HoverRequest, InitializeParams, InitializeRequest, + InitializeResult, InitializedNotification, LanguageKind, Location, @@ -34,6 +35,7 @@ ResponseError, ResponseMessage, ServerCapabilities, + ServerInfo, ShutdownRequest, TextDocumentDidChangeNotification, TextDocumentDidCloseNotification, @@ -79,6 +81,7 @@ "HoverRequest", "InitializeParams", "InitializeRequest", + "InitializeResult", "InitializedNotification", "LSPClient", "LSPErrorCodes", @@ -93,6 +96,7 @@ "ResponseError", "ResponseMessage", "ServerCapabilities", + "ServerInfo", "ShutdownRequest", "TextDocumentDidChangeNotification", "TextDocumentDidCloseNotification", diff --git a/lsp_client/protocol.py b/lsp_client/protocol.py index 5d392cb..34d069b 100644 --- a/lsp_client/protocol.py +++ b/lsp_client/protocol.py @@ -300,6 +300,24 @@ def __init__(self, **kwargs: Any) -> None: super(InitializeRequest, self).__init__(**kwargs) +class ServerInfo(BaseModel): + """Information about the server. @since 3.15.0""" + + #: The name of the server as defined by the server. + name: str + #: The server's version as defined by the server. + version: str | None = None + + +class InitializeResult(BaseModel): + """The result returned from an ``initialize`` request.""" + + #: The capabilities the language server provides. + capabilities: ServerCapabilities + #: Information about the server. @since 3.15.0 + serverInfo: ServerInfo | None = None + + class InitializedNotification(BaseNotification): def __init__(self, **kwargs: Any) -> None: kwargs["method"] = "initialized" diff --git a/tests/test_protocol.py b/tests/test_protocol.py index 03a0c0a..03d101c 100644 --- a/tests/test_protocol.py +++ b/tests/test_protocol.py @@ -21,6 +21,7 @@ HoverRequest, InitializeParams, InitializeRequest, + InitializeResult, InitializedNotification, LanguageKind, Location, @@ -35,6 +36,7 @@ ResponseError, ResponseMessage, ServerCapabilities, + ServerInfo, ShutdownRequest, TextDocumentDidChangeNotification, TextDocumentDidCloseNotification, @@ -462,6 +464,50 @@ def test_server_capabilities_position_encoding_optional(): assert ServerCapabilities().model_dump(exclude_none=True) == {} +def test_initialize_result_minimal(): + result = InitializeResult(capabilities=ServerCapabilities()) + data = result.model_dump(exclude_none=True) + # Only capabilities is required; serverInfo is omitted when absent. + assert data == {"capabilities": {}} + assert "serverInfo" not in data + + +def test_initialize_result_with_server_info(): + result = InitializeResult( + capabilities=ServerCapabilities(positionEncoding=PositionEncodingKind.UTF8), + serverInfo=ServerInfo(name="pyright", version="1.1.0"), + ) + data = result.model_dump(exclude_none=True) + assert data == { + "capabilities": {"positionEncoding": "utf-8"}, + "serverInfo": {"name": "pyright", "version": "1.1.0"}, + } + + +def test_server_info_version_optional(): + data = ServerInfo(name="gopls").model_dump(exclude_none=True) + assert data == {"name": "gopls"} + + +def test_server_info_requires_name(): + with pytest.raises(ValidationError): + ServerInfo() + + +def test_initialize_result_parses_from_response_payload(): + # A server's initialize response coerces into nested models. + result = InitializeResult.model_validate( + { + "capabilities": {"positionEncoding": "utf-16"}, + "serverInfo": {"name": "rust-analyzer"}, + } + ) + assert result.capabilities.positionEncoding == PositionEncodingKind.UTF16 + assert result.serverInfo is not None + assert result.serverInfo.name == "rust-analyzer" + assert result.serverInfo.version is None + + def _range() -> Range: return Range(start=Position(line=1, character=0), end=Position(line=1, character=8))