From f6fd130ad3b95c3c039fd7f882825c49f7e70bde Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Fri, 24 Apr 2026 20:05:10 -0500 Subject: [PATCH] lib/plugins: add public callSetup override Expose a user-facing callSetup option for mkNeovimPlugin users while keeping plugin defaults internal. Preserve optional setup semantics, document the override, and cover the new behavior with unit and failure tests. --- CONTRIBUTING.md | 2 +- lib/plugins/mk-neovim-plugin.nix | 55 +++++++++++++++++++----------- tests/lib-tests.nix | 57 ++++++++++++++++++++++++++++++-- 3 files changed, 91 insertions(+), 23 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 74f75c6f..4c10504f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,7 +68,7 @@ A template plugin can be found in (plugins/TEMPLATE.nix)[https://github.com/nix- | **name** | The name of the Neovim plugin. | Yes | N/A | | **url** | The URL of the plugin's repository. | Yes | `package` parameter's `meta.homepage` | | **maintainers** | Maintainers for the plugin. | Yes | N/A | -| **callSetup** | Whether to call the setup function. Accepts `true`, `false`, or `"optional"`. `"optional"` only emits the generated setup call when `settings` was explicitly defined, so untouched defaults do not call `setup()`, while an explicit `settings = { }` still does. Useful when `setup()` is only needed for customizations. | No | `true` | +| **callSetup** | The plugin's default behavior for generating the setup function call. Accepts `true`, `false`, or `"optional"`. `"optional"` only emits the generated setup call when `settings` was explicitly defined, so untouched defaults do not call `setup()`, while an explicit `settings = { }` still does. When this default is not `false`, users can override it with `plugins..callSetup = null | true | false`. Useful when `setup()` is only needed for customizations. | No | `true` | | **colorscheme** | The name of the colorscheme. | No | `name` parameter | | **configLocation** | The option location where the lua configuration should be installed. Nested option locations can be represented as a list. The location can also be wrapped using `lib.mkBefore`, `lib.mkAfter`, or `lib.mkOrder`. | No | `"extraConfigLuaPre"` if `isColorscheme` then `extraConfigLuaPre`, otherwise `"extraConfigLua"` | | **dependencies** | A list of [`dependencies`] to enable by default with this plugin. (List of strings) | No | `[]` | diff --git a/lib/plugins/mk-neovim-plugin.nix b/lib/plugins/mk-neovim-plugin.nix index 5751a5ea..977c7738 100644 --- a/lib/plugins/mk-neovim-plugin.nix +++ b/lib/plugins/mk-neovim-plugin.nix @@ -47,6 +47,7 @@ let false "optional" ]; + defaultCallSetup = callSetup; optionDefaultPriority = (lib.mkOptionDefault null).priority; namespace = if isColorscheme then "colorschemes" else "plugins"; loc = [ @@ -59,20 +60,23 @@ let let cfg = lib.getAttrFromPath loc config; opts = lib.getAttrFromPath loc options; + userCallSetup = if opts ? callSetup then cfg.callSetup else null; settingsWereDefined = hasSettings && (opts.settings.highestPrio or optionDefaultPriority) < optionDefaultPriority; + effectiveCallSetup = if userCallSetup != null then userCallSetup else defaultCallSetup; setupCode = '' require('${moduleName}')${setup}(${ lib.optionalString (cfg ? settings) (lib.nixvim.toLuaObject cfg.settings) }) ''; - setupContent = - if callSetup == true then - setupCode - else if callSetup == "optional" then - lib.optionalString settingsWereDefined setupCode + shouldCallSetup = + if effectiveCallSetup == true then + true + else if effectiveCallSetup == "optional" then + settingsWereDefined else - ""; + false; + setupContent = lib.optionalString shouldCallSetup setupCode; luaConfigAtLocation = utils.mkConfigAt configLocation cfg.luaConfig.content; in @@ -90,26 +94,41 @@ let example = settingsExample; }; } - // lib.optionalAttrs hasLuaConfig { - luaConfig = lib.mkOption { - type = lib.types.pluginLuaConfig; - default = { }; - description = "The plugin's lua configuration"; - }; - } + // lib.optionalAttrs hasLuaConfig ( + lib.optionalAttrs (defaultCallSetup != false) { + callSetup = lib.mkOption { + type = with lib.types; nullOr bool; + default = null; + description = '' + Whether to generate the standard `require('${moduleName}')${setup}(...)` call for this plugin. + + By default, this follows the plugin's built-in behavior. Set this to `false` + to disable the generated setup call, or to `true` to force it even when the + plugin would only call it conditionally. + ''; + }; + } + // { + luaConfig = lib.mkOption { + type = lib.types.pluginLuaConfig; + default = { }; + description = "The plugin's lua configuration"; + }; + } + ) // extraOptions ); config = - assert lib.assertMsg (lib.elem callSetup validCallSetupModes) '' + assert lib.assertMsg (lib.elem defaultCallSetup validCallSetupModes) '' Unexpected `callSetup` value for `${lib.showOption loc}`. Expected one of: true, false, "optional" ''; assert lib.assertMsg ( - callSetup != false -> hasLuaConfig + defaultCallSetup != false -> hasLuaConfig ) "This plugin is supposed to call the `setup()` function but has `hasLuaConfig` set to false"; assert lib.assertMsg ( - callSetup != "optional" || hasSettings + defaultCallSetup != "optional" || hasSettings ) "This plugin uses `callSetup = \"optional\"` but has `hasSettings` set to false"; lib.mkIf cfg.enable ( lib.mkMerge ( @@ -166,9 +185,7 @@ let ++ lib.optionals hasLuaConfig [ # Add the plugin setup code `require('foo').setup(...)` to the lua configuration - (lib.optionalAttrs (callSetup != false) ( - lib.setAttrByPath loc { luaConfig.content = setupContent; } - )) + (lib.setAttrByPath loc { luaConfig.content = lib.mkIf (effectiveCallSetup != false) setupContent; }) # When NOT lazy loading, write `luaConfig.content` to `configLocation` (lib.mkIf (!cfg.lazyLoad.enable) luaConfigAtLocation) diff --git a/tests/lib-tests.nix b/tests/lib-tests.nix index 2e290524..182a27de 100644 --- a/tests/lib-tests.nix +++ b/tests/lib-tests.nix @@ -69,18 +69,41 @@ let }; }; - optionalSetupConfig = - module: + alwaysSetupInlinePlugin = + { lib, ... }: + lib.nixvim.plugins.mkNeovimPlugin { + name = "fake"; + moduleName = "fake"; + package = [ + "vimPlugins" + "vim-repeat" + ]; + maintainers = [ ]; + callSetup = true; + + settingsOptions = { + foo = lib.mkOption { + type = lib.types.bool; + default = false; + }; + }; + }; + + inlinePluginConfig = + plugin: module: (lib.nixvim.modules.evalNixvim { modules = [ { _module.args.pkgs = lib.mkForce pkgs; } - optionalSetupInlinePlugin + plugin module ]; }).config.content; + optionalSetupConfig = inlinePluginConfig optionalSetupInlinePlugin; + alwaysSetupConfig = inlinePluginConfig alwaysSetupInlinePlugin; + results = lib.runTests { testToLuaObject = { expr = lib.nixvim.toLuaObject { @@ -597,6 +620,34 @@ let expected = false; }; + testMkNeovimPluginCallSetupOptionForcesOptionalSetup = { + expr = + let + content = optionalSetupConfig { + plugins.fake = { + enable = true; + callSetup = true; + }; + }; + in + lib.hasInfix "require('fake').setup(" content; + expected = true; + }; + + testMkNeovimPluginCallSetupOptionDisablesDefaultSetup = { + expr = + let + content = alwaysSetupConfig { + plugins.fake = { + enable = true; + callSetup = false; + }; + }; + in + lib.hasInfix "require('fake').setup(" content; + expected = false; + }; + testMkNeovimPluginOptionalSetupRunsForExplicitEmptySettings = { expr = let