Skip to content

Commit 6358569

Browse files
committed
LspClient (and json rpc client), use it optionally(enabled by default) in pyrefly and ruff
1 parent b39729a commit 6358569

16 files changed

Lines changed: 1471 additions & 18 deletions

File tree

extensions/fine_python_pyrefly/fine_python_pyrefly/lint_files_handler.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@
1212
icommandrunner,
1313
ilogger,
1414
ifileeditor,
15+
iprojectinfoprovider,
1516
isrcartifactfileclassifier,
1617
iextensionrunnerinfoprovider,
1718
)
19+
from fine_python_pyrefly.pyrefly_lsp_service import PyreflyLspService
1820

1921

2022
@dataclasses.dataclass
2123
class PyreflyLintFilesHandlerConfig(code_action.ActionHandlerConfig):
2224
python_version: str | None = None
25+
use_cli: bool = False
2326

2427

2528
class PyreflyLintFilesHandler(
@@ -47,6 +50,8 @@ def __init__(
4750
command_runner: icommandrunner.ICommandRunner,
4851
src_artifact_file_classifier: isrcartifactfileclassifier.ISrcArtifactFileClassifier,
4952
extension_runner_info_provider: iextensionrunnerinfoprovider.IExtensionRunnerInfoProvider,
53+
project_info_provider: iprojectinfoprovider.IProjectInfoProvider,
54+
lsp_service: PyreflyLspService,
5055
) -> None:
5156
self.config = config
5257
self.cache = cache
@@ -55,9 +60,27 @@ def __init__(
5560
self.command_runner = command_runner
5661
self.src_artifact_file_classifier = src_artifact_file_classifier
5762
self.extension_runner_info_provider = extension_runner_info_provider
63+
self.project_info_provider: iprojectinfoprovider.IProjectInfoProvider = project_info_provider
64+
self.lsp_service: PyreflyLspService = lsp_service
5865

5966
self.pyrefly_bin_path = Path(sys.executable).parent / "pyrefly"
6067

68+
if not self.config.use_cli:
69+
# Pyrefly uses pull-based config: the LSP server sends
70+
# workspace/configuration requests with section="python",
71+
# expecting responses like [{"pyrefly": {"displayTypeErrors": ...}}].
72+
# The same format is used for initializationOptions.
73+
venv_dir = self.extension_runner_info_provider.get_venv_dir_path_of_env("runtime")
74+
interpreter_path = self.extension_runner_info_provider.get_venv_python_interpreter(venv_dir)
75+
site_packages = self.extension_runner_info_provider.get_venv_site_packages(venv_dir)
76+
self.lsp_service.update_settings({
77+
"pythonPath": str(interpreter_path),
78+
"pyrefly": {
79+
"displayTypeErrors": "force-on",
80+
"extraPaths": [str(p) for p in site_packages],
81+
},
82+
})
83+
6184
async def run_on_single_file(
6285
self, file_path: Path
6386
) -> lint_files_action.LintFilesRunResult:
@@ -76,7 +99,14 @@ async def run_on_single_file(
7699
) as session:
77100
file_version = await session.read_file_version(file_path)
78101

79-
lint_messages = await self.run_pyrefly_lint_on_single_file(file_path)
102+
if self.config.use_cli:
103+
lint_messages = await self.run_pyrefly_lint_on_single_file(file_path)
104+
else:
105+
root_uri = self.project_info_provider.get_current_project_dir_path().as_uri()
106+
await self.lsp_service.ensure_started(root_uri)
107+
108+
lint_messages = await self.lsp_service.check_file(file_path)
109+
80110
messages[str(file_path)] = lint_messages
81111
await self.cache.save_file_cache(
82112
file_path, file_version, self.CACHE_KEY, lint_messages
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from __future__ import annotations
2+
3+
import sys
4+
from pathlib import Path
5+
from typing import override
6+
7+
from finecode_extension_api import service
8+
from finecode_extension_api.actions import lint_files as lint_files_action
9+
from finecode_extension_api.interfaces import ifileeditor, ilspclient, ilogger
10+
from finecode_extension_api.contrib.lsp_service import LspService, map_diagnostics_to_lint_messages
11+
12+
13+
class PyreflyLspService(service.DisposableService):
14+
"""Pyrefly LSP service — thin wrapper around generic LspService."""
15+
16+
def __init__(
17+
self,
18+
lsp_client: ilspclient.ILspClient,
19+
file_editor: ifileeditor.IFileEditor,
20+
logger: ilogger.ILogger,
21+
) -> None:
22+
pyrefly_bin = Path(sys.executable).parent / "pyrefly"
23+
self._lsp_service = LspService(
24+
lsp_client=lsp_client,
25+
file_editor=file_editor,
26+
logger=logger,
27+
cmd=f"{pyrefly_bin} lsp",
28+
language_id="python",
29+
readable_id="pyrefly-lsp",
30+
)
31+
32+
@override
33+
async def init(self) -> None:
34+
await self._lsp_service.init()
35+
36+
@override
37+
def dispose(self) -> None:
38+
self._lsp_service.dispose()
39+
40+
def update_settings(self, settings: dict[str, object]) -> None:
41+
self._lsp_service.update_settings(settings)
42+
43+
async def ensure_started(self, root_uri: str) -> None:
44+
await self._lsp_service.ensure_started(root_uri)
45+
46+
async def check_file(
47+
self,
48+
file_path: Path,
49+
timeout: float = 30.0,
50+
) -> list[lint_files_action.LintMessage]:
51+
raw_diagnostics = await self._lsp_service.check_file(file_path, timeout)
52+
return map_diagnostics_to_lint_messages(
53+
raw_diagnostics, default_source="pyrefly"
54+
)

extensions/fine_python_pyrefly/pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ readme = "README.md"
77
requires-python = ">=3.11, <= 3.14"
88
dependencies = ["finecode_extension_api~=0.4.0a0", "pyrefly (>=0.30.0,<1.0.0)"]
99

10+
[project.optional-dependencies]
11+
jsonrpc = ["finecode_jsonrpc~=0.1.0a0"]
12+
1013
[project.entry-points."finecode.activator"]
1114
fine_python_pyrefly = "fine_python_pyrefly.activator:Activator"
1215

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from finecode_extension_api import extension
2+
from finecode_extension_api.interfaces import iserviceregistry
3+
4+
from fine_python_ruff.ruff_lsp_service import RuffLspService
5+
6+
7+
class Activator(extension.ExtensionActivator):
8+
def __init__(self, registry: iserviceregistry.IServiceRegistry) -> None:
9+
self.registry = registry
10+
11+
def activate(self) -> None:
12+
self.registry.register_impl(
13+
RuffLspService,
14+
RuffLspService,
15+
)

extensions/fine_python_ruff/fine_python_ruff/lint_files_handler.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
icommandrunner,
1313
ilogger,
1414
ifileeditor,
15+
iprojectinfoprovider,
1516
)
17+
from fine_python_ruff.ruff_lsp_service import RuffLspService
1618

1719

1820
@dataclasses.dataclass
@@ -23,6 +25,7 @@ class RuffLintFilesHandlerConfig(code_action.ActionHandlerConfig):
2325
ignore: list[str] | None = None # Rules to disable
2426
extend_select: list[str] | None = None
2527
preview: bool = False
28+
use_cli: bool = False
2629

2730

2831
class RuffLintFilesHandler(
@@ -40,15 +43,37 @@ def __init__(
4043
logger: ilogger.ILogger,
4144
file_editor: ifileeditor.IFileEditor,
4245
command_runner: icommandrunner.ICommandRunner,
46+
project_info_provider: iprojectinfoprovider.IProjectInfoProvider,
47+
lsp_service: RuffLspService,
4348
) -> None:
4449
self.config = config
4550
self.cache = cache
4651
self.logger = logger
4752
self.file_editor = file_editor
4853
self.command_runner = command_runner
54+
self.project_info_provider: iprojectinfoprovider.IProjectInfoProvider = project_info_provider
55+
self.lsp_service: RuffLspService = lsp_service
4956

5057
self.ruff_bin_path = Path(sys.executable).parent / "ruff"
5158

59+
if not self.config.use_cli:
60+
# reference: https://docs.astral.sh/ruff/editors/settings/
61+
lint_settings: dict[str, object] = {"enable": True}
62+
if self.config.select is not None:
63+
lint_settings["select"] = self.config.select
64+
if self.config.extend_select is not None:
65+
lint_settings["extendSelect"] = self.config.extend_select
66+
if self.config.ignore is not None:
67+
lint_settings["ignore"] = self.config.ignore
68+
if self.config.preview:
69+
lint_settings["preview"] = True
70+
self.lsp_service.update_settings({
71+
"lint": lint_settings,
72+
"showSyntaxErrors": True,
73+
"lineLength": self.config.line_length,
74+
"targetVersion": self.config.target_version,
75+
})
76+
5277
async def run_on_single_file(
5378
self, file_path: Path
5479
) -> lint_files_action.LintFilesRunResult:
@@ -69,7 +94,13 @@ async def run_on_single_file(
6994
file_content: str = file_info.content
7095
file_version: str = file_info.version
7196

72-
lint_messages = await self.run_ruff_lint_on_single_file(file_path, file_content)
97+
if self.config.use_cli:
98+
lint_messages = await self.run_ruff_lint_on_single_file(file_path, file_content)
99+
else:
100+
root_uri = self.project_info_provider.get_current_project_dir_path().as_uri()
101+
await self.lsp_service.ensure_started(root_uri)
102+
103+
lint_messages = await self.lsp_service.check_file(file_path)
73104
messages[str(file_path)] = lint_messages
74105
await self.cache.save_file_cache(
75106
file_path, file_version, self.CACHE_KEY, lint_messages
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from __future__ import annotations
2+
3+
import sys
4+
from pathlib import Path
5+
from typing import override
6+
7+
from finecode_extension_api import service
8+
from finecode_extension_api.actions import lint_files as lint_files_action
9+
from finecode_extension_api.interfaces import ifileeditor, ilspclient, ilogger
10+
from finecode_extension_api.contrib.lsp_service import LspService, map_diagnostics_to_lint_messages
11+
12+
13+
class RuffLspService(service.DisposableService):
14+
"""Ruff LSP service — thin wrapper around generic LspService."""
15+
16+
def __init__(
17+
self,
18+
lsp_client: ilspclient.ILspClient,
19+
file_editor: ifileeditor.IFileEditor,
20+
logger: ilogger.ILogger,
21+
) -> None:
22+
ruff_bin = Path(sys.executable).parent / "ruff"
23+
self._lsp_service = LspService(
24+
lsp_client=lsp_client,
25+
file_editor=file_editor,
26+
logger=logger,
27+
cmd=f"{ruff_bin} server",
28+
language_id="python",
29+
readable_id="ruff-lsp",
30+
)
31+
32+
@override
33+
async def init(self) -> None:
34+
await self._lsp_service.init()
35+
36+
@override
37+
def dispose(self) -> None:
38+
self._lsp_service.dispose()
39+
40+
def update_settings(self, settings: dict[str, object]) -> None:
41+
self._lsp_service.update_settings(settings)
42+
43+
async def ensure_started(self, root_uri: str) -> None:
44+
await self._lsp_service.ensure_started(root_uri)
45+
46+
async def check_file(
47+
self,
48+
file_path: Path,
49+
timeout: float = 30.0,
50+
) -> list[lint_files_action.LintMessage]:
51+
raw_diagnostics = await self._lsp_service.check_file(file_path, timeout)
52+
return map_diagnostics_to_lint_messages(
53+
raw_diagnostics, default_source="ruff"
54+
)

extensions/fine_python_ruff/pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ readme = "README.md"
77
requires-python = ">=3.11, <= 3.14"
88
dependencies = ["finecode_extension_api~=0.4.0a0", "ruff (>=0.8.0,<1.0.0)"]
99

10+
[project.optional-dependencies]
11+
jsonrpc = ["finecode_jsonrpc~=0.1.0a0"]
12+
13+
[project.entry-points."finecode.activator"]
14+
fine_python_ruff = "fine_python_ruff.activator:Activator"
15+
1016
[dependency-groups]
1117
dev_workspace = ["finecode~=0.4.0a0", "finecode_dev_common_preset~=0.3.0a0"]
1218

finecode_dev_common_preset/src/finecode_dev_common_preset/preset.toml

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,6 @@ source = "finecode_httpclient.HttpClient"
4242
env = "dev_no_runtime"
4343
dependencies = ["finecode_httpclient~=0.1.0a1"]
4444

45-
[[tool.finecode.service]]
46-
interface = "finecode_extension_api.interfaces.ijsonrpcclient.IJsonRpcClient"
47-
source = "finecode_jsonrpc.jsonrpc_client.JsonRpcClientImpl"
48-
env = "dev_no_runtime"
49-
dependencies = ["finecode_jsonrpc~=0.1.0a1"]
50-
51-
[[tool.finecode.service]]
52-
interface = "finecode_extension_api.interfaces.ilspclient.ILspClient"
53-
source = "finecode_extension_runner.impls.lsp_client.LspClientImpl"
54-
env = "dev_no_runtime"
55-
dependencies = []
56-
5745
# TODO: recognize minimal python version automatically
5846
[[tool.finecode.action_handler]]
5947
source = "fine_python_ruff.RuffLintFilesHandler"

finecode_extension_api/src/finecode_extension_api/contrib/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)