Skip to content

Commit 998ecec

Browse files
committed
feat: add project root detection and language-specific client base classes
1 parent c3b0cff commit 998ecec

File tree

10 files changed

+89
-37
lines changed

10 files changed

+89
-37
lines changed

src/lsp_client/client/abc.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,18 @@ def get_workspace(self) -> Workspace:
9595
def get_server(self) -> Server:
9696
return self._server
9797

98+
@classmethod
99+
@abstractmethod
100+
def check_project_root(cls, path: Path) -> bool:
101+
"""Check if the given path is a project root for this client."""
102+
103+
@classmethod
104+
def find_project_root(cls, path: Path) -> Path | None:
105+
"""Find the project root for the given path by searching upwards."""
106+
for parent in [path, *path.parents]:
107+
if cls.check_project_root(parent):
108+
return parent
109+
98110
@abstractmethod
99111
def get_language_id(self) -> lsp_type.LanguageKind:
100112
"""The language ID of the client."""

src/lsp_client/clients/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,4 @@
3131
"PythonClient",
3232
"RustClient",
3333
"TypeScriptClient",
34-
"client",
3534
]

src/lsp_client/clients/base.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from __future__ import annotations
2+
3+
from abc import ABC
4+
from pathlib import Path
5+
from typing import override
6+
7+
from lsp_client.client.abc import Client
8+
from lsp_client.utils.types import lsp_type
9+
10+
11+
class PythonClientBase(Client, ABC):
12+
@classmethod
13+
def check_project_root(cls, path: Path) -> bool:
14+
return any(
15+
(path / f).exists()
16+
for f in (
17+
"pyproject.toml",
18+
"setup.py",
19+
"setup.cfg",
20+
"requirements.txt",
21+
".python-version",
22+
)
23+
)
24+
25+
@override
26+
def get_language_id(self) -> lsp_type.LanguageKind:
27+
return lsp_type.LanguageKind.Python
28+
29+
30+
class RustClientBase(Client, ABC):
31+
@classmethod
32+
def check_project_root(cls, path: Path) -> bool:
33+
return (path / "Cargo.toml").exists()
34+
35+
@override
36+
def get_language_id(self) -> lsp_type.LanguageKind:
37+
return lsp_type.LanguageKind.Rust
38+
39+
40+
class GoClientBase(Client, ABC):
41+
@classmethod
42+
def check_project_root(cls, path: Path) -> bool:
43+
return (path / "go.mod").exists()
44+
45+
@override
46+
def get_language_id(self) -> lsp_type.LanguageKind:
47+
return lsp_type.LanguageKind.Go
48+
49+
50+
class TypeScriptClientBase(Client, ABC):
51+
@classmethod
52+
def check_project_root(cls, path: Path) -> bool:
53+
return any(
54+
(path / f).exists()
55+
for f in ("package.json", "tsconfig.json", "jsconfig.json")
56+
)
57+
58+
@override
59+
def get_language_id(self) -> lsp_type.LanguageKind:
60+
return lsp_type.LanguageKind.TypeScript

src/lsp_client/clients/deno/client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import shutil
44
from functools import partial
5+
from pathlib import Path
56
from subprocess import CalledProcessError
67
from typing import Any, override
78

@@ -158,6 +159,10 @@ class DenoClient(
158159
testing_enable: bool = False
159160
testing_args: list[str] = Factory(list)
160161

162+
@classmethod
163+
def check_project_root(cls, path: Path) -> bool:
164+
return any((path / f).exists() for f in ("deno.json", "deno.jsonc"))
165+
161166
@override
162167
def get_language_id(self) -> lsp_type.LanguageKind:
163168
return lsp_type.LanguageKind.TypeScript

src/lsp_client/clients/gopls.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
WithRespondShowMessageRequest,
4040
WithRespondWorkspaceFoldersRequest,
4141
)
42-
from lsp_client.client.abc import Client
42+
from lsp_client.clients.base import GoClientBase
4343
from lsp_client.server import DefaultServers, ServerInstallationError
4444
from lsp_client.server.container import ContainerServer
4545
from lsp_client.server.local import LocalServer
@@ -75,7 +75,7 @@ async def ensure_gopls_installed() -> None:
7575

7676
@define
7777
class GoplsClient(
78-
Client,
78+
GoClientBase,
7979
WithNotifyDidChangeConfiguration,
8080
WithRequestCallHierarchy,
8181
WithRequestCompletion,
@@ -135,10 +135,6 @@ class GoplsClient(
135135
use_placeholders: bool = False
136136
verbose_output: bool = False
137137

138-
@override
139-
def get_language_id(self) -> lsp_type.LanguageKind:
140-
return lsp_type.LanguageKind.Go
141-
142138
@override
143139
def create_default_servers(self) -> DefaultServers:
144140
return DefaultServers(

src/lsp_client/clients/pyrefly.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
WithRespondShowMessageRequest,
4040
WithRespondWorkspaceFoldersRequest,
4141
)
42-
from lsp_client.client.abc import Client
42+
from lsp_client.clients.base import PythonClientBase
4343
from lsp_client.server import DefaultServers, ServerInstallationError
4444
from lsp_client.server.container import ContainerServer
4545
from lsp_client.server.local import LocalServer
@@ -80,7 +80,7 @@ async def ensure_pyrefly_installed() -> None:
8080

8181
@define
8282
class PyreflyClient(
83-
Client,
83+
PythonClientBase,
8484
WithNotifyDidChangeConfiguration,
8585
WithRequestCallHierarchy,
8686
WithRequestCompletion,
@@ -118,10 +118,6 @@ class PyreflyClient(
118118
diagnostic_mode: Literal["Workspace", "OpenFilesOnly"] = "Workspace"
119119
"""How diagnostics are reported"""
120120

121-
@override
122-
def get_language_id(self) -> lsp_type.LanguageKind:
123-
return lsp_type.LanguageKind.Python
124-
125121
@override
126122
def create_default_servers(self) -> DefaultServers:
127123
return DefaultServers(

src/lsp_client/clients/pyright.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
WithRespondShowMessageRequest,
3838
WithRespondWorkspaceFoldersRequest,
3939
)
40-
from lsp_client.client.abc import Client
40+
from lsp_client.clients.base import PythonClientBase
4141
from lsp_client.server import DefaultServers, ServerInstallationError
4242
from lsp_client.server.container import ContainerServer
4343
from lsp_client.server.local import LocalServer
@@ -75,7 +75,7 @@ async def ensure_pyright_installed() -> None:
7575

7676
@define
7777
class PyrightClient(
78-
Client,
78+
PythonClientBase,
7979
WithNotifyDidChangeConfiguration,
8080
WithRequestCallHierarchy,
8181
WithRequestCompletion,
@@ -108,10 +108,6 @@ class PyrightClient(
108108
diagnostic_mode: Literal["openFilesOnly", "workspace"] = "workspace"
109109
disable_pull_diagnostics: bool = False
110110

111-
@override
112-
def get_language_id(self) -> lsp_type.LanguageKind:
113-
return lsp_type.LanguageKind.Python
114-
115111
@override
116112
def create_default_servers(self) -> DefaultServers:
117113
return DefaultServers(

src/lsp_client/clients/rust_analyzer.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
WithRespondShowMessageRequest,
4141
WithRespondWorkspaceFoldersRequest,
4242
)
43-
from lsp_client.client.abc import Client
43+
from lsp_client.clients.base import RustClientBase
4444
from lsp_client.server import DefaultServers, ServerInstallationError
4545
from lsp_client.server.container import ContainerServer
4646
from lsp_client.server.local import LocalServer
@@ -77,7 +77,7 @@ async def ensure_rust_analyzer_installed() -> None:
7777

7878
@define
7979
class RustAnalyzerClient(
80-
Client,
80+
RustClientBase,
8181
WithNotifyDidChangeConfiguration,
8282
WithRequestCallHierarchy,
8383
WithRequestCompletion,
@@ -124,10 +124,6 @@ class RustAnalyzerClient(
124124
cargo_extra_args: list[str] = Factory(list)
125125
cargo_extra_env: dict[str, str] = Factory(dict)
126126

127-
@override
128-
def get_language_id(self) -> lsp_type.LanguageKind:
129-
return lsp_type.LanguageKind.Rust
130-
131127
@override
132128
def create_default_servers(self) -> DefaultServers:
133129
return DefaultServers(

src/lsp_client/clients/ty.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
WithRespondShowMessageRequest,
3737
WithRespondWorkspaceFoldersRequest,
3838
)
39-
from lsp_client.client.abc import Client
39+
from lsp_client.clients.base import PythonClientBase
4040
from lsp_client.server import DefaultServers, ServerInstallationError
4141
from lsp_client.server.container import ContainerServer
4242
from lsp_client.server.local import LocalServer
@@ -72,7 +72,7 @@ async def ensure_ty_installed() -> None:
7272

7373
@define
7474
class TyClient(
75-
Client,
75+
PythonClientBase,
7676
WithNotifyDidChangeConfiguration,
7777
WithRequestCompletion,
7878
WithRequestDeclaration,
@@ -105,10 +105,6 @@ class TyClient(
105105
inlay_hints_variable_types: bool = True
106106
inlay_hints_call_argument_names: bool = True
107107

108-
@override
109-
def get_language_id(self) -> lsp_type.LanguageKind:
110-
return lsp_type.LanguageKind.Python
111-
112108
@override
113109
def create_default_servers(self) -> DefaultServers:
114110
return DefaultServers(

src/lsp_client/clients/typescript.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
WithRespondShowMessageRequest,
3838
WithRespondWorkspaceFoldersRequest,
3939
)
40-
from lsp_client.client.abc import Client
40+
from lsp_client.clients.base import TypeScriptClientBase
4141
from lsp_client.server import DefaultServers, ServerInstallationError
4242
from lsp_client.server.container import ContainerServer
4343
from lsp_client.server.local import LocalServer
@@ -81,7 +81,7 @@ async def ensure_typescript_installed() -> None:
8181

8282
@define
8383
class TypescriptClient(
84-
Client,
84+
TypeScriptClientBase,
8585
WithNotifyDidChangeConfiguration,
8686
WithRequestCompletion,
8787
WithRequestHover,
@@ -118,10 +118,6 @@ class TypescriptClient(
118118
include_completions_for_module_exports: bool = True
119119
include_completions_with_insert_text: bool = True
120120

121-
@override
122-
def get_language_id(self) -> lsp_type.LanguageKind:
123-
return lsp_type.LanguageKind.TypeScript
124-
125121
@override
126122
def create_default_servers(self) -> DefaultServers:
127123
return DefaultServers(

0 commit comments

Comments
 (0)