diff --git a/modules/programs/claude-code.nix b/modules/programs/claude-code.nix index 49d2940a..ceadba99 100644 --- a/modules/programs/claude-code.nix +++ b/modules/programs/claude-code.nix @@ -364,6 +364,33 @@ in example = lib.literalExpression "./skills"; }; + lspServers = lib.mkOption { + type = lib.types.attrsOf jsonFormat.type; + default = { }; + description = '' + LSP (Language Server Protocol) servers configuration. + ''; + example = { + go = { + command = "gopls"; + args = [ "serve" ]; + extensionToLanguage = { + ".go" = "go"; + }; + }; + typescript = { + command = "typescript-language-server"; + args = [ "--stdio" ]; + extensionToLanguage = { + ".ts" = "typescript"; + ".tsx" = "typescriptreact"; + ".js" = "javascript"; + ".jsx" = "javascriptreact"; + }; + }; + }; + }; + mcpServers = lib.mkOption { type = lib.types.attrsOf jsonFormat.type; default = { }; @@ -408,8 +435,10 @@ in config = lib.mkIf cfg.enable { assertions = [ { - assertion = (cfg.mcpServers == { } && !cfg.enableMcpIntegration) || cfg.package != null; - message = "`programs.claude-code.package` cannot be null when `mcpServers` or `enableMcpIntegration` is configured"; + assertion = + (cfg.mcpServers == { } && cfg.lspServers == { } && !cfg.enableMcpIntegration) + || cfg.package != null; + message = "`programs.claude-code.package` cannot be null when `mcpServers`, `lspServers`, or `enableMcpIntegration` is configured"; } { assertion = !(cfg.memory.text != null && cfg.memory.source != null); @@ -448,6 +477,19 @@ in jsonFormat.generate "claude-code-mcp-config.json" { mcpServers = mergedMcpServers; } }" ]) + (lib.optional (cfg.lspServers != { }) [ + "--append-flags" + "--plugin-dir ${ + pkgs.runCommand "claude-code-lsp-plugin" { } '' + install -Dm644 ${ + jsonFormat.generate "claude-code-lsp-plugin.json" { + name = "claude-code-lsp"; + lspServers = cfg.lspServers; + } + } $out/.claude-plugin/plugin.json + '' + }" + ]) ] ); diff --git a/tests/modules/programs/claude-code/assertion.nix b/tests/modules/programs/claude-code/assertion.nix index 8ce468ef..8cdf5be9 100644 --- a/tests/modules/programs/claude-code/assertion.nix +++ b/tests/modules/programs/claude-code/assertion.nix @@ -47,7 +47,7 @@ }; test.asserts.assertions.expected = [ - "`programs.claude-code.package` cannot be null when `mcpServers` or `enableMcpIntegration` is configured" + "`programs.claude-code.package` cannot be null when `mcpServers`, `lspServers`, or `enableMcpIntegration` is configured" "Cannot specify both `programs.claude-code.memory.text` and `programs.claude-code.memory.source`" "Cannot specify both `programs.claude-code.agents` and `programs.claude-code.agentsDir`" "Cannot specify both `programs.claude-code.commands` and `programs.claude-code.commandsDir`" diff --git a/tests/modules/programs/claude-code/default.nix b/tests/modules/programs/claude-code/default.nix index 7f2828d2..acfaf6ef 100644 --- a/tests/modules/programs/claude-code/default.nix +++ b/tests/modules/programs/claude-code/default.nix @@ -1,6 +1,7 @@ { claude-code-basic = ./basic.nix; claude-code-full-config = ./full-config.nix; + claude-code-lsp = ./lsp.nix; claude-code-mcp = ./mcp.nix; claude-code-mcp-integration = ./mcp-integration.nix; claude-code-assertion = ./assertion.nix; diff --git a/tests/modules/programs/claude-code/expected-lsp-wrapper b/tests/modules/programs/claude-code/expected-lsp-wrapper new file mode 100644 index 00000000..aaadf0c3 --- /dev/null +++ b/tests/modules/programs/claude-code/expected-lsp-wrapper @@ -0,0 +1,2 @@ +#! /nix/store/00000000000000000000000000000000-bash/bin/bash -e +exec -a "$0" "/nix/store/00000000000000000000000000000000-claude-code/bin/.claude-wrapped" "$@" --plugin-dir /nix/store/00000000000000000000000000000000-claude-code-lsp-plugin diff --git a/tests/modules/programs/claude-code/lsp.nix b/tests/modules/programs/claude-code/lsp.nix new file mode 100644 index 00000000..f225a40e --- /dev/null +++ b/tests/modules/programs/claude-code/lsp.nix @@ -0,0 +1,38 @@ +{ config, ... }: + +{ + programs.claude-code = { + package = config.lib.test.mkStubPackage { + name = "claude-code"; + buildScript = '' + mkdir -p $out/bin + touch $out/bin/claude + chmod 755 $out/bin/claude + ''; + }; + enable = true; + + lspServers = { + go = { + command = "gopls"; + args = [ "serve" ]; + extensionToLanguage = { + ".go" = "go"; + }; + }; + typescript = { + command = "typescript-language-server"; + args = [ "--stdio" ]; + extensionToLanguage = { + ".ts" = "typescript"; + ".tsx" = "typescriptreact"; + }; + }; + }; + }; + + nmt.script = '' + normalizedWrapper=$(normalizeStorePaths home-path/bin/claude) + assertFileContent $normalizedWrapper ${./expected-lsp-wrapper} + ''; +}