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.
This commit is contained in:
Austin Horstman 2026-04-24 20:05:10 -05:00
parent 53d9c3c50b
commit f6fd130ad3
3 changed files with 91 additions and 23 deletions

View file

@ -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.<name>.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 | `[]` |

View file

@ -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)

View file

@ -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