Skip to content

Commit 84c45ed

Browse files
committed
refactor: replace get_language_id with get_language_config API
Replace the simple get_language_id method with a more comprehensive get_language_config that provides language kind, file suffixes, and project file patterns. This enables better language detection and project root identification across all client implementations.
1 parent 0e85655 commit 84c45ed

File tree

6 files changed

+96
-17
lines changed

6 files changed

+96
-17
lines changed

src/lsp_client/capability/notification/text_document_synchronize.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ async def notify_text_document_opened(
6060
lsp_type.DidOpenTextDocumentParams(
6161
text_document=lsp_type.TextDocumentItem(
6262
uri=self.as_uri(file_path),
63-
language_id=self.get_language_id(),
63+
language_id=self.get_language_config().kind,
6464
version=0, # Version 0 for the initial open
6565
text=file_content,
6666
)

src/lsp_client/client/abc.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
)
2020
from lsp_client.capability.notification import WithNotifyTextDocumentSynchronize
2121
from lsp_client.client.buffer import LSPFileBuffer
22+
from lsp_client.client.lang import LanguageConfig
2223
from lsp_client.jsonrpc.convert import (
2324
notification_serialize,
2425
request_deserialize,
@@ -109,8 +110,8 @@ def find_project_root(cls, path: Path) -> Path | None:
109110
return parent
110111

111112
@abstractmethod
112-
def get_language_id(self) -> lsp_type.LanguageKind:
113-
"""The language ID of the client."""
113+
def get_language_config(self) -> LanguageConfig:
114+
"""Get language-specific configuration for this client."""
114115

115116
@abstractmethod
116117
def create_default_servers(self) -> DefaultServers:

src/lsp_client/client/lang.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
5+
from attrs import Factory, frozen
6+
7+
from lsp_client.utils.workspace import lsp_type
8+
9+
10+
@frozen
11+
class LanguageConfig:
12+
"""Configuration for a programming language in the LSP client."""
13+
14+
kind: lsp_type.LanguageKind
15+
"""The kind of programming language."""
16+
17+
suffixes: list[str]
18+
"""File suffixes associated with the language."""
19+
20+
project_files: list[str]
21+
"""Files that indicate the root of a project for this language."""
22+
23+
exclude_files: list[str] = Factory(list)
24+
"""Files that indicate a directory should not be considered a project root for this language."""
25+
26+
def find_project_root(self, file_path: Path) -> Path | None:
27+
"""Find the project root directory for the given file path.
28+
29+
Args:
30+
file_path (Path): The file path to check.
31+
Returns:
32+
Path | None: The project root directory if found, otherwise None.
33+
"""
34+
35+
if not file_path.is_file():
36+
raise ValueError(f"Expected a file path, got: {file_path}")
37+
38+
if not any(file_path.name.endswith(suffix) for suffix in self.suffixes):
39+
return
40+
41+
for parent in file_path.parents:
42+
if any((parent / excl).exists() for excl in self.exclude_files):
43+
return
44+
if any((parent / proj).exists() for proj in self.project_files):
45+
return parent
46+
47+
return file_path.parent

src/lsp_client/clients/base.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import override
66

77
from lsp_client.client.abc import Client
8+
from lsp_client.client.lang import LanguageConfig
89
from lsp_client.utils.types import lsp_type
910

1011

@@ -23,8 +24,18 @@ def check_project_root(cls, path: Path) -> bool:
2324
)
2425

2526
@override
26-
def get_language_id(self) -> lsp_type.LanguageKind:
27-
return lsp_type.LanguageKind.Python
27+
def get_language_config(self) -> LanguageConfig:
28+
return LanguageConfig(
29+
kind=lsp_type.LanguageKind.Python,
30+
suffixes=[".py", ".pyi"],
31+
project_files=[
32+
"pyproject.toml",
33+
"setup.py",
34+
"setup.cfg",
35+
"requirements.txt",
36+
".python-version",
37+
],
38+
)
2839

2940

3041
class RustClientBase(Client, ABC):
@@ -33,8 +44,12 @@ def check_project_root(cls, path: Path) -> bool:
3344
return (path / "Cargo.toml").exists()
3445

3546
@override
36-
def get_language_id(self) -> lsp_type.LanguageKind:
37-
return lsp_type.LanguageKind.Rust
47+
def get_language_config(self) -> LanguageConfig:
48+
return LanguageConfig(
49+
kind=lsp_type.LanguageKind.Rust,
50+
suffixes=[".rs"],
51+
project_files=["Cargo.toml"],
52+
)
3853

3954

4055
class GoClientBase(Client, ABC):
@@ -43,8 +58,12 @@ def check_project_root(cls, path: Path) -> bool:
4358
return (path / "go.mod").exists()
4459

4560
@override
46-
def get_language_id(self) -> lsp_type.LanguageKind:
47-
return lsp_type.LanguageKind.Go
61+
def get_language_config(self) -> LanguageConfig:
62+
return LanguageConfig(
63+
kind=lsp_type.LanguageKind.Go,
64+
suffixes=[".go"],
65+
project_files=["go.mod"],
66+
)
4867

4968

5069
class TypeScriptClientBase(Client, ABC):
@@ -56,5 +75,9 @@ def check_project_root(cls, path: Path) -> bool:
5675
)
5776

5877
@override
59-
def get_language_id(self) -> lsp_type.LanguageKind:
60-
return lsp_type.LanguageKind.TypeScript
78+
def get_language_config(self) -> LanguageConfig:
79+
return LanguageConfig(
80+
kind=lsp_type.LanguageKind.TypeScript,
81+
suffixes=[".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
82+
project_files=["package.json", "tsconfig.json", "jsconfig.json"],
83+
)

src/lsp_client/clients/deno/client.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
WithRespondWorkspaceFoldersRequest,
4242
)
4343
from lsp_client.client.abc import Client
44+
from lsp_client.client.lang import LanguageConfig
4445
from lsp_client.server import DefaultServers, ServerInstallationError
4546
from lsp_client.server.container import ContainerServer
4647
from lsp_client.server.local import LocalServer
@@ -142,8 +143,12 @@ def check_project_root(cls, path: Path) -> bool:
142143
return any((path / f).exists() for f in ("deno.json", "deno.jsonc"))
143144

144145
@override
145-
def get_language_id(self) -> lsp_type.LanguageKind:
146-
return lsp_type.LanguageKind.TypeScript
146+
def get_language_config(self) -> LanguageConfig:
147+
return LanguageConfig(
148+
kind=lsp_type.LanguageKind.TypeScript,
149+
suffixes=[".ts", ".tsx", ".js", ".jsx", ".mjs"],
150+
project_files=["deno.json", "deno.jsonc"],
151+
)
147152

148153
@override
149154
def create_default_servers(self) -> DefaultServers:

src/lsp_client/protocol/client.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
from collections.abc import AsyncGenerator
55
from contextlib import asynccontextmanager
66
from pathlib import Path
7-
from typing import Protocol, runtime_checkable
7+
from typing import TYPE_CHECKING, Protocol, runtime_checkable
88

99
import anyio
1010

11-
from lsp_client.utils.types import AnyPath, Notification, Request, Response, lsp_type
11+
from lsp_client.utils.types import AnyPath, Notification, Request, Response
1212
from lsp_client.utils.uri import from_local_uri
1313
from lsp_client.utils.workspace import DEFAULT_WORKSPACE_DIR, Workspace
1414

15+
if TYPE_CHECKING:
16+
from lsp_client.client.lang import LanguageConfig
17+
1518

1619
@runtime_checkable
1720
class CapabilityClientProtocol(Protocol):
@@ -24,8 +27,8 @@ def get_workspace(self) -> Workspace:
2427
"""The workspace folders of the client."""
2528

2629
@abstractmethod
27-
def get_language_id(self) -> lsp_type.LanguageKind:
28-
"""The language ID of the client."""
30+
def get_language_config(self) -> LanguageConfig:
31+
"""Get language-specific configuration for this client."""
2932

3033
@abstractmethod
3134
@asynccontextmanager

0 commit comments

Comments
 (0)