From 1f34c2c855751c74b01df5068cf4dd64ea2c7d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Yves=20Landur=C3=A9?= Date: Fri, 31 Oct 2025 12:01:41 +0100 Subject: [PATCH 1/9] zed-editor: options to generate debug.json Add `mutableUserDebug` and `userDebug` options to generate `debug.json` file. The options are heavily inspired by `mutableUserTasks` and `userTasks` options implementation. Closes #8091 --- modules/programs/zed-editor.nix | 41 +++++++ .../programs/zed-editor/debug-empty.nix | 83 +++++++++++++++ .../programs/zed-editor/debug-immutable.nix | 58 ++++++++++ tests/modules/programs/zed-editor/debug.nix | 100 ++++++++++++++++++ tests/modules/programs/zed-editor/default.nix | 3 + 5 files changed, 285 insertions(+) create mode 100644 tests/modules/programs/zed-editor/debug-empty.nix create mode 100644 tests/modules/programs/zed-editor/debug-immutable.nix create mode 100644 tests/modules/programs/zed-editor/debug.nix diff --git a/modules/programs/zed-editor.nix b/modules/programs/zed-editor.nix index 5b6fc4ff3..d95f59e88 100644 --- a/modules/programs/zed-editor.nix +++ b/modules/programs/zed-editor.nix @@ -81,6 +81,15 @@ in ''; }; + mutableUserDebug = mkOption { + type = types.bool; + default = true; + example = false; + description = '' + Whether user debug configurations (debug.json) can be updated by zed. + ''; + }; + userSettings = mkOption { type = jsonFormat.type; default = { }; @@ -140,6 +149,27 @@ in ''; }; + userDebug = mkOption { + type = jsonFormat.type; + default = [ ]; + example = literalExpression '' + [ + { + label = "Go (Delve)"; + adapter = "Delve"; + program = "$ZED_FILE"; + request = "launch"; + mode = "debug"; + } + ] + ''; + description = '' + Configuration written to Zed's {file}`debug.json`. + + Global debug configurations for Zed's [Debugger](https://zed.dev/docs/debugger). + ''; + }; + extensions = mkOption { type = types.listOf types.str; default = [ ]; @@ -241,6 +271,14 @@ in (jsonFormat.generate "zed-user-tasks" cfg.userTasks) ); }) + (mkIf (cfg.mutableUserDebug && cfg.userDebug != [ ]) { + zedDebugActivation = lib.hm.dag.entryAfter [ "linkGeneration" ] ( + impureConfigMerger "[]" + "$dynamic + $static | group_by(.label) | map(reduce .[] as $item ({}; . * $item))" + "${config.xdg.configHome}/zed/debug.json" + (jsonFormat.generate "zed-user-debug" cfg.userDebug) + ); + }) ]; xdg.configFile = mkMerge [ @@ -265,6 +303,9 @@ in (mkIf (!cfg.mutableUserTasks && cfg.userTasks != [ ]) { "zed/tasks.json".source = jsonFormat.generate "zed-user-tasks" cfg.userTasks; }) + (mkIf (!cfg.mutableUserDebug && cfg.userDebug != [ ]) { + "zed/debug.json".source = jsonFormat.generate "zed-user-debug" cfg.userDebug; + }) ]; assertions = [ diff --git a/tests/modules/programs/zed-editor/debug-empty.nix b/tests/modules/programs/zed-editor/debug-empty.nix new file mode 100644 index 000000000..eed8e7354 --- /dev/null +++ b/tests/modules/programs/zed-editor/debug-empty.nix @@ -0,0 +1,83 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + programs.zed-editor = { + enable = true; + package = config.lib.test.mkStubPackage { }; + userDebug = [ + { + label = "PHP: Listen to Xdebug"; + adapter = "Xdebug"; + request = "launch"; + port = 9003; + } + { + label = "PHP: Debug this test"; + adapter = "Xdebug"; + request = "launch"; + program = "vendor/bin/phpunit"; + args = [ + "--filter" + "$ZED_SYMBOL" + ]; + } + ]; + }; + + home.homeDirectory = lib.mkForce "/@TMPDIR@/hm-user"; + + nmt.script = + let + preexistingDebug = builtins.toFile "preexisting.json" ""; + + expectedContent = builtins.toFile "expected.json" '' + [ + { + "adapter": "Xdebug", + "args": [ + "--filter", + "$ZED_SYMBOL" + ], + "label": "PHP: Debug this test", + "program": "vendor/bin/phpunit", + "request": "launch" + }, + { + "adapter": "Xdebug", + "label": "PHP: Listen to Xdebug", + "port": 9003, + "request": "launch" + } + ] + ''; + + debugPath = ".config/zed/debug.json"; + activationScript = pkgs.writeScript "activation" config.home.activation.zedDebugActivation.data; + in + '' + export HOME=$TMPDIR/hm-user + + # Simulate preexisting debug + mkdir -p $HOME/.config/zed + cat ${preexistingDebug} > $HOME/${debugPath} + + # Run the activation script + substitute ${activationScript} $TMPDIR/activate --subst-var TMPDIR + chmod +x $TMPDIR/activate + $TMPDIR/activate + + # Validate the merged debug + assertFileExists "$HOME/${debugPath}" + assertFileContent "$HOME/${debugPath}" "${expectedContent}" + + # Test idempotency + $TMPDIR/activate + assertFileExists "$HOME/${debugPath}" + assertFileContent "$HOME/${debugPath}" "${expectedContent}" + ''; +} diff --git a/tests/modules/programs/zed-editor/debug-immutable.nix b/tests/modules/programs/zed-editor/debug-immutable.nix new file mode 100644 index 000000000..f433d9128 --- /dev/null +++ b/tests/modules/programs/zed-editor/debug-immutable.nix @@ -0,0 +1,58 @@ +# Test custom keymap functionality +{ config, ... }: + +{ + programs.zed-editor = { + enable = true; + package = config.lib.test.mkStubPackage { }; + mutableUserDebug = false; + userDebug = [ + { + label = "PHP: Listen to Xdebug"; + adapter = "Xdebug"; + request = "launch"; + port = 9003; + } + { + label = "PHP: Debug this test"; + adapter = "Xdebug"; + request = "launch"; + program = "vendor/bin/phpunit"; + args = [ + "--filter" + "$ZED_SYMBOL" + ]; + } + ]; + }; + + nmt.script = + let + expectedContent = builtins.toFile "expected.json" '' + [ + { + "adapter": "Xdebug", + "label": "PHP: Listen to Xdebug", + "port": 9003, + "request": "launch" + }, + { + "adapter": "Xdebug", + "args": [ + "--filter", + "$ZED_SYMBOL" + ], + "label": "PHP: Debug this test", + "program": "vendor/bin/phpunit", + "request": "launch" + } + ] + ''; + + settingsPath = ".config/zed/debug.json"; + in + '' + assertFileExists "home-files/${settingsPath}" + assertFileContent "home-files/${settingsPath}" "${expectedContent}" + ''; +} diff --git a/tests/modules/programs/zed-editor/debug.nix b/tests/modules/programs/zed-editor/debug.nix new file mode 100644 index 000000000..b49fb85d3 --- /dev/null +++ b/tests/modules/programs/zed-editor/debug.nix @@ -0,0 +1,100 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + programs.zed-editor = { + enable = true; + package = config.lib.test.mkStubPackage { }; + userDebug = [ + { + label = "PHP: Listen to Xdebug"; + adapter = "Xdebug"; + request = "launch"; + port = 9003; + } + { + label = "PHP: Debug this test"; + adapter = "Xdebug"; + request = "launch"; + program = "vendor/bin/phpunit"; + args = [ + "--filter" + "$ZED_SYMBOL" + ]; + } + ]; + }; + + home.homeDirectory = lib.mkForce "/@TMPDIR@/hm-user"; + + nmt.script = + let + preexistingDebug = builtins.toFile "preexisting.json" '' + [ + { + "label": "Debug active Python file", + "adapter": "Debugpy", + "program": "$ZED_FILE", + "request": "launch", + "cwd": "$ZED_WORKTREE_ROOT" + } + ] + ''; + + expectedContent = builtins.toFile "expected.json" '' + [ + { + "label": "Debug active Python file", + "adapter": "Debugpy", + "program": "$ZED_FILE", + "request": "launch", + "cwd": "$ZED_WORKTREE_ROOT" + }, + { + "adapter": "Xdebug", + "args": [ + "--filter", + "$ZED_SYMBOL" + ], + "label": "PHP: Debug this test", + "program": "vendor/bin/phpunit", + "request": "launch" + }, + { + "adapter": "Xdebug", + "label": "PHP: Listen to Xdebug", + "port": 9003, + "request": "launch" + } + ] + ''; + + debugPath = ".config/zed/debug.json"; + activationScript = pkgs.writeScript "activation" config.home.activation.zedDebugActivation.data; + in + '' + export HOME=$TMPDIR/hm-user + + # Simulate preexisting debug + mkdir -p $HOME/.config/zed + cat ${preexistingDebug} > $HOME/${debugPath} + + # Run the activation script + substitute ${activationScript} $TMPDIR/activate --subst-var TMPDIR + chmod +x $TMPDIR/activate + $TMPDIR/activate + + # Validate the merged debug + assertFileExists "$HOME/${debugPath}" + assertFileContent "$HOME/${debugPath}" "${expectedContent}" + + # Test idempotency + $TMPDIR/activate + assertFileExists "$HOME/${debugPath}" + assertFileContent "$HOME/${debugPath}" "${expectedContent}" + ''; +} diff --git a/tests/modules/programs/zed-editor/default.nix b/tests/modules/programs/zed-editor/default.nix index 1029dba99..b7717320c 100644 --- a/tests/modules/programs/zed-editor/default.nix +++ b/tests/modules/programs/zed-editor/default.nix @@ -11,5 +11,8 @@ zed-tasks = ./tasks.nix; zed-tasks-immutable = ./tasks-immutable.nix; zed-tasks-empty = ./tasks-empty.nix; + zed-debug = ./debug.nix; + zed-debug-immutable = ./debug-immutable.nix; + zed-debug-empty = ./debug-empty.nix; zed-themes = ./themes; } From 083b20c1a01533efe877bfe7198e8ccd7dbf3546 Mon Sep 17 00:00:00 2001 From: Thierry Delafontaine Date: Mon, 27 Oct 2025 12:15:04 +0100 Subject: [PATCH 2/9] mcp: init module --- modules/programs/mcp.nix | 64 ++++++++++++++++++++ tests/modules/programs/mcp/default.nix | 4 ++ tests/modules/programs/mcp/empty-servers.nix | 9 +++ tests/modules/programs/mcp/mcp.json | 17 ++++++ tests/modules/programs/mcp/servers.nix | 25 ++++++++ 5 files changed, 119 insertions(+) create mode 100644 modules/programs/mcp.nix create mode 100644 tests/modules/programs/mcp/default.nix create mode 100644 tests/modules/programs/mcp/empty-servers.nix create mode 100644 tests/modules/programs/mcp/mcp.json create mode 100644 tests/modules/programs/mcp/servers.nix diff --git a/modules/programs/mcp.nix b/modules/programs/mcp.nix new file mode 100644 index 000000000..9fedbdf2e --- /dev/null +++ b/modules/programs/mcp.nix @@ -0,0 +1,64 @@ +{ + config, + lib, + pkgs, + ... +}: +let + inherit (lib) + literalExpression + mkEnableOption + mkIf + mkOption + ; + + cfg = config.programs.mcp; + + jsonFormat = pkgs.formats.json { }; +in +{ + meta.maintainers = with lib.maintainers; [ delafthi ]; + + options.programs.mcp = { + enable = mkEnableOption "mcp"; + + servers = mkOption { + inherit (jsonFormat) type; + default = { }; + example = literalExpression '' + { + everything = { + command = "npx"; + args = [ + "-y" + "@modelcontextprotocol/server-everything" + ]; + }; + context7 = { + url = "https://mcp.context7.com/mcp"; + headers = { + CONTEXT7_API_KEY = "{env:CONTEXT7_API_KEY}"; + }; + }; + } + ''; + description = '' + MCP server configurations written to + {file}`XDG_CONFIG_HOME/.config/mcp/mcp.json` + ''; + }; + }; + + config = mkIf cfg.enable { + xdg.configFile = mkIf (cfg.servers != { }) ( + let + mcp-config = { + mcpServers = cfg.servers; + }; + in + { + "mcp/mcp.json".source = jsonFormat.generate "mcp.json" mcp-config; + } + ); + }; +} diff --git a/tests/modules/programs/mcp/default.nix b/tests/modules/programs/mcp/default.nix new file mode 100644 index 000000000..2a10a18b8 --- /dev/null +++ b/tests/modules/programs/mcp/default.nix @@ -0,0 +1,4 @@ +{ + mcp-servers = ./servers.nix; + mcp-empty-servers = ./empty-servers.nix; +} diff --git a/tests/modules/programs/mcp/empty-servers.nix b/tests/modules/programs/mcp/empty-servers.nix new file mode 100644 index 000000000..63c72c5c0 --- /dev/null +++ b/tests/modules/programs/mcp/empty-servers.nix @@ -0,0 +1,9 @@ +{ + programs.mcp = { + enable = true; + servers = { }; + }; + nmt.script = '' + assertPathNotExists home-files/.config/mcp/mcp.json + ''; +} diff --git a/tests/modules/programs/mcp/mcp.json b/tests/modules/programs/mcp/mcp.json new file mode 100644 index 000000000..9e2571d5e --- /dev/null +++ b/tests/modules/programs/mcp/mcp.json @@ -0,0 +1,17 @@ +{ + "mcpServers": { + "context7": { + "headers": { + "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" + }, + "serverUrl": "https://mcp.context7.com/mcp" + }, + "everything": { + "args": [ + "-y", + "@modelcontextprotocol/server-everything" + ], + "command": "npx" + } + } +} diff --git a/tests/modules/programs/mcp/servers.nix b/tests/modules/programs/mcp/servers.nix new file mode 100644 index 000000000..a63e0f4ae --- /dev/null +++ b/tests/modules/programs/mcp/servers.nix @@ -0,0 +1,25 @@ +{ + programs.mcp = { + enable = true; + servers = { + everything = { + command = "npx"; + args = [ + "-y" + "@modelcontextprotocol/server-everything" + ]; + }; + context7 = { + serverUrl = "https://mcp.context7.com/mcp"; + headers = { + CONTEXT7_API_KEY = "{env:CONTEXT7_API_KEY}"; + }; + }; + }; + }; + nmt.script = '' + assertFileExists home-files/.config/mcp/mcp.json + assertFileContent home-files/.config/mcp/mcp.json \ + ${./mcp.json} + ''; +} From c7403518704e2487b4deaa910628531aa91a66ee Mon Sep 17 00:00:00 2001 From: Thierry Delafontaine Date: Mon, 27 Oct 2025 12:15:04 +0100 Subject: [PATCH 3/9] opencode: add mcp module integration --- modules/programs/opencode.nix | 67 ++++++++++++++++--- tests/modules/programs/opencode/default.nix | 2 + .../mcp-integration-with-override.json | 27 ++++++++ .../mcp-integration-with-override.nix | 48 +++++++++++++ .../programs/opencode/mcp-integration.json | 30 +++++++++ .../programs/opencode/mcp-integration.nix | 36 ++++++++++ 6 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 tests/modules/programs/opencode/mcp-integration-with-override.json create mode 100644 tests/modules/programs/opencode/mcp-integration-with-override.nix create mode 100644 tests/modules/programs/opencode/mcp-integration.json create mode 100644 tests/modules/programs/opencode/mcp-integration.nix diff --git a/modules/programs/opencode.nix b/modules/programs/opencode.nix index 538cff2a3..8e74ca872 100644 --- a/modules/programs/opencode.nix +++ b/modules/programs/opencode.nix @@ -16,6 +16,35 @@ let cfg = config.programs.opencode; jsonFormat = pkgs.formats.json { }; + + transformMcpServer = name: server: { + name = name; + value = { + enabled = !(server.disabled or false); + } + // ( + if server ? url then + { + type = "remote"; + url = server.url; + } + // (lib.optionalAttrs (server ? headers) { headers = server.headers; }) + else if server ? command then + { + type = "local"; + command = [ server.command ] ++ (server.args or [ ]); + } + // (lib.optionalAttrs (server ? env) { environment = server.env; }) + else + { } + ); + }; + + transformedMcpServers = + if cfg.enableMcpIntegration && config.programs.mcp.enable && config.programs.mcp.servers != { } then + lib.listToAttrs (lib.mapAttrsToList transformMcpServer config.programs.mcp.servers) + else + { }; in { meta.maintainers = with lib.maintainers; [ delafthi ]; @@ -25,6 +54,20 @@ in package = mkPackageOption pkgs "opencode" { nullable = true; }; + enableMcpIntegration = mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to integrate the MCP servers config from + {option}`programs.mcp.servers` into + {option}`programs.opencode.settings.mcp`. + + Note: Settings defined in {option}`programs.mcp.servers` are merged + with {option}`programs.opencode.settings.mcp`, with OpenCode settings + taking precedence. + ''; + }; + settings = mkOption { inherit (jsonFormat) type; default = { }; @@ -147,7 +190,7 @@ in Custom themes for opencode. The attribute name becomes the theme filename, and the value is either: - An attribute set, that is converted to a json - - A path to a file conaining the content + - A path to a file containing the content Themes are stored in {file}`$XDG_CONFIG_HOME/opencode/themes/` directory. Set `programs.opencode.settings.theme` to enable the custom theme. See for the documentation. @@ -159,13 +202,21 @@ in home.packages = mkIf (cfg.package != null) [ cfg.package ]; xdg.configFile = { - "opencode/config.json" = mkIf (cfg.settings != { }) { - source = jsonFormat.generate "config.json" ( - { - "$schema" = "https://opencode.ai/config.json"; - } - // cfg.settings - ); + "opencode/config.json" = mkIf (cfg.settings != { } || transformedMcpServers != { }) { + source = + let + # Merge MCP servers: transformed servers + user settings, with user settings taking precedence + mergedMcpServers = transformedMcpServers // (cfg.settings.mcp or { }); + # Merge all settings + mergedSettings = + cfg.settings // (lib.optionalAttrs (mergedMcpServers != { }) { mcp = mergedMcpServers; }); + in + jsonFormat.generate "config.json" ( + { + "$schema" = "https://opencode.ai/config.json"; + } + // mergedSettings + ); }; "opencode/AGENTS.md" = ( diff --git a/tests/modules/programs/opencode/default.nix b/tests/modules/programs/opencode/default.nix index 62d780d5d..74bfc6a8b 100644 --- a/tests/modules/programs/opencode/default.nix +++ b/tests/modules/programs/opencode/default.nix @@ -11,4 +11,6 @@ opencode-mixed-content = ./mixed-content.nix; opencode-themes-inline = ./themes-inline.nix; opencode-themes-path = ./themes-path.nix; + opencode-mcp-integration = ./mcp-integration.nix; + opencode-mcp-integration-with-override = ./mcp-integration-with-override.nix; } diff --git a/tests/modules/programs/opencode/mcp-integration-with-override.json b/tests/modules/programs/opencode/mcp-integration-with-override.json new file mode 100644 index 000000000..46df26ad1 --- /dev/null +++ b/tests/modules/programs/opencode/mcp-integration-with-override.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "context7": { + "enabled": true, + "headers": { + "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" + }, + "type": "remote", + "url": "https://mcp.context7.com/mcp" + }, + "custom-server": { + "enabled": true, + "type": "remote", + "url": "https://example.com" + }, + "everything": { + "command": [ + "custom-command" + ], + "enabled": false, + "type": "local" + } + }, + "model": "anthropic/claude-sonnet-4-20250514", + "theme": "opencode" +} diff --git a/tests/modules/programs/opencode/mcp-integration-with-override.nix b/tests/modules/programs/opencode/mcp-integration-with-override.nix new file mode 100644 index 000000000..c66133b83 --- /dev/null +++ b/tests/modules/programs/opencode/mcp-integration-with-override.nix @@ -0,0 +1,48 @@ +{ + programs.mcp = { + enable = true; + servers = { + everything = { + command = "npx"; + args = [ + "-y" + "@modelcontextprotocol/server-everything" + ]; + }; + context7 = { + url = "https://mcp.context7.com/mcp"; + headers = { + CONTEXT7_API_KEY = "{env:CONTEXT7_API_KEY}"; + }; + }; + }; + }; + + programs.opencode = { + enable = true; + enableMcpIntegration = true; + settings = { + theme = "opencode"; + model = "anthropic/claude-sonnet-4-20250514"; + # User's custom MCP settings should override generated ones + mcp = { + everything = { + enabled = false; # Override to disable + command = [ "custom-command" ]; + type = "local"; + }; + custom-server = { + enabled = true; + type = "remote"; + url = "https://example.com"; + }; + }; + }; + }; + + nmt.script = '' + assertFileExists home-files/.config/opencode/config.json + assertFileContent home-files/.config/opencode/config.json \ + ${./mcp-integration-with-override.json} + ''; +} diff --git a/tests/modules/programs/opencode/mcp-integration.json b/tests/modules/programs/opencode/mcp-integration.json new file mode 100644 index 000000000..ddd917af9 --- /dev/null +++ b/tests/modules/programs/opencode/mcp-integration.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "context7": { + "enabled": true, + "headers": { + "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" + }, + "type": "remote", + "url": "https://mcp.context7.com/mcp" + }, + "disabled-server": { + "command": [ + "echo", + "test" + ], + "enabled": false, + "type": "local" + }, + "everything": { + "command": [ + "npx", + "-y", + "@modelcontextprotocol/server-everything" + ], + "enabled": true, + "type": "local" + } + } +} diff --git a/tests/modules/programs/opencode/mcp-integration.nix b/tests/modules/programs/opencode/mcp-integration.nix new file mode 100644 index 000000000..f3d0f1808 --- /dev/null +++ b/tests/modules/programs/opencode/mcp-integration.nix @@ -0,0 +1,36 @@ +{ + programs.mcp = { + enable = true; + servers = { + everything = { + command = "npx"; + args = [ + "-y" + "@modelcontextprotocol/server-everything" + ]; + }; + context7 = { + url = "https://mcp.context7.com/mcp"; + headers = { + CONTEXT7_API_KEY = "{env:CONTEXT7_API_KEY}"; + }; + }; + disabled-server = { + command = "echo"; + args = [ "test" ]; + disabled = true; + }; + }; + }; + + programs.opencode = { + enable = true; + enableMcpIntegration = true; + }; + + nmt.script = '' + assertFileExists home-files/.config/opencode/config.json + assertFileContent home-files/.config/opencode/config.json \ + ${./mcp-integration.json} + ''; +} From 9ff9a94fd484a7e4f7bd79091a6ed65c927e6a3d Mon Sep 17 00:00:00 2001 From: Thierry Delafontaine Date: Mon, 27 Oct 2025 12:15:04 +0100 Subject: [PATCH 4/9] vscode: add mcp module integration --- modules/programs/vscode/default.nix | 70 +++++++++++++++- tests/modules/programs/vscode/default.nix | 2 + .../vscode/mcp-integration-default.json | 26 ++++++ .../programs/vscode/mcp-integration-test.json | 7 ++ .../vscode/mcp-integration-with-override.json | 25 ++++++ .../vscode/mcp-integration-with-override.nix | 79 +++++++++++++++++++ .../programs/vscode/mcp-integration.nix | 73 +++++++++++++++++ 7 files changed, 278 insertions(+), 4 deletions(-) create mode 100644 tests/modules/programs/vscode/mcp-integration-default.json create mode 100644 tests/modules/programs/vscode/mcp-integration-test.json create mode 100644 tests/modules/programs/vscode/mcp-integration-with-override.json create mode 100644 tests/modules/programs/vscode/mcp-integration-with-override.nix create mode 100644 tests/modules/programs/vscode/mcp-integration.nix diff --git a/modules/programs/vscode/default.nix b/modules/programs/vscode/default.nix index 4eba83784..5c89da6e5 100644 --- a/modules/programs/vscode/default.nix +++ b/modules/programs/vscode/default.nix @@ -114,6 +114,33 @@ let isPath = p: builtins.isPath p || lib.isStorePath p; + transformMcpServerForVscode = + name: server: + let + # Remove the disabled field from the server config + cleanServer = lib.filterAttrs (n: v: n != "disabled") server; + in + { + name = name; + value = { + enabled = !(server.disabled or false); + } + // ( + if server ? url then + { + type = "http"; + } + // cleanServer + else if server ? command then + { + type = "stdio"; + } + // cleanServer + else + { } + ); + }; + profileType = types.submodule { options = { userSettings = mkOption { @@ -154,6 +181,20 @@ let ''; }; + enableMcpIntegration = mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to integrate the MCP servers config from + {option}`programs.mcp.servers` into + {option}`programs.vscode.profiles..userMcp`. + + Note: Settings defined in {option}`programs.mcp.servers` are merged + with {option}`programs.vscode.profiles..userMcp`, with VSCode + settings taking precedence. + ''; + }; + userMcp = mkOption { type = types.either types.path jsonFormat.type; default = { }; @@ -459,10 +500,31 @@ in if isPath v.userTasks then v.userTasks else jsonFormat.generate "vscode-user-tasks" v.userTasks; }) - (mkIf (v.userMcp != { }) { - "${mcpFilePath n}".source = - if isPath v.userMcp then v.userMcp else jsonFormat.generate "vscode-user-mcp" v.userMcp; - }) + (mkIf + ( + v.userMcp != { } + || (v.enableMcpIntegration && config.programs.mcp.enable && config.programs.mcp.servers != { }) + ) + { + "${mcpFilePath n}".source = + if isPath v.userMcp then + v.userMcp + else + let + transformedMcpServers = + if v.enableMcpIntegration && config.programs.mcp.enable && config.programs.mcp.servers != { } then + lib.listToAttrs (lib.mapAttrsToList transformMcpServerForVscode config.programs.mcp.servers) + else + { }; + # Merge MCP servers: transformed servers + user servers, with user servers taking precedence + mergedServers = transformedMcpServers // ((v.userMcp.servers or { })); + # Merge all MCP config + mergedMcpConfig = + v.userMcp // (lib.optionalAttrs (mergedServers != { }) { servers = mergedServers; }); + in + jsonFormat.generate "vscode-user-mcp" mergedMcpConfig; + } + ) (mkIf (v.keybindings != [ ]) { "${keybindingsFilePath n}".source = diff --git a/tests/modules/programs/vscode/default.nix b/tests/modules/programs/vscode/default.nix index 4db8b2c8a..a62521746 100644 --- a/tests/modules/programs/vscode/default.nix +++ b/tests/modules/programs/vscode/default.nix @@ -24,6 +24,8 @@ let keybindings = import ./keybindings.nix; tasks = import ./tasks.nix; mcp = import ./mcp.nix; + mcp-integration = import ./mcp-integration.nix; + mcp-integration-with-override = import ./mcp-integration-with-override.nix; update-checks = import ./update-checks.nix; snippets = import ./snippets.nix; }; diff --git a/tests/modules/programs/vscode/mcp-integration-default.json b/tests/modules/programs/vscode/mcp-integration-default.json new file mode 100644 index 000000000..66e3dfd42 --- /dev/null +++ b/tests/modules/programs/vscode/mcp-integration-default.json @@ -0,0 +1,26 @@ +{ + "servers": { + "context7": { + "enabled": true, + "headers": { + "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" + }, + "type": "http", + "url": "https://mcp.context7.com/mcp" + }, + "disabled-server": { + "command": "echo", + "enabled": false, + "type": "stdio" + }, + "everything": { + "args": [ + "-y", + "@modelcontextprotocol/server-everything" + ], + "command": "npx", + "enabled": true, + "type": "stdio" + } + } +} diff --git a/tests/modules/programs/vscode/mcp-integration-test.json b/tests/modules/programs/vscode/mcp-integration-test.json new file mode 100644 index 000000000..4f35aeaf1 --- /dev/null +++ b/tests/modules/programs/vscode/mcp-integration-test.json @@ -0,0 +1,7 @@ +{ + "servers": { + "Github": { + "url": "https://api.githubcopilot.com/mcp/" + } + } +} diff --git a/tests/modules/programs/vscode/mcp-integration-with-override.json b/tests/modules/programs/vscode/mcp-integration-with-override.json new file mode 100644 index 000000000..10772d0bf --- /dev/null +++ b/tests/modules/programs/vscode/mcp-integration-with-override.json @@ -0,0 +1,25 @@ +{ + "servers": { + "CustomServer": { + "type": "http", + "url": "https://example.com/mcp" + }, + "context7": { + "enabled": true, + "headers": { + "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" + }, + "type": "http", + "url": "https://mcp.context7.com/mcp" + }, + "everything": { + "args": [ + "-y", + "@modelcontextprotocol/server-everything" + ], + "command": "custom-npx", + "enabled": false, + "type": "stdio" + } + } +} diff --git a/tests/modules/programs/vscode/mcp-integration-with-override.nix b/tests/modules/programs/vscode/mcp-integration-with-override.nix new file mode 100644 index 000000000..a1d7371f0 --- /dev/null +++ b/tests/modules/programs/vscode/mcp-integration-with-override.nix @@ -0,0 +1,79 @@ +package: + +{ + config, + pkgs, + lib, + ... +}: + +let + cfg = config.programs.vscode; + willUseIfd = package.pname != "vscode"; + + mcpFilePath = + name: + if pkgs.stdenv.hostPlatform.isDarwin then + "Library/Application Support/${cfg.nameShort}/User/${ + lib.optionalString (name != "default") "profiles/${name}/" + }mcp.json" + else + ".config/${cfg.nameShort}/User/${ + lib.optionalString (name != "default") "profiles/${name}/" + }mcp.json"; + +in + +lib.mkIf (willUseIfd -> config.test.enableLegacyIfd) { + programs.mcp = { + enable = true; + servers = { + everything = { + command = "npx"; + args = [ + "-y" + "@modelcontextprotocol/server-everything" + ]; + }; + context7 = { + url = "https://mcp.context7.com/mcp"; + headers = { + CONTEXT7_API_KEY = "{env:CONTEXT7_API_KEY}"; + }; + }; + }; + }; + + programs.vscode = { + enable = true; + inherit package; + profiles = { + default = { + enableMcpIntegration = true; + # User MCP settings should override generated ones + userMcp = { + servers = { + everything = { + command = "custom-npx"; + args = [ + "-y" + "@modelcontextprotocol/server-everything" + ]; + enabled = false; + type = "stdio"; + }; + CustomServer = { + type = "http"; + url = "https://example.com/mcp"; + }; + }; + }; + }; + }; + }; + + nmt.script = '' + assertFileExists "home-files/${mcpFilePath "default"}" + assertFileContent "home-files/${mcpFilePath "default"}" ${./mcp-integration-with-override.json} + ''; +} diff --git a/tests/modules/programs/vscode/mcp-integration.nix b/tests/modules/programs/vscode/mcp-integration.nix new file mode 100644 index 000000000..e40056747 --- /dev/null +++ b/tests/modules/programs/vscode/mcp-integration.nix @@ -0,0 +1,73 @@ +package: + +{ + config, + pkgs, + lib, + ... +}: + +let + cfg = config.programs.vscode; + willUseIfd = package.pname != "vscode"; + + mcpFilePath = + name: + if pkgs.stdenv.hostPlatform.isDarwin then + "Library/Application Support/${cfg.nameShort}/User/${ + lib.optionalString (name != "default") "profiles/${name}/" + }mcp.json" + else + ".config/${cfg.nameShort}/User/${ + lib.optionalString (name != "default") "profiles/${name}/" + }mcp.json"; + +in + +lib.mkIf (willUseIfd -> config.test.enableLegacyIfd) { + programs.mcp = { + enable = true; + servers = { + everything = { + command = "npx"; + args = [ + "-y" + "@modelcontextprotocol/server-everything" + ]; + }; + context7 = { + url = "https://mcp.context7.com/mcp"; + headers = { + CONTEXT7_API_KEY = "{env:CONTEXT7_API_KEY}"; + }; + }; + disabled-server = { + command = "echo"; + disabled = true; + }; + }; + }; + + programs.vscode = { + enable = true; + inherit package; + profiles = { + default.enableMcpIntegration = true; + test.userMcp = { + servers = { + Github = { + url = "https://api.githubcopilot.com/mcp/"; + }; + }; + }; + }; + }; + + nmt.script = '' + assertFileExists "home-files/${mcpFilePath "default"}" + assertFileContent "home-files/${mcpFilePath "default"}" ${./mcp-integration-default.json} + + assertFileExists "home-files/${mcpFilePath "test"}" + assertFileContent "home-files/${mcpFilePath "test"}" ${./mcp-integration-test.json} + ''; +} From 1342b821db15b6c79731310ba787d152cd60e74b Mon Sep 17 00:00:00 2001 From: Thierry Delafontaine Date: Mon, 3 Nov 2025 11:25:16 +0100 Subject: [PATCH 5/9] news: add entry for mcp module and integrations Add news entry documenting the new `programs.mcp` module and MCP integration support in OpenCode and VSCode modules. --- .../misc/news/2025/11/2025-11-03_11-18-15.nix | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 modules/misc/news/2025/11/2025-11-03_11-18-15.nix diff --git a/modules/misc/news/2025/11/2025-11-03_11-18-15.nix b/modules/misc/news/2025/11/2025-11-03_11-18-15.nix new file mode 100644 index 000000000..82d9dd5bb --- /dev/null +++ b/modules/misc/news/2025/11/2025-11-03_11-18-15.nix @@ -0,0 +1,25 @@ +{ + time = "2025-11-03T10:18:15+00:00"; + condition = true; + message = '' + A new module 'programs.mcp' is now available for managing Model + Context Protocol (MCP) server configurations. + + The 'programs.mcp.servers' option allows you to define MCP servers + in a central location. These configurations can be automatically + integrated into applications that support MCP. + + Two modules now support MCP integration: + + - 'programs.opencode.enableMcpIntegration': Integrates MCP servers + into OpenCode's configuration. + + - 'programs.vscode.profiles..enableMcpIntegration': Integrates + MCP servers into VSCode profiles. + + When integration is enabled, servers from 'programs.mcp.servers' are + merged with application-specific MCP settings, with the latter taking + precedence. This allows you to define MCP servers once and reuse them + across multiple applications. + ''; +} From 5cdf9ef99563a457f12a712e1f42c4f0d709203c Mon Sep 17 00:00:00 2001 From: leiserfg Date: Fri, 31 Oct 2025 13:16:27 +0100 Subject: [PATCH 6/9] vicinae service: init Signed-off-by: Austin Horstman --- modules/programs/vicinae.nix | 244 ++++++++++++++++++ tests/modules/programs/vicinae/default.nix | 4 + .../programs/vicinae/example-settings.nix | 77 ++++++ 3 files changed, 325 insertions(+) create mode 100644 modules/programs/vicinae.nix create mode 100644 tests/modules/programs/vicinae/default.nix create mode 100644 tests/modules/programs/vicinae/example-settings.nix diff --git a/modules/programs/vicinae.nix b/modules/programs/vicinae.nix new file mode 100644 index 000000000..bde52d4eb --- /dev/null +++ b/modules/programs/vicinae.nix @@ -0,0 +1,244 @@ +{ + config, + pkgs, + lib, + ... +}: +let + cfg = config.programs.vicinae; + + jsonFormat = pkgs.formats.json { }; +in +{ + meta.maintainers = [ lib.maintainers.leiserfg ]; + + options.programs.vicinae = { + enable = lib.mkEnableOption "vicinae launcher daemon"; + + package = lib.mkPackageOption pkgs "vicinae" { nullable = true; }; + + systemd = { + enable = lib.mkEnableOption "vicinae systemd integration"; + + autoStart = lib.mkOption { + type = lib.types.bool; + default = true; + description = "If the vicinae daemon should be started automatically"; + }; + + target = lib.mkOption { + type = lib.types.str; + default = "graphical-session.target"; + example = "sway-session.target"; + description = '' + The systemd target that will automatically start the vicinae service. + ''; + }; + }; + + useLayerShell = lib.mkOption { + type = lib.types.bool; + default = true; + description = "If vicinae should use the layer shell"; + }; + + extensions = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = [ ]; + description = '' + List of Vicinae extensions to install. + + You can use the `config.lib.vicinae.mkExtension` and `config.lib.vicinae.mkRayCastExtension` functions to create them, like: + ```nix + [ + (config.lib.vicinae.mkExtension { + name = "test-extension"; + src = + pkgs.fetchFromGitHub { + owner = "schromp"; + repo = "vicinae-extensions"; + rev = "f8be5c89393a336f773d679d22faf82d59631991"; + sha256 = "sha256-zk7WIJ19ITzRFnqGSMtX35SgPGq0Z+M+f7hJRbyQugw="; + } + + "/test-extension"; + }) + (config.lib.vicinae.mkRayCastExtension { + name = "gif-search"; + sha256 = "sha256-G7il8T1L+P/2mXWJsb68n4BCbVKcrrtK8GnBNxzt73Q="; + rev = "4d417c2dfd86a5b2bea202d4a7b48d8eb3dbaeb1"; + }) + ], + ``` + ''; + }; + + themes = lib.mkOption { + inherit (jsonFormat) type; + default = { }; + description = '' + Theme settings to add to the themes folder in `~/.config/vicinae/themes`. + + The attribute name of the theme will be the name of theme json file, + e.g. `base16-default-dark` will be `base16-default-dark.json`. + ''; + example = + lib.literalExpression # nix + '' + { + base16-default-dark = { + version = "1.0.0"; + appearance = "dark"; + icon = /path/to/icon.png; + name = "base16 default dark"; + description = "base16 default dark by Chris Kempson"; + palette = { + background = "#181818"; + foreground = "#d8d8d8"; + blue = "#7cafc2"; + green = "#a3be8c"; + magenta = "#ba8baf"; + orange = "#dc9656"; + purple = "#a16946"; + red = "#ab4642"; + yellow = "#f7ca88"; + cyan = "#86c1b9"; + }; + }; + } + ''; + }; + + settings = lib.mkOption { + inherit (jsonFormat) type; + default = { }; + description = "Settings written as JSON to `~/.config/vicinae/vicinae.json."; + example = lib.literalExpression '' + { + faviconService = "twenty"; + font = { + size = 10; + }; + popToRootOnClose = false; + rootSearch = { + searchFiles = false; + }; + theme = { + name = "vicinae-dark"; + }; + window = { + csd = true; + opacity = 0.95; + rounding = 10; + }; + } + ''; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + (lib.hm.assertions.assertPlatform "programs.vicinae" pkgs lib.platforms.linux) + { + assertion = cfg.systemd.enable -> cfg.package != null; + message = "{option}programs.vicinae.systemd.enable requires non null {option}programs.vicinae.package"; + } + ]; + lib.vicinae.mkExtension = ( + { + name, + src, + }: + (pkgs.buildNpmPackage { + inherit name src; + installPhase = '' + runHook preInstall + + mkdir -p $out + cp -r /build/.local/share/vicinae/extensions/${name}/* $out/ + + runHook postInstall + ''; + npmDeps = pkgs.importNpmLock { npmRoot = src; }; + npmConfigHook = pkgs.importNpmLock.npmConfigHook; + }) + ); + + lib.vicinae.mkRayCastExtension = ( + { + name, + sha256, + rev, + }: + let + src = + pkgs.fetchgit { + inherit rev sha256; + url = "https://github.com/raycast/extensions"; + sparseCheckout = [ + "/extensions/${name}" + ]; + } + + "/extensions/${name}"; + in + (pkgs.buildNpmPackage { + inherit name src; + installPhase = '' + runHook preInstall + + mkdir -p $out + cp -r /build/.config/raycast/extensions/${name}/* $out/ + + runHook postInstall + ''; + npmDeps = pkgs.importNpmLock { npmRoot = src; }; + npmConfigHook = pkgs.importNpmLock.npmConfigHook; + }) + ); + + home.packages = lib.mkIf (cfg.package != null) [ cfg.package ]; + + xdg = { + configFile = { + "vicinae/vicinae.json" = lib.mkIf (cfg.settings != { }) { + source = jsonFormat.generate "vicinae-settings" cfg.settings; + }; + } + // lib.mapAttrs' ( + name: theme: + lib.nameValuePair "vicinae/themes/${name}.json" { + source = jsonFormat.generate "vicinae-${name}-theme" theme; + } + ) cfg.themes; + + dataFile = builtins.listToAttrs ( + builtins.map (item: { + name = "vicinae/extensions/${item.name}"; + value.source = item; + }) cfg.extensions + ); + }; + + systemd.user.services.vicinae = lib.mkIf (cfg.systemd.enable && cfg.package != null) { + Unit = { + Description = "Vicinae server daemon"; + Documentation = [ "https://docs.vicinae.com" ]; + After = [ cfg.systemd.target ]; + PartOf = [ cfg.systemd.target ]; + BindsTo = [ cfg.systemd.target ]; + }; + Service = { + EnvironmentFile = pkgs.writeText "vicinae-env" '' + USE_LAYER_SHELL=${if cfg.useLayerShell then builtins.toString 1 else builtins.toString 0} + ''; + Type = "simple"; + ExecStart = "${lib.getExe' cfg.package "vicinae"} server"; + Restart = "always"; + RestartSec = 5; + KillMode = "process"; + }; + Install = lib.mkIf cfg.systemd.autoStart { + WantedBy = [ cfg.systemd.target ]; + }; + }; + }; +} diff --git a/tests/modules/programs/vicinae/default.nix b/tests/modules/programs/vicinae/default.nix new file mode 100644 index 000000000..4a4736323 --- /dev/null +++ b/tests/modules/programs/vicinae/default.nix @@ -0,0 +1,4 @@ +{ lib, pkgs, ... }: +lib.optionalAttrs (pkgs.stdenv.hostPlatform.isLinux) { + vicinae-example-settings = ./example-settings.nix; +} diff --git a/tests/modules/programs/vicinae/example-settings.nix b/tests/modules/programs/vicinae/example-settings.nix new file mode 100644 index 000000000..b9de5f24f --- /dev/null +++ b/tests/modules/programs/vicinae/example-settings.nix @@ -0,0 +1,77 @@ +{ + pkgs, + config, + ... +}: + +{ + programs.vicinae = { + enable = true; + systemd.enable = true; + + settings = { + faviconService = "twenty"; + font = { + size = 10; + }; + popToRootOnClose = false; + rootSearch = { + searchFiles = false; + }; + theme = { + name = "vicinae-dark"; + }; + window = { + csd = true; + opacity = 0.95; + rounding = 10; + }; + }; + themes = { + base16-default-dark = { + version = "1.0.0"; + appearance = "dark"; + name = "base16 default dark"; + description = "base16 default dark by Chris Kempson"; + palette = { + background = "#181818"; + foreground = "#d8d8d8"; + blue = "#7cafc2"; + green = "#a3be8c"; + magenta = "#ba8baf"; + orange = "#dc9656"; + purple = "#a16946"; + red = "#ab4642"; + yellow = "#f7ca88"; + cyan = "#86c1b9"; + }; + }; + }; + + extensions = [ + (config.lib.vicinae.mkRayCastExtension { + name = "gif-search"; + sha256 = "sha256-G7il8T1L+P/2mXWJsb68n4BCbVKcrrtK8GnBNxzt73Q="; + rev = "4d417c2dfd86a5b2bea202d4a7b48d8eb3dbaeb1"; + }) + (config.lib.vicinae.mkExtension { + name = "test-extension"; + src = + pkgs.fetchFromGitHub { + owner = "schromp"; + repo = "vicinae-extensions"; + rev = "f8be5c89393a336f773d679d22faf82d59631991"; + sha256 = "sha256-zk7WIJ19ITzRFnqGSMtX35SgPGq0Z+M+f7hJRbyQugw="; + } + + "/test-extension"; + }) + ]; + }; + + nmt.script = '' + assertFileExists "home-files/.config/vicinae/vicinae.json" + assertFileExists "home-files/.config/systemd/user/vicinae.service" + assertFileExists "home-files/.local/share/vicinae/extensions/gif-search/package.json" + assertFileExists "home-files/.local/share/vicinae/extensions/test-extension/package.json" + ''; +} From 6feb3685114e5807b5effe7806b425b75b1b75c0 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 3 Nov 2025 20:46:03 -0600 Subject: [PATCH 7/9] news: add vicinae entry Signed-off-by: Austin Horstman --- .../misc/news/2025/11/2025-11-03_20-33-15.nix | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 modules/misc/news/2025/11/2025-11-03_20-33-15.nix diff --git a/modules/misc/news/2025/11/2025-11-03_20-33-15.nix b/modules/misc/news/2025/11/2025-11-03_20-33-15.nix new file mode 100644 index 000000000..b63b6d6e2 --- /dev/null +++ b/modules/misc/news/2025/11/2025-11-03_20-33-15.nix @@ -0,0 +1,20 @@ +{ pkgs, ... }: +{ + time = "2025-11-04T02:33:15+00:00"; + condition = pkgs.stdenv.hostPlatform.isLinux; + message = '' + A new program is available: 'programs.vicinae'. + + Vicinae is a modern application launcher daemon for Linux with support for + extensions, custom themes, and layer shell integration. + + The module provides: + - Systemd service integration with automatic start support + - Extension management with helpers for Vicinae and Raycast extensions + - Theme configuration support + - Declarative settings via 'programs.vicinae.settings' + - Layer shell integration for Wayland compositors + + See the module options for more details on configuration. + ''; +} From 65bf99c5793ff83436fa65f64c4cdd874cdb4ebc Mon Sep 17 00:00:00 2001 From: meck Date: Tue, 28 Oct 2025 16:35:02 +0100 Subject: [PATCH 8/9] yazi: update wrappers not to use cat in subshell If cat is aliased to bat the non piping operation in the script might include extra text, use builtins instead --- modules/programs/yazi.nix | 4 ++-- tests/modules/programs/yazi/fish-integration-expected.fish | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/programs/yazi.nix b/modules/programs/yazi.nix index c945447cd..6d8dbe0fb 100644 --- a/modules/programs/yazi.nix +++ b/modules/programs/yazi.nix @@ -221,7 +221,7 @@ in function ${cfg.shellWrapperName}() { local tmp="$(mktemp -t "yazi-cwd.XXXXX")" yazi "$@" --cwd-file="$tmp" - if cwd="$(cat -- "$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then + if cwd="$(<"$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then builtin cd -- "$cwd" fi rm -f -- "$tmp" @@ -231,7 +231,7 @@ in fishIntegration = '' set -l tmp (mktemp -t "yazi-cwd.XXXXX") command yazi $argv --cwd-file="$tmp" - if set cwd (cat -- "$tmp"); and [ -n "$cwd" ]; and [ "$cwd" != "$PWD" ] + if read cwd < "$tmp"; and [ -n "$cwd" ]; and [ "$cwd" != "$PWD" ] builtin cd -- "$cwd" end rm -f -- "$tmp" diff --git a/tests/modules/programs/yazi/fish-integration-expected.fish b/tests/modules/programs/yazi/fish-integration-expected.fish index d8f11f284..d0019d999 100644 --- a/tests/modules/programs/yazi/fish-integration-expected.fish +++ b/tests/modules/programs/yazi/fish-integration-expected.fish @@ -1,7 +1,7 @@ function yy set -l tmp (mktemp -t "yazi-cwd.XXXXX") command yazi $argv --cwd-file="$tmp" - if set cwd (cat -- "$tmp"); and [ -n "$cwd" ]; and [ "$cwd" != "$PWD" ] + if read cwd <"$tmp"; and [ -n "$cwd" ]; and [ "$cwd" != "$PWD" ] builtin cd -- "$cwd" end rm -f -- "$tmp" From aa6936bb637e46a49cf1292486200ba41dd4bcf7 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 3 Nov 2025 21:51:55 -0600 Subject: [PATCH 9/9] tests/yazi: fix bash/zsh integration tests Didn't fail even with incorrect assertion. Multi line string for assertFileContains didn't properly work. Don't want to manage an entire zsh config file in assertFileContent so just multi step asserting the generated file. Signed-off-by: Austin Horstman --- .../yazi/bash-integration-enabled.nix | 20 ++++++----------- .../programs/yazi/zsh-integration-enabled.nix | 22 +++++++------------ 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/tests/modules/programs/yazi/bash-integration-enabled.nix b/tests/modules/programs/yazi/bash-integration-enabled.nix index 1bebbccad..41f56d765 100644 --- a/tests/modules/programs/yazi/bash-integration-enabled.nix +++ b/tests/modules/programs/yazi/bash-integration-enabled.nix @@ -1,15 +1,3 @@ -let - shellIntegration = '' - function yy() { - local tmp="$(mktemp -t "yazi-cwd.XXXXX")" - yazi "$@" --cwd-file="$tmp" - if cwd="$(cat -- "$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then - builtin cd -- "$cwd" - fi - rm -f -- "$tmp" - } - ''; -in { programs.bash.enable = true; @@ -19,6 +7,12 @@ in }; nmt.script = '' - assertFileContains home-files/.bashrc '${shellIntegration}' + assertFileExists home-files/.bashrc + assertFileContains home-files/.bashrc 'function yy() {' + assertFileContains home-files/.bashrc 'local tmp="$(mktemp -t "yazi-cwd.XXXXX")"' + assertFileContains home-files/.bashrc 'yazi "$@" --cwd-file="$tmp"' + assertFileContains home-files/.bashrc 'if cwd="$(<"$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then' + assertFileContains home-files/.bashrc 'builtin cd -- "$cwd"' + assertFileContains home-files/.bashrc 'rm -f -- "$tmp"' ''; } diff --git a/tests/modules/programs/yazi/zsh-integration-enabled.nix b/tests/modules/programs/yazi/zsh-integration-enabled.nix index ef8e0ccc8..6087810c5 100644 --- a/tests/modules/programs/yazi/zsh-integration-enabled.nix +++ b/tests/modules/programs/yazi/zsh-integration-enabled.nix @@ -1,24 +1,18 @@ -let - shellIntegration = '' - function yy() { - local tmp="$(mktemp -t "yazi-cwd.XXXXX")" - yazi "$@" --cwd-file="$tmp" - if cwd="$(cat -- "$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then - builtin cd -- "$cwd" - fi - rm -f -- "$tmp" - } - ''; -in { programs.zsh.enable = true; programs.yazi = { enable = true; - enableBashIntegration = true; + enableZshIntegration = true; }; nmt.script = '' - assertFileContains home-files/.zshrc '${shellIntegration}' + assertFileExists home-files/.zshrc + assertFileContains home-files/.zshrc 'function yy() {' + assertFileContains home-files/.zshrc 'local tmp="$(mktemp -t "yazi-cwd.XXXXX")"' + assertFileContains home-files/.zshrc 'yazi "$@" --cwd-file="$tmp"' + assertFileContains home-files/.zshrc 'if cwd="$(<"$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then' + assertFileContains home-files/.zshrc 'builtin cd -- "$cwd"' + assertFileContains home-files/.zshrc 'rm -f -- "$tmp"' ''; }