diff --git a/docs/configuration.md b/docs/configuration.md index b114211..ac24b8c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -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. @@ -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}") +``` diff --git a/examples/default_configuration.py b/examples/default_configuration.py new file mode 100644 index 0000000..c82dbfc --- /dev/null +++ b/examples/default_configuration.py @@ -0,0 +1,124 @@ +""" +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 configuration - inlay hints for most clients, + # or other features like diagnostics for clients without inlay hints + for section in [ + "rust-analyzer.inlayHints", + "gopls.hints", + "python.analysis.inlayHints", + "typescript.inlayHints", + "deno.inlayHints", + "pyrefly.inlayHints", + "ty.diagnostics", # TyClient uses diagnostics instead of inlay hints + ]: + 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!") diff --git a/src/lsp_client/client/abc.py b/src/lsp_client/client/abc.py index 16a4965..ef3b2ab 100644 --- a/src/lsp_client/client/abc.py +++ b/src/lsp_client/client/abc.py @@ -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, @@ -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]: @@ -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] diff --git a/src/lsp_client/clients/deno/client.py b/src/lsp_client/clients/deno/client.py index 4fc2445..fc07131 100644 --- a/src/lsp_client/clients/deno/client.py +++ b/src/lsp_client/clients/deno/client.py @@ -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 ( @@ -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 diff --git a/src/lsp_client/clients/gopls.py b/src/lsp_client/clients/gopls.py index 8272fc2..6dde108 100644 --- a/src/lsp_client/clients/gopls.py +++ b/src/lsp_client/clients/gopls.py @@ -41,6 +41,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 GoplsContainerServer = partial(ContainerServer, image="ghcr.io/lsp-client/gopls:latest") @@ -115,3 +116,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 gopls with all features enabled.""" + config_map = ConfigurationMap() + config_map.update_global( + { + "gopls": { + # Enable inlay hints + "hints": { + "assignVariableTypes": True, + "compositeLiteralFields": True, + "compositeLiteralTypes": True, + "constantValues": True, + "functionTypeParameters": True, + "parameterNames": True, + "rangeVariableTypes": True, + }, + # Enable code lenses + "codelenses": { + "gc_details": True, + "generate": True, + "regenerate_cgo": True, + "run_govulncheck": True, + "test": True, + "tidy": True, + "upgrade_dependency": True, + "vendor": True, + }, + # Enable diagnostics + "diagnosticsDelay": "250ms", + "analyses": { + "fieldalignment": True, + "nilness": True, + "unusedparams": True, + "unusedwrite": True, + "useany": True, + }, + # Enable completion features + "completionDocumentation": True, + "deepCompletion": True, + "matcher": "Fuzzy", + "usePlaceholders": True, + # Enable semantic tokens + "semanticTokens": True, + } + } + ) + return config_map diff --git a/src/lsp_client/clients/pyrefly.py b/src/lsp_client/clients/pyrefly.py index 2dd8a11..5b4f140 100644 --- a/src/lsp_client/clients/pyrefly.py +++ b/src/lsp_client/clients/pyrefly.py @@ -43,6 +43,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 PyreflyContainerServer = partial( @@ -122,3 +123,29 @@ 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 pyrefly with all features enabled.""" + config_map = ConfigurationMap() + config_map.update_global( + { + "pyrefly": { + # Enable inlay hints + "inlayHints": { + "variableTypes": True, + "functionReturnTypes": True, + "parameterTypes": True, + }, + # Enable diagnostics + "diagnostics": { + "enable": True, + }, + # Enable auto-imports + "completion": { + "autoImports": True, + }, + } + } + ) + return config_map diff --git a/src/lsp_client/clients/pyright.py b/src/lsp_client/clients/pyright.py index bc3279b..c2874ee 100644 --- a/src/lsp_client/clients/pyright.py +++ b/src/lsp_client/clients/pyright.py @@ -41,6 +41,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 PyrightContainerServer = partial( @@ -115,3 +116,38 @@ 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 pyright with all features enabled.""" + config_map = ConfigurationMap() + config_map.update_global( + { + "python": { + "analysis": { + # Enable inlay hints + "inlayHints": { + "variableTypes": True, + "functionReturnTypes": True, + "callArgumentNames": True, + "pytestParameters": True, + }, + # Enable auto-import completions + "autoImportCompletions": True, + # Enable type checking (default: basic) + "typeCheckingMode": "basic", + # Enable diagnostics + "diagnosticMode": "openFilesOnly", + # Enable auto-search paths + "autoSearchPaths": True, + # Enable indexing + "indexing": True, + # Enable diagnostics for shadowed imports + "diagnosticSeverityOverrides": { + "reportGeneralTypeIssues": "warning", + }, + } + } + } + ) + return config_map diff --git a/src/lsp_client/clients/rust_analyzer.py b/src/lsp_client/clients/rust_analyzer.py index 36408b2..ed97c42 100644 --- a/src/lsp_client/clients/rust_analyzer.py +++ b/src/lsp_client/clients/rust_analyzer.py @@ -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 RustAnalyzerContainerServer = partial( @@ -120,3 +121,54 @@ 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 rust-analyzer with all features enabled.""" + config_map = ConfigurationMap() + config_map.update_global( + { + "rust-analyzer": { + # Enable inlay hints for all types + "inlayHints": { + "enable": True, + "chainingHints": {"enable": True}, + "closureReturnTypeHints": {"enable": "always"}, + "lifetimeElisionHints": {"enable": "always"}, + "parameterHints": {"enable": True}, + "reborrowHints": {"enable": "always"}, + "renderColons": True, + "typeHints": {"enable": True}, + }, + # Enable diagnostics + "diagnostics": { + "enable": True, + "experimental": {"enable": True}, + }, + # Enable completion features + "completion": { + "autoimport": {"enable": True}, + "autoself": {"enable": True}, + "callable": {"snippets": "fill_arguments"}, + "postfix": {"enable": True}, + "privateEditable": {"enable": True}, + }, + # Enable checkOnSave with cargo check + "checkOnSave": {"enable": True}, + # Enable code lens + "lens": { + "enable": True, + "run": {"enable": True}, + "debug": {"enable": True}, + "implementations": {"enable": True}, + "references": { + "adt": {"enable": True}, + "enumVariant": {"enable": True}, + "method": {"enable": True}, + "trait": {"enable": True}, + }, + }, + } + } + ) + return config_map diff --git a/src/lsp_client/clients/ty.py b/src/lsp_client/clients/ty.py index 58900d1..3adf493 100644 --- a/src/lsp_client/clients/ty.py +++ b/src/lsp_client/clients/ty.py @@ -40,6 +40,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 TyContainerServer = partial(ContainerServer, image="ghcr.io/lsp-client/ty:latest") @@ -110,3 +111,23 @@ 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 ty with all features enabled.""" + config_map = ConfigurationMap() + config_map.update_global( + { + "ty": { + # Enable diagnostics + "diagnostics": { + "enable": True, + }, + # Enable completion features + "completion": { + "autoImports": True, + }, + } + } + ) + return config_map diff --git a/src/lsp_client/clients/typescript.py b/src/lsp_client/clients/typescript.py index 72b1ad3..fdfe16b 100644 --- a/src/lsp_client/clients/typescript.py +++ b/src/lsp_client/clients/typescript.py @@ -41,6 +41,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 TypescriptContainerServer = partial( @@ -121,3 +122,61 @@ 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 typescript-language-server with all features enabled.""" + config_map = ConfigurationMap() + config_map.update_global( + { + "typescript": { + # Enable inlay hints for TypeScript + "inlayHints": { + "includeInlayParameterNameHints": "all", + "includeInlayParameterNameHintsWhenArgumentMatchesName": True, + "includeInlayFunctionParameterTypeHints": True, + "includeInlayVariableTypeHints": True, + "includeInlayVariableTypeHintsWhenTypeMatchesName": True, + "includeInlayPropertyDeclarationTypeHints": True, + "includeInlayFunctionLikeReturnTypeHints": True, + "includeInlayEnumMemberValueHints": True, + }, + # Enable suggestions + "suggest": { + "autoImports": True, + "completeFunctionCalls": True, + "includeCompletionsForModuleExports": True, + }, + # Enable preferences + "preferences": { + "includePackageJsonAutoImports": "on", + "importModuleSpecifier": "shortest", + }, + }, + "javascript": { + # Enable inlay hints for JavaScript + "inlayHints": { + "includeInlayParameterNameHints": "all", + "includeInlayParameterNameHintsWhenArgumentMatchesName": True, + "includeInlayFunctionParameterTypeHints": True, + "includeInlayVariableTypeHints": True, + "includeInlayVariableTypeHintsWhenTypeMatchesName": True, + "includeInlayPropertyDeclarationTypeHints": True, + "includeInlayFunctionLikeReturnTypeHints": True, + "includeInlayEnumMemberValueHints": True, + }, + # Enable suggestions + "suggest": { + "autoImports": True, + "completeFunctionCalls": True, + "includeCompletionsForModuleExports": True, + }, + # Enable preferences + "preferences": { + "includePackageJsonAutoImports": "on", + "importModuleSpecifier": "shortest", + }, + }, + } + ) + return config_map diff --git a/src/lsp_client/utils/config.py b/src/lsp_client/utils/config.py index 4797fa5..9c4527c 100644 --- a/src/lsp_client/utils/config.py +++ b/src/lsp_client/utils/config.py @@ -105,3 +105,12 @@ def get(self, scope_uri: str | None, section: str | None) -> Any: logger.warning(f"Failed to parse scope URI: {scope_uri}") return self._get_section(final_config, section) + + def has_global_config(self) -> bool: + """ + Check if the configuration map has any global configuration. + + Returns: + True if global configuration is not empty, False otherwise. + """ + return bool(self._global_config) diff --git a/tests/test_default_configuration.py b/tests/test_default_configuration.py new file mode 100644 index 0000000..24f6cf2 --- /dev/null +++ b/tests/test_default_configuration.py @@ -0,0 +1,174 @@ +from __future__ import annotations + +import pytest + +from lsp_client.capability.server_request import WithRespondConfigurationRequest +from lsp_client.clients import clients +from lsp_client.utils.config import ConfigurationMap + + +@pytest.mark.parametrize("client_cls", clients) +def test_clients_have_default_configuration_method(client_cls): + """Test that all clients have a create_default_configuration_map method.""" + assert hasattr(client_cls, "create_default_configuration_map"), ( + f"{client_cls.__name__} should have create_default_configuration_map method" + ) + + +@pytest.mark.parametrize("client_cls", clients) +def test_default_configuration_returns_valid_type(client_cls): + """Test that create_default_configuration_map returns None or ConfigurationMap.""" + client = client_cls() + result = client.create_default_configuration_map() + assert result is None or isinstance(result, ConfigurationMap), ( + f"{client_cls.__name__}.create_default_configuration_map() should return None or ConfigurationMap" + ) + + +@pytest.mark.parametrize("client_cls", clients) +def test_clients_with_configuration_support_have_defaults(client_cls): + """Test that clients supporting configuration have default configuration maps.""" + client = client_cls() + + # Check if client supports configuration request + if not isinstance(client, WithRespondConfigurationRequest): + pytest.skip( + f"{client_cls.__name__} does not support WithRespondConfigurationRequest" + ) + + # Get default configuration + config_map = client.create_default_configuration_map() + + # Clients with configuration support should have defaults + # (especially those with inlay hints, diagnostics, etc.) + assert config_map is not None, ( + f"{client_cls.__name__} supports configuration requests " + f"and should provide default configuration" + ) + + # Verify it's a proper ConfigurationMap instance + assert isinstance(config_map, ConfigurationMap) + + +@pytest.mark.parametrize("client_cls", clients) +def test_default_configuration_has_content(client_cls): + """Test that default configurations contain actual settings.""" + client = client_cls() + config_map = client.create_default_configuration_map() + + if config_map is None: + pytest.skip(f"{client_cls.__name__} has no default configuration") + + # Check that the configuration map has some content using public API + assert config_map.has_global_config(), ( + f"{client_cls.__name__} default configuration should not be empty" + ) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("client_cls", clients) +async def test_configuration_initialized_on_client_startup(client_cls): + """Test that configuration is automatically initialized when client starts.""" + # This test would require actually starting the language server + # For now, we just verify the client can be instantiated + client = client_cls() + + # Check if client supports configuration + if isinstance(client, WithRespondConfigurationRequest): + # Before async context, configuration_map should be None + assert client.configuration_map is None + + # Note: Full integration test would require: + # async with client as c: + # if isinstance(c, WithRespondConfigurationRequest): + # assert c.configuration_map is not None + # But this requires language servers to be installed + + +def test_rust_analyzer_default_config_has_inlay_hints(): + """Test that rust-analyzer default config enables inlay hints.""" + from lsp_client.clients.rust_analyzer import RustAnalyzerClient + + client = RustAnalyzerClient() + config_map = client.create_default_configuration_map() + + assert config_map is not None + config = config_map.get(None, "rust-analyzer.inlayHints.enable") + assert config is True, "rust-analyzer should enable inlay hints by default" + + +def test_gopls_default_config_has_hints(): + """Test that gopls default config enables hints.""" + from lsp_client.clients.gopls import GoplsClient + + client = GoplsClient() + config_map = client.create_default_configuration_map() + + assert config_map is not None + config = config_map.get(None, "gopls.hints") + assert config is not None, "gopls should have hints configuration" + assert isinstance(config, dict), "gopls hints should be a dict" + + +def test_pyright_default_config_has_inlay_hints(): + """Test that pyright default config enables inlay hints.""" + from lsp_client.clients.pyright import PyrightClient + + client = PyrightClient() + config_map = client.create_default_configuration_map() + + assert config_map is not None + config = config_map.get(None, "python.analysis.inlayHints") + assert config is not None, "pyright should have inlay hints configuration" + assert isinstance(config, dict), "pyright inlay hints should be a dict" + + +def test_typescript_default_config_has_inlay_hints(): + """Test that typescript-language-server default config enables inlay hints.""" + from lsp_client.clients.typescript import TypescriptClient + + client = TypescriptClient() + config_map = client.create_default_configuration_map() + + assert config_map is not None + config = config_map.get(None, "typescript.inlayHints") + assert config is not None, "typescript should have inlay hints configuration" + + +def test_deno_default_config_has_inlay_hints(): + """Test that deno default config enables inlay hints.""" + from lsp_client.clients.deno import DenoClient + + client = DenoClient() + config_map = client.create_default_configuration_map() + + assert config_map is not None + config = config_map.get(None, "deno.inlayHints") + assert config is not None, "deno should have inlay hints configuration" + + +def test_pyrefly_default_config_has_inlay_hints(): + """Test that pyrefly default config enables inlay hints.""" + from lsp_client.clients.pyrefly import PyreflyClient + + client = PyreflyClient() + config_map = client.create_default_configuration_map() + + assert config_map is not None + config = config_map.get(None, "pyrefly.inlayHints") + assert config is not None, "pyrefly should have inlay hints configuration" + assert isinstance(config, dict), "pyrefly inlay hints should be a dict" + + +def test_ty_default_config_has_diagnostics(): + """Test that ty default config enables diagnostics and completion.""" + from lsp_client.clients.ty import TyClient + + client = TyClient() + config_map = client.create_default_configuration_map() + + assert config_map is not None + diagnostics = config_map.get(None, "ty.diagnostics") + assert diagnostics is not None, "ty should have diagnostics configuration" + completion = config_map.get(None, "ty.completion") + assert completion is not None, "ty should have completion configuration"