Skip to content

Commit 9a5851d

Browse files
Copilotobserverw
andauthored
Add default configuration maps for LSP clients to enable extra features (#15)
* Initial plan * Add default configuration_map for all clients with extra features enabled Co-authored-by: observerw <[email protected]> * Add comprehensive tests and documentation for default configurations Co-authored-by: observerw <[email protected]> * Address code review comments: add public API and improve documentation Co-authored-by: observerw <[email protected]> * Add example demonstrating default configuration usage Co-authored-by: observerw <[email protected]> * Add test coverage for TyClient and PyreflyClient, improve example documentation Co-authored-by: observerw <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: observerw <[email protected]>
1 parent 0b7a4c4 commit 9a5851d

File tree

12 files changed

+756
-0
lines changed

12 files changed

+756
-0
lines changed

docs/configuration.md

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

33
The `lsp-client` SDK provides a robust and flexible way to manage Language Server Protocol (LSP) configurations. It supports global settings, path-based overrides, and automatic synchronization with the server.
44

5+
## Default Configurations
6+
7+
**All built-in clients now come with sensible default configurations that enable extra features out of the box**, including:
8+
- **Inlay hints** for types, parameters, return values, etc.
9+
- **Enhanced diagnostics** and linting
10+
- **Auto-import completions**
11+
- **Code lenses** and other IDE features
12+
13+
This means you can start using clients immediately without needing to manually configure these features. The defaults are automatically applied when the client starts, unless you provide your own `configuration_map`.
14+
15+
### Example: Using Default Configuration
16+
17+
```python
18+
from lsp_client.clients.rust_analyzer import RustAnalyzerClient
19+
20+
# Simply create and use the client - inlay hints and other features are enabled by default
21+
async with RustAnalyzerClient() as client:
22+
hints = await client.request_inlay_hint(file_path="main.rs", range=...)
23+
# Inlay hints are automatically enabled without any configuration!
24+
```
25+
26+
### Customizing Default Configuration
27+
28+
You can still provide your own configuration to override or extend the defaults:
29+
30+
```python
31+
from lsp_client.utils.config import ConfigurationMap
32+
from lsp_client.clients.pyright import PyrightClient
33+
34+
# Create custom configuration
35+
config_map = ConfigurationMap()
36+
config_map.update_global({
37+
"python": {
38+
"analysis": {
39+
"typeCheckingMode": "strict", # Override default
40+
"autoImportCompletions": False, # Disable a feature
41+
}
42+
}
43+
})
44+
45+
# Apply custom configuration
46+
async with PyrightClient() as client:
47+
client.configuration_map = config_map
48+
# Now uses your custom settings instead of defaults
49+
```
50+
551
## ConfigurationMap
652

753
The `ConfigurationMap` is the central utility for managing settings. It implements a tiered configuration logic similar to "User Settings" vs. "Workspace Settings" in popular IDEs.
@@ -104,3 +150,66 @@ When a file matches multiple scope patterns, configurations are merged in the or
104150

105151
### Path Resolution
106152
The `scope_uri` provided by the server is automatically converted to a local filesystem path before pattern matching, allowing you to use standard Glob patterns.
153+
154+
## Default Configurations by Client
155+
156+
Each built-in client provides default configurations tailored to the language server's capabilities:
157+
158+
### Rust Analyzer
159+
- Inlay hints: Types, parameters, lifetimes, closures, reborrow hints
160+
- Diagnostics: Enabled with experimental features
161+
- Completion: Auto-import, auto-self, callable snippets
162+
- Check on save: Enabled
163+
- Code lenses: Run, debug, implementations, references
164+
165+
### Gopls (Go)
166+
- Inlay hints: All types (variables, parameters, function types, etc.)
167+
- Code lenses: Generate, test, tidy, upgrade dependencies, vulnerability checks
168+
- Analyses: Field alignment, nilness, unused params/writes
169+
- Completion: Documentation, deep completion, fuzzy matching, placeholders
170+
- Semantic tokens: Enabled
171+
172+
### Pyright (Python)
173+
- Inlay hints: Variable types, function return types, call arguments, pytest parameters
174+
- Auto-import completions: Enabled
175+
- Type checking mode: Basic (can be overridden)
176+
- Diagnostics: Open files only
177+
- Auto-search paths and indexing: Enabled
178+
179+
### TypeScript Language Server
180+
- Inlay hints: All types for both TypeScript and JavaScript
181+
- Suggestions: Auto-imports, complete function calls, module exports
182+
- Preferences: Package.json auto-imports, shortest import specifier
183+
184+
### Deno
185+
- Inlay hints: All parameter and type hints
186+
- Linting: Enabled
187+
- Unstable features: Enabled
188+
- Code lenses: Implementations, references, tests
189+
- Import suggestions: Auto-discover from deno.land and esm.sh
190+
191+
### Pyrefly (Python)
192+
- Inlay hints: Variable types, function return types, parameter types
193+
- Diagnostics and auto-imports: Enabled
194+
195+
### Ty (Python)
196+
- Diagnostics and auto-imports: Enabled
197+
198+
You can always inspect the default configuration for any client:
199+
200+
```python
201+
from lsp_client.clients.rust_analyzer import RustAnalyzerClient
202+
203+
client = RustAnalyzerClient()
204+
config_map = client.create_default_configuration_map()
205+
206+
# Check if configuration exists
207+
if config_map and config_map.has_global_config():
208+
# Get specific configuration values
209+
inlay_hints_enabled = config_map.get(None, "rust-analyzer.inlayHints.enable")
210+
print(f"Inlay hints enabled: {inlay_hints_enabled}")
211+
212+
# Get entire section
213+
all_inlay_hints = config_map.get(None, "rust-analyzer.inlayHints")
214+
print(f"All inlay hint settings: {all_inlay_hints}")
215+
```

examples/default_configuration.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
"""
2+
Example demonstrating default configurations for LSP clients.
3+
4+
This example shows how the new default configurations work:
5+
1. All clients automatically have sensible defaults
6+
2. Inlay hints and other features are enabled out of the box
7+
3. Users can still override defaults if needed
8+
"""
9+
10+
from __future__ import annotations
11+
12+
from lsp_client.clients.pyright import PyrightClient
13+
from lsp_client.clients.rust_analyzer import RustAnalyzerClient
14+
from lsp_client.utils.config import ConfigurationMap
15+
16+
17+
def example_using_defaults():
18+
"""Example: Using default configuration (no setup required)"""
19+
print("Example 1: Using Default Configuration")
20+
print("-" * 50)
21+
22+
# Create a client - it automatically has default configuration
23+
client = PyrightClient()
24+
25+
# Get the default configuration that was created
26+
config_map = client.create_default_configuration_map()
27+
28+
if config_map:
29+
print("✓ Default configuration is available!")
30+
31+
# Check what inlay hints are enabled
32+
inlay_hints = config_map.get(None, "python.analysis.inlayHints")
33+
print(f" Inlay hints configuration: {inlay_hints}")
34+
35+
# Check type checking mode
36+
type_checking = config_map.get(None, "python.analysis.typeCheckingMode")
37+
print(f" Type checking mode: {type_checking}")
38+
39+
# Check auto-import completions
40+
auto_imports = config_map.get(None, "python.analysis.autoImportCompletions")
41+
print(f" Auto-import completions: {auto_imports}")
42+
43+
print()
44+
45+
46+
def example_with_custom_override():
47+
"""Example: Overriding default configuration"""
48+
print("Example 2: Overriding Default Configuration")
49+
print("-" * 50)
50+
51+
# Create a client
52+
client = RustAnalyzerClient()
53+
54+
# Get defaults - demonstrating that defaults exist
55+
_ = client.create_default_configuration_map()
56+
print("✓ Default configuration created")
57+
58+
# Create custom configuration that overrides some defaults
59+
custom_config = ConfigurationMap()
60+
custom_config.update_global(
61+
{
62+
"rust-analyzer": {
63+
"inlayHints": {
64+
"enable": False, # Disable inlay hints
65+
},
66+
"checkOnSave": {"enable": False}, # Disable check on save
67+
}
68+
}
69+
)
70+
71+
print("✓ Custom configuration created")
72+
print(" - Inlay hints: disabled")
73+
print(" - Check on save: disabled")
74+
75+
# You would set this on the client:
76+
# client.configuration_map = custom_config
77+
78+
print()
79+
80+
81+
def example_inspecting_defaults():
82+
"""Example: Inspecting default configurations for all clients"""
83+
print("Example 3: Inspecting Default Configurations")
84+
print("-" * 50)
85+
86+
from lsp_client.clients import clients
87+
88+
for client_cls in clients:
89+
client = client_cls()
90+
config_map = client.create_default_configuration_map()
91+
92+
if config_map and config_map.has_global_config():
93+
print(f"✓ {client_cls.__name__} has default configuration")
94+
95+
# Try to find configuration - inlay hints for most clients,
96+
# or other features like diagnostics for clients without inlay hints
97+
for section in [
98+
"rust-analyzer.inlayHints",
99+
"gopls.hints",
100+
"python.analysis.inlayHints",
101+
"typescript.inlayHints",
102+
"deno.inlayHints",
103+
"pyrefly.inlayHints",
104+
"ty.diagnostics", # TyClient uses diagnostics instead of inlay hints
105+
]:
106+
value = config_map.get(None, section)
107+
if value is not None:
108+
print(f" → {section} is configured")
109+
break
110+
111+
print()
112+
113+
114+
if __name__ == "__main__":
115+
print("LSP Client Default Configuration Examples")
116+
print("=" * 50)
117+
print()
118+
119+
example_using_defaults()
120+
example_with_custom_override()
121+
example_inspecting_defaults()
122+
123+
print("=" * 50)
124+
print("All examples completed successfully!")

src/lsp_client/client/abc.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from lsp_client.server import DefaultServers, Server, ServerRuntimeError
3535
from lsp_client.server.types import ServerRequest
3636
from lsp_client.utils.channel import Receiver, channel
37+
from lsp_client.utils.config import ConfigurationMap
3738
from lsp_client.utils.types import AnyPath, Notification, Request, Response, lsp_type
3839
from lsp_client.utils.workspace import (
3940
DEFAULT_WORKSPACE_DIR,
@@ -109,6 +110,38 @@ def create_default_servers(self) -> DefaultServers:
109110
def check_server_compatibility(self, info: lsp_type.ServerInfo | None) -> None:
110111
"""Check if the available server capabilities are compatible with the client."""
111112

113+
def create_default_configuration_map(self) -> ConfigurationMap | None:
114+
"""
115+
Create default configuration map for this client.
116+
117+
This method can be overridden by subclasses to provide default configurations
118+
that enable extra features like inlay hints, diagnostics, etc.
119+
120+
The base implementation returns None, meaning no default configuration.
121+
Subclasses that support configuration should override this method to provide
122+
sensible defaults that enable commonly-used features.
123+
124+
Returns:
125+
ConfigurationMap with default settings, or None if no defaults are needed.
126+
127+
Example:
128+
Override this method in a client subclass to provide defaults:
129+
130+
```python
131+
@override
132+
def create_default_configuration_map(self) -> ConfigurationMap | None:
133+
config_map = ConfigurationMap()
134+
config_map.update_global({
135+
"myserver": {
136+
"inlayHints": {"enable": True},
137+
"diagnostics": {"enable": True},
138+
}
139+
})
140+
return config_map
141+
```
142+
"""
143+
return None
144+
112145
@override
113146
@asynccontextmanager
114147
async def open_files(self, *file_paths: AnyPath) -> AsyncGenerator[None]:
@@ -246,6 +279,18 @@ async def _run_server(
246279
async def __asynccontextmanager__(self) -> AsyncGenerator[Self]:
247280
self._workspace = format_workspace(self._workspace_arg)
248281

282+
# Initialize default configuration map if the client supports configuration
283+
# and no configuration map has been set yet
284+
from lsp_client.capability.server_request import WithRespondConfigurationRequest
285+
286+
if (
287+
isinstance(self, WithRespondConfigurationRequest)
288+
and self.configuration_map is None
289+
):
290+
default_config = self.create_default_configuration_map()
291+
if default_config is not None:
292+
self.configuration_map = default_config
293+
249294
async with (
250295
asyncer.create_task_group() as tg,
251296
self._run_server() as (server, receiver), # ty: ignore[invalid-argument-type]

src/lsp_client/clients/deno/client.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from lsp_client.server import DefaultServers, ServerInstallationError
4545
from lsp_client.server.container import ContainerServer
4646
from lsp_client.server.local import LocalServer
47+
from lsp_client.utils.config import ConfigurationMap
4748
from lsp_client.utils.types import lsp_type
4849

4950
from .extension import (
@@ -155,3 +156,52 @@ def create_default_servers(self) -> DefaultServers:
155156
@override
156157
def check_server_compatibility(self, info: lsp_type.ServerInfo | None) -> None:
157158
return
159+
160+
@override
161+
def create_default_configuration_map(self) -> ConfigurationMap | None:
162+
"""Create default configuration for deno with all features enabled."""
163+
config_map = ConfigurationMap()
164+
config_map.update_global(
165+
{
166+
"deno": {
167+
# Enable deno
168+
"enable": True,
169+
# Enable inlay hints
170+
"inlayHints": {
171+
"parameterNames": {"enabled": "all"},
172+
"parameterTypes": {"enabled": True},
173+
"variableTypes": {"enabled": True},
174+
"propertyDeclarationTypes": {"enabled": True},
175+
"functionLikeReturnTypes": {"enabled": True},
176+
"enumMemberValues": {"enabled": True},
177+
},
178+
# Enable linting
179+
"lint": True,
180+
# Enable unstable features
181+
"unstable": True,
182+
# Enable code lens
183+
"codeLens": {
184+
"implementations": True,
185+
"references": True,
186+
"referencesAllFunctions": True,
187+
"test": True,
188+
"testArgs": ["--allow-all"],
189+
},
190+
# Enable suggestions
191+
"suggest": {
192+
"autoImports": True,
193+
"completeFunctionCalls": True,
194+
"names": True,
195+
"paths": True,
196+
"imports": {
197+
"autoDiscover": True,
198+
"hosts": {
199+
"https://deno.land": True,
200+
"https://esm.sh": True,
201+
},
202+
},
203+
},
204+
}
205+
}
206+
)
207+
return config_map

0 commit comments

Comments
 (0)