Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,52 @@

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.

## Default Configurations

**All built-in clients now come with sensible default configurations that enable extra features out of the box**, including:
- **Inlay hints** for types, parameters, return values, etc.
- **Enhanced diagnostics** and linting
- **Auto-import completions**
- **Code lenses** and other IDE features

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`.

### Example: Using Default Configuration

```python
from lsp_client.clients.rust_analyzer import RustAnalyzerClient

# Simply create and use the client - inlay hints and other features are enabled by default
async with RustAnalyzerClient() as client:
hints = await client.request_inlay_hint(file_path="main.rs", range=...)
# Inlay hints are automatically enabled without any configuration!
```

### Customizing Default Configuration

You can still provide your own configuration to override or extend the defaults:

```python
from lsp_client.utils.config import ConfigurationMap
from lsp_client.clients.pyright import PyrightClient

# Create custom configuration
config_map = ConfigurationMap()
config_map.update_global({
"python": {
"analysis": {
"typeCheckingMode": "strict", # Override default
"autoImportCompletions": False, # Disable a feature
}
}
})

# Apply custom configuration
async with PyrightClient() as client:
client.configuration_map = config_map
# Now uses your custom settings instead of defaults
```

## ConfigurationMap

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.
Expand Down Expand Up @@ -104,3 +150,66 @@ When a file matches multiple scope patterns, configurations are merged in the or

### Path Resolution
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.

## Default Configurations by Client

Each built-in client provides default configurations tailored to the language server's capabilities:

### Rust Analyzer
- Inlay hints: Types, parameters, lifetimes, closures, reborrow hints
- Diagnostics: Enabled with experimental features
- Completion: Auto-import, auto-self, callable snippets
- Check on save: Enabled
- Code lenses: Run, debug, implementations, references

### Gopls (Go)
- Inlay hints: All types (variables, parameters, function types, etc.)
- Code lenses: Generate, test, tidy, upgrade dependencies, vulnerability checks
- Analyses: Field alignment, nilness, unused params/writes
- Completion: Documentation, deep completion, fuzzy matching, placeholders
- Semantic tokens: Enabled

### Pyright (Python)
- Inlay hints: Variable types, function return types, call arguments, pytest parameters
- Auto-import completions: Enabled
- Type checking mode: Basic (can be overridden)
- Diagnostics: Open files only
- Auto-search paths and indexing: Enabled

### TypeScript Language Server
- Inlay hints: All types for both TypeScript and JavaScript
- Suggestions: Auto-imports, complete function calls, module exports
- Preferences: Package.json auto-imports, shortest import specifier

### Deno
- Inlay hints: All parameter and type hints
- Linting: Enabled
- Unstable features: Enabled
- Code lenses: Implementations, references, tests
- Import suggestions: Auto-discover from deno.land and esm.sh

### Pyrefly (Python)
- Inlay hints: Variable types, function return types, parameter types
- Diagnostics and auto-imports: Enabled

### Ty (Python)
- Diagnostics and auto-imports: Enabled

You can always inspect the default configuration for any client:

```python
from lsp_client.clients.rust_analyzer import RustAnalyzerClient

client = RustAnalyzerClient()
config_map = client.create_default_configuration_map()

# Check if configuration exists
if config_map and config_map.has_global_config():
# Get specific configuration values
inlay_hints_enabled = config_map.get(None, "rust-analyzer.inlayHints.enable")
print(f"Inlay hints enabled: {inlay_hints_enabled}")

# Get entire section
all_inlay_hints = config_map.get(None, "rust-analyzer.inlayHints")
print(f"All inlay hint settings: {all_inlay_hints}")
```
122 changes: 122 additions & 0 deletions examples/default_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""
Example demonstrating default configurations for LSP clients.

This example shows how the new default configurations work:
1. All clients automatically have sensible defaults
2. Inlay hints and other features are enabled out of the box
3. Users can still override defaults if needed
"""

from __future__ import annotations

from lsp_client.clients.pyright import PyrightClient
from lsp_client.clients.rust_analyzer import RustAnalyzerClient
from lsp_client.utils.config import ConfigurationMap


def example_using_defaults():
"""Example: Using default configuration (no setup required)"""
print("Example 1: Using Default Configuration")
print("-" * 50)

# Create a client - it automatically has default configuration
client = PyrightClient()

# Get the default configuration that was created
config_map = client.create_default_configuration_map()

if config_map:
print("✓ Default configuration is available!")

# Check what inlay hints are enabled
inlay_hints = config_map.get(None, "python.analysis.inlayHints")
print(f" Inlay hints configuration: {inlay_hints}")

# Check type checking mode
type_checking = config_map.get(None, "python.analysis.typeCheckingMode")
print(f" Type checking mode: {type_checking}")

# Check auto-import completions
auto_imports = config_map.get(None, "python.analysis.autoImportCompletions")
print(f" Auto-import completions: {auto_imports}")

print()


def example_with_custom_override():
"""Example: Overriding default configuration"""
print("Example 2: Overriding Default Configuration")
print("-" * 50)

# Create a client
client = RustAnalyzerClient()

# Get defaults - demonstrating that defaults exist
_ = client.create_default_configuration_map()
print("✓ Default configuration created")

# Create custom configuration that overrides some defaults
custom_config = ConfigurationMap()
custom_config.update_global(
{
"rust-analyzer": {
"inlayHints": {
"enable": False, # Disable inlay hints
},
"checkOnSave": {"enable": False}, # Disable check on save
}
}
)

print("✓ Custom configuration created")
print(" - Inlay hints: disabled")
print(" - Check on save: disabled")

# You would set this on the client:
# client.configuration_map = custom_config

print()


def example_inspecting_defaults():
"""Example: Inspecting default configurations for all clients"""
print("Example 3: Inspecting Default Configurations")
print("-" * 50)

from lsp_client.clients import clients

for client_cls in clients:
client = client_cls()
config_map = client.create_default_configuration_map()

if config_map and config_map.has_global_config():
print(f"✓ {client_cls.__name__} has default configuration")

# Try to find inlay hints config
for section in [
"rust-analyzer.inlayHints",
"gopls.hints",
"python.analysis.inlayHints",
"typescript.inlayHints",
"deno.inlayHints",
"pyrefly.inlayHints",
]:
Comment on lines 97 to 105
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inlay hints section list in the example is missing "ty.inlayHints". However, upon checking the ty.py implementation, TyClient does not configure inlayHints - it only configures diagnostics and completion. While this is technically correct, it would be helpful to add a comment or include "ty.diagnostics" or "ty.completion" in the list to demonstrate that TyClient is also being checked, so it's clear to readers that all clients are covered.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added "ty.diagnostics" to the configuration list with a comment explaining that TyClient uses diagnostics instead of inlay hints. This makes it clear all clients are covered. (661ad01)

value = config_map.get(None, section)
if value is not None:
print(f" → {section} is configured")
break

print()


if __name__ == "__main__":
print("LSP Client Default Configuration Examples")
print("=" * 50)
print()

example_using_defaults()
example_with_custom_override()
example_inspecting_defaults()

print("=" * 50)
print("All examples completed successfully!")
45 changes: 45 additions & 0 deletions src/lsp_client/client/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from lsp_client.server import DefaultServers, Server, ServerRuntimeError
from lsp_client.server.types import ServerRequest
from lsp_client.utils.channel import Receiver, channel
from lsp_client.utils.config import ConfigurationMap
from lsp_client.utils.types import AnyPath, Notification, Request, Response, lsp_type
from lsp_client.utils.workspace import (
DEFAULT_WORKSPACE_DIR,
Expand Down Expand Up @@ -109,6 +110,38 @@ def create_default_servers(self) -> DefaultServers:
def check_server_compatibility(self, info: lsp_type.ServerInfo | None) -> None:
"""Check if the available server capabilities are compatible with the client."""

def create_default_configuration_map(self) -> ConfigurationMap | None:
"""
Create default configuration map for this client.

This method can be overridden by subclasses to provide default configurations
that enable extra features like inlay hints, diagnostics, etc.

The base implementation returns None, meaning no default configuration.
Subclasses that support configuration should override this method to provide
sensible defaults that enable commonly-used features.

Returns:
ConfigurationMap with default settings, or None if no defaults are needed.

Example:
Override this method in a client subclass to provide defaults:

```python
@override
def create_default_configuration_map(self) -> ConfigurationMap | None:
config_map = ConfigurationMap()
config_map.update_global({
"myserver": {
"inlayHints": {"enable": True},
"diagnostics": {"enable": True},
}
})
return config_map
```
"""
return None

@override
@asynccontextmanager
async def open_files(self, *file_paths: AnyPath) -> AsyncGenerator[None]:
Expand Down Expand Up @@ -246,6 +279,18 @@ async def _run_server(
async def __asynccontextmanager__(self) -> AsyncGenerator[Self]:
self._workspace = format_workspace(self._workspace_arg)

# Initialize default configuration map if the client supports configuration
# and no configuration map has been set yet
from lsp_client.capability.server_request import WithRespondConfigurationRequest

if (
isinstance(self, WithRespondConfigurationRequest)
and self.configuration_map is None
):
default_config = self.create_default_configuration_map()
if default_config is not None:
self.configuration_map = default_config

async with (
asyncer.create_task_group() as tg,
self._run_server() as (server, receiver), # ty: ignore[invalid-argument-type]
Expand Down
50 changes: 50 additions & 0 deletions src/lsp_client/clients/deno/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from lsp_client.server import DefaultServers, ServerInstallationError
from lsp_client.server.container import ContainerServer
from lsp_client.server.local import LocalServer
from lsp_client.utils.config import ConfigurationMap
from lsp_client.utils.types import lsp_type

from .extension import (
Expand Down Expand Up @@ -155,3 +156,52 @@ def create_default_servers(self) -> DefaultServers:
@override
def check_server_compatibility(self, info: lsp_type.ServerInfo | None) -> None:
return

@override
def create_default_configuration_map(self) -> ConfigurationMap | None:
"""Create default configuration for deno with all features enabled."""
config_map = ConfigurationMap()
config_map.update_global(
{
"deno": {
# Enable deno
"enable": True,
# Enable inlay hints
"inlayHints": {
"parameterNames": {"enabled": "all"},
"parameterTypes": {"enabled": True},
"variableTypes": {"enabled": True},
"propertyDeclarationTypes": {"enabled": True},
"functionLikeReturnTypes": {"enabled": True},
"enumMemberValues": {"enabled": True},
},
# Enable linting
"lint": True,
# Enable unstable features
"unstable": True,
# Enable code lens
"codeLens": {
"implementations": True,
"references": True,
"referencesAllFunctions": True,
"test": True,
"testArgs": ["--allow-all"],
},
# Enable suggestions
"suggest": {
"autoImports": True,
"completeFunctionCalls": True,
"names": True,
"paths": True,
"imports": {
"autoDiscover": True,
"hosts": {
"https://deno.land": True,
"https://esm.sh": True,
},
},
},
}
}
)
return config_map
Loading