Skip to content

Commit a66b625

Browse files
committed
docs: add language configuration guide and refactor project root discovery
1 parent 3d9f8f1 commit a66b625

File tree

4 files changed

+56
-46
lines changed

4 files changed

+56
-46
lines changed

docs/language-configuration.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Language Configuration Guide
2+
3+
In the `lsp-client` SDK, language-specific configurations are primarily defined through the `LanguageConfig` class. This configuration determines how the client identifies files of a specific language, locates project roots, and interacts with the Language Server.
4+
5+
## LanguageConfig
6+
7+
`LanguageConfig` is an immutable configuration class defined in `lsp_client.client.lang`. it contains the following core fields:
8+
9+
- **kind (`lsp_type.LanguageKind`)**:
10+
Specifies the type of programming language (e.g., `Python`, `Rust`, `Go`, `TypeScript`). This typically corresponds to the language identifier in the LSP protocol.
11+
- **suffixes (`list[str]`)**:
12+
A list of file extensions associated with the language (e.g., `[".py", ".pyi"]`). The client uses these suffixes to determine if a file belongs to this language.
13+
- **project_files (`list[str]`)**:
14+
A list of "marker" files used to identify the project root directory (e.g., `["pyproject.toml", "Cargo.toml", "package.json"]`). When the client attempts to determine the root directory for a file, it searches upwards recursively for directories containing these files.
15+
- **exclude_files (`list[str]`)**:
16+
A list of marker files indicating that a directory should _not_ be considered a project root.
17+
18+
## Project Root Discovery
19+
20+
The client determines the project root by searching upwards from the given file path until a valid project root is found or the system root is reached. This discovery is based on the `project_files` and `exclude_files` defined in the `LanguageConfig`.
21+
22+
The `LanguageConfig` class provides a `find_project_root(path: Path)` method that combines `suffixes`, `project_files`, and `exclude_files` for precise detection.
23+
24+
## Client Language Attributes
25+
26+
The `Client` base class includes several general configurations related to language processing and server interaction:
27+
28+
- **`sync_file` (bool, default `True`)**:
29+
Whether to automatically synchronize file content with the server. If enabled, the client automatically sends `textDocument/didOpen` and `textDocument/didClose` notifications when using the `open_files` context manager.
30+
- **`initialization_options` (dict)**:
31+
Custom options passed to the server during the `initialize` request. Most Language Servers require specific configurations here to function correctly.
32+
- **`request_timeout` (float, default `5.0`)**:
33+
The timeout in seconds for LSP requests.
34+
35+
## Example: Defining a Custom Language Client
36+
37+
To support a new language, you typically inherit from `Client` (or a base class like `PythonClientBase`) and implement the `get_language_config` method:
38+
39+
```python
40+
from pathlib import Path
41+
from typing import override
42+
from lsp_client.client.abc import Client
43+
from lsp_client.client.lang import LanguageConfig
44+
from lsp_client.utils.types import lsp_type
45+
46+
class MyNewLanguageClient(Client):
47+
@override
48+
def get_language_config(self) -> LanguageConfig:
49+
return LanguageConfig(
50+
kind=lsp_type.LanguageKind.PlainText, # Replace with actual language kind
51+
suffixes=[".mylang"],
52+
project_files=["my_project.config"]
53+
)
54+
55+
# Other abstract methods like create_default_servers must also be implemented
56+
```

src/lsp_client/client/abc.py

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

100-
@classmethod
101-
@abstractmethod
102-
def check_project_root(cls, path: Path) -> bool:
103-
"""Check if the given path is a project root for this client."""
104-
105-
@classmethod
106-
def find_project_root(cls, path: Path) -> Path | None:
107-
"""Find the project root for the given path by searching upwards."""
108-
for parent in [path, *path.parents]:
109-
if cls.check_project_root(parent):
110-
return parent
111-
112100
@abstractmethod
113101
def get_language_config(self) -> LanguageConfig:
114102
"""Get language-specific configuration for this client."""

src/lsp_client/clients/base.py

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
from abc import ABC
4-
from pathlib import Path
54
from typing import override
65

76
from lsp_client.client.abc import Client
@@ -10,19 +9,6 @@
109

1110

1211
class PythonClientBase(Client, ABC):
13-
@classmethod
14-
def check_project_root(cls, path: Path) -> bool:
15-
return any(
16-
(path / f).exists()
17-
for f in (
18-
"pyproject.toml",
19-
"setup.py",
20-
"setup.cfg",
21-
"requirements.txt",
22-
".python-version",
23-
)
24-
)
25-
2612
@override
2713
def get_language_config(self) -> LanguageConfig:
2814
return LanguageConfig(
@@ -39,10 +25,6 @@ def get_language_config(self) -> LanguageConfig:
3925

4026

4127
class RustClientBase(Client, ABC):
42-
@classmethod
43-
def check_project_root(cls, path: Path) -> bool:
44-
return (path / "Cargo.toml").exists()
45-
4628
@override
4729
def get_language_config(self) -> LanguageConfig:
4830
return LanguageConfig(
@@ -53,10 +35,6 @@ def get_language_config(self) -> LanguageConfig:
5335

5436

5537
class GoClientBase(Client, ABC):
56-
@classmethod
57-
def check_project_root(cls, path: Path) -> bool:
58-
return (path / "go.mod").exists()
59-
6038
@override
6139
def get_language_config(self) -> LanguageConfig:
6240
return LanguageConfig(
@@ -67,13 +45,6 @@ def get_language_config(self) -> LanguageConfig:
6745

6846

6947
class TypeScriptClientBase(Client, ABC):
70-
@classmethod
71-
def check_project_root(cls, path: Path) -> bool:
72-
return any(
73-
(path / f).exists()
74-
for f in ("package.json", "tsconfig.json", "jsconfig.json")
75-
)
76-
7748
@override
7849
def get_language_config(self) -> LanguageConfig:
7950
return LanguageConfig(

src/lsp_client/clients/deno/client.py

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

33
import shutil
44
from functools import partial
5-
from pathlib import Path
65
from subprocess import CalledProcessError
76
from typing import override
87

@@ -138,10 +137,6 @@ class DenoClient(
138137
- VSCode Extension: https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno
139138
"""
140139

141-
@classmethod
142-
def check_project_root(cls, path: Path) -> bool:
143-
return any((path / f).exists() for f in ("deno.json", "deno.jsonc"))
144-
145140
@override
146141
def get_language_config(self) -> LanguageConfig:
147142
return LanguageConfig(

0 commit comments

Comments
 (0)