-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pytest-lsp: Add syntax for selecting a specific version of a client
- Loading branch information
Showing
4 changed files
with
97 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
It is now possible to select a specific version of a client when using the ``client_capabilities()`` function. | ||
e.g. ``client-name@latest``, ``client-name@v2`` or ``[email protected]``. ``pytest-lsp`` will choose the latest available version of the client that satisfies the given constraint. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,15 +2,18 @@ | |
import json | ||
import logging | ||
import os | ||
import pathlib | ||
import sys | ||
import traceback | ||
import typing | ||
from typing import Dict | ||
from typing import List | ||
from typing import Optional | ||
from typing import Union | ||
|
||
from lsprotocol import types | ||
from lsprotocol.converters import get_converter | ||
from packaging.version import parse as parse_version | ||
from pygls.exceptions import JsonRpcException | ||
from pygls.exceptions import PyglsError | ||
from pygls.lsp.client import BaseLanguageClient | ||
|
@@ -207,32 +210,73 @@ def show_document( | |
def client_capabilities(client_spec: str) -> types.ClientCapabilities: | ||
"""Find the capabilities that correspond to the given client spec. | ||
This function supports the following syntax | ||
``client-name`` or ``client-name@latest`` | ||
Return the capabilities of the latest version of ``client-name`` | ||
``client-name@v2`` | ||
Return the latest release of the ``v2`` of ``client-name`` | ||
``[email protected]`` | ||
Return exactly ``v2.3.1`` of ``client-name`` | ||
Parameters | ||
---------- | ||
client_spec | ||
A string describing the client to load the corresponding | ||
The string describing the client to load the corresponding | ||
capabilities for. | ||
Raises | ||
------ | ||
ValueError | ||
If the requested client's capabilities could not be found | ||
Returns | ||
------- | ||
ClientCapabilities | ||
The requested client capabilities | ||
""" | ||
|
||
# Currently, we only have a single version of each client so let's just return the | ||
# first one we find. | ||
# | ||
# TODO: Implement support for [email protected] | ||
# TODO: Implement support for client@latest? | ||
filename = None | ||
candidates: Dict[str, pathlib.Path] = {} | ||
|
||
client_spec = client_spec.replace("-", "_") | ||
target_version = "latest" | ||
|
||
if "@" in client_spec: | ||
client_spec, target_version = client_spec.split("@") | ||
if target_version.startswith("v"): | ||
target_version = target_version[1:] | ||
|
||
for resource in resources.files("pytest_lsp.clients").iterdir(): | ||
filename = typing.cast(pathlib.Path, resource) | ||
|
||
# Skip the README or any other files that we don't care about. | ||
if not resource.name.endswith(".json"): | ||
if not filename.suffix == ".json": | ||
continue | ||
|
||
if resource.name.startswith(client_spec.replace("-", "_")): | ||
filename = resource | ||
break | ||
name, version = filename.stem.split("_v") | ||
if name == client_spec: | ||
if version.startswith(target_version) or target_version == "latest": | ||
candidates[version] = filename | ||
|
||
if len(candidates) == 0: | ||
raise ValueError( | ||
f"Could not find capabilities for '{client_spec}@{target_version}'" | ||
) | ||
|
||
if not filename: | ||
raise ValueError(f"Unknown client: '{client_spec}'") | ||
# Out of the available candidates, choose the latest version | ||
selected_version = sorted(candidates.keys(), key=parse_version, reverse=True)[0] | ||
filename = candidates[selected_version] | ||
|
||
converter = get_converter() | ||
capabilities = json.loads(filename.read_text()) | ||
|
||
params = converter.structure(capabilities, types.InitializeParams) | ||
logger.info( | ||
"Selected %s v%s", | ||
params.client_info.name, # type: ignore[union-attr] | ||
params.client_info.version, # type: ignore[union-attr] | ||
) | ||
|
||
return params.capabilities |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,31 +5,51 @@ | |
|
||
import pygls.uris as uri | ||
import pytest | ||
|
||
import pytest_lsp | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"client_spec,client_capabilities", | ||
"client_spec,capabilities", | ||
[ | ||
*itertools.product( | ||
["visual_studio_code", "visual-studio-code"], | ||
["visual_studio_code_v1.65.2.json"], | ||
), | ||
("neovim", "neovim_v0.6.1.json"), | ||
*itertools.product( | ||
["[email protected]", "[email protected]", "[email protected]"], | ||
["neovim_v0.6.1.json"], | ||
), | ||
*itertools.product( | ||
["neovim", "neovim@latest", "neovim@v0", "[email protected]", "[email protected]"], | ||
["neovim_v0.9.1.json"], | ||
), | ||
], | ||
) | ||
def test_client_capabilities( | ||
pytester: pytest.Pytester, client_spec: str, client_capabilities: str | ||
pytester: pytest.Pytester, client_spec: str, capabilities: str | ||
): | ||
"""Ensure that the plugin can mimic the requested client's capabilities.""" | ||
"""Ensure that the plugin can mimic the requested client's capabilities correctly. | ||
Parameters | ||
---------- | ||
pytester | ||
pytest's built in pytester fixture. | ||
client_spec | ||
The string used to select the desired client and version | ||
client_capabilities | ||
The filename containing the expected client capabilities | ||
""" | ||
|
||
python = sys.executable | ||
testdir = pathlib.Path(__file__).parent | ||
server = testdir / "servers" / "capabilities.py" | ||
root_uri = uri.from_fs_path(str(testdir)) | ||
|
||
clients_dir = pathlib.Path(pytest_lsp.__file__).parent / "clients" | ||
with (clients_dir / client_capabilities).open() as f: | ||
with (clients_dir / capabilities).open() as f: | ||
# Easiest way to reformat the JSON onto a single line | ||
expected = json.dumps(json.load(f)["capabilities"]) | ||
|
||
|
@@ -71,14 +91,23 @@ async def client(lsp_client: LanguageClient): | |
f""" | ||
import json | ||
import pytest | ||
from lsprotocol.types import ExecuteCommandParams | ||
from lsprotocol import types | ||
from lsprotocol.converters import get_converter | ||
@pytest.mark.asyncio | ||
async def test_capabilities(client): | ||
actual = await client.workspace_execute_command_async( | ||
ExecuteCommandParams(command="return.client.capabilities") | ||
types.ExecuteCommandParams(command="return.client.capabilities") | ||
) | ||
assert actual == json.loads('{expected}') | ||
expected = json.loads('{expected}') | ||
# lsprotocol is going to filter out any quirks of the client | ||
# so we can't compare the dicts directly. | ||
converter = get_converter() | ||
actual_capabilities = converter.structure(actual, types.ClientCapabilities) | ||
expected_capabilities = converter.structure(expected, types.ClientCapabilities) | ||
assert actual_capabilities == expected_capabilities | ||
""" | ||
) | ||
|
||
|