feat: new module interface has 100% feature parity

dev
LeMarsu 2025-10-18 19:13:07 +02:00
parent c12a87bfa1
commit bf329e5f42
11 changed files with 538 additions and 197 deletions

View File

@ -14,19 +14,35 @@
... ...
}: let }: let
cfg = config.sloth; cfg = config.sloth;
sLib = (cfg).lib; sLib = cfg.lib;
packageModule = types.submodule { packageModule = types.submodule ({config, ...}: {
options.modules = mkOption { options = {
description = "modules used to create your neovim package"; module = mkOption {
type = with types; listOf attrs; description = "module used to create your neovim package";
default = []; type = types.deferredModule;
};
modules = mkOption {
description = "modules used to create your neovim package";
type = with types; listOf attrs;
default = [config.module];
};
specialArgs = mkOption {
description = "specialArgs to follow to your modules";
type = types.attrs;
default = {};
};
}; };
}; });
buildPackage = { name, value }: { buildPackage = {
name,
value,
}: {
inherit name; inherit name;
value = sLib.mkNeovimPkg {inherit (value) modules;}; value = sLib.mkNeovimPkg {inherit (value) modules specialArgs;};
}; };
packagesList = map buildPackage (attrsToList cfg.packages); packagesList = map buildPackage (attrsToList cfg.packages);
in { in {
@ -35,7 +51,7 @@
type = types.attrs; type = types.attrs;
description = "sloths lib"; description = "sloths lib";
default = topConfig.flake.mkLib {inherit pkgs;}; default = topConfig.flake.mkLib {inherit pkgs;};
defaultText = "mkLib { inherit pkgs; }"; defaultText = "mkLib {inherit pkgs;}";
}; };
packages = mkOption { packages = mkOption {

View File

@ -15,8 +15,8 @@ in {
flake = { flake = {
lib = { lib = {
mkNeovimPkg = import ./mkNeovimPkg.nix {inherit version types;}; mkNeovimPkg = import ./mkNeovimPkg.nix {inherit version types;};
mkPluginsFromInputs = import ./mkPluginsFromInputs.nix; mkPluginsFromInputs = import ./oldMkPluginsFromInputs.nix;
}; };
mkLib = import ./lib.nix; mkLib = import ./lib.nix {inherit version;};
}; };
} }

51
lib/init.nix Normal file
View File

@ -0,0 +1,51 @@
{pkgs, ...}: let
inherit (builtins) isAttrs isPath isString readFile;
inherit (pkgs.lib) fix mkOption types;
in
fix (self: {
module = types.submodule {
options = {
init = mkOption {
type = with types; nullOr (either path str);
default = null;
description = ''
Lua code to call before plugins loaded
'';
};
postInit = mkOption {
type = with types; nullOr (either path str);
default = null;
description = ''
Lua code called after init but before import
'';
};
config = mkOption {
type = with types; nullOr (either path str);
default = null;
description = ''
Lua code called after all plugins are loaded
'';
};
};
};
option = mkOption {
default = null;
description = ''
init.lua configuration
'';
type = with types; nullOr (oneOf [path str self.module]);
example = ./init.lua;
};
mkCustomLuaRc = init:
if isString init
then init
else if isPath init
then readFile init
else if isAttrs init
then "print('not implmented yet')"
else null;
})

View File

@ -1,181 +1,82 @@
{pkgs, ...}: let {version, ...}: {pkgs, ...}: let
inherit (pkgs) lib; inherit (builtins) concatLists;
inherit (lib) concatMap evalModules fix literalExample mkEnableOption mkOption mkPackageOption optional types; inherit (pkgs) bash lib;
inherit
(lib)
callPackageWith
concatMap
evalModules
fix
flip
literalExample
mkEnableOption
mkOption
mkPackageOption
optional
optionalString
types
;
initModule = types.submodule { callModule = callPackageWith {inherit pkgs callModule;};
options = {
init = mkOption {
type = with types; nullOr (either path str);
default = null;
description = ''
Lua code to call before plugins loaded
'';
};
postInit = mkOption { modules = {
type = with types; nullOr (either path str); init = callModule ./init.nix {};
default = null; plugin = callModule ./plugin {};
description = '' runtime = callModule ./runtime.nix {};
Lua code called after init but before import sloth = callModule ./sloth.nix {};
'';
};
config = mkOption {
type = with types; nullOr (either path str);
default = null;
description = ''
Lua code called after all plugins are loaded
'';
};
};
};
pluginModule = types.submodule {
options = {
init = mkOption {
type = with types; nullOr (either path str);
default = null;
description = ''
The init configuration of your plugin.
This will be called before loading your plugin.
'';
};
config = mkOption {
type = with types; nullOr (either path str);
default = null;
description = ''
The configuration of your plugin.
This will be called after loading your plugin.
'';
};
plugin = mkOption {
# TODO Type should allow `basicPluginType`
type = with types; nullOr package;
default = null;
description = ''
Ensure thoses plugins are loaded before the current one
'';
};
dependencies = mkOption {
type = with types; listOf package;
default = [];
description = ''
Ensure thoses plugins are loaded before the current one
'';
};
extraLuaPackages = mkOption {
type = with types; listOf package;
default = [];
description = ''
Ensure those packages are available
'';
};
lazy = mkEnableOption "loading lazily the plugin";
events = mkOption {
# TODO See eventType
type = with types; listOf str;
default = [];
description = ''
List of events on which the plugin should be loaded
'';
};
cmd = mkOption {
type = with types; listOf str;
default = [];
description = ''
List of commands on which the plugin should be loaded
'';
};
ft = mkOption {
type = with types; listOf str;
default = [];
description = ''
List of filetypes on which the plugin should be loaded
'';
};
keymaps = mkOption {
# TODO See keymapType
type = with types; listOf str;
default = [];
description = ''
List of keystrokes on which the plugin should be loaded
'';
};
# priority = mkOption {
# type = types.int;
# default = [];
# description = ''
# Priority of the module. Influence the order of loading plugins.
# Highest values get loaded before.
# '';
# };
};
};
runtimeModule = types.submodule {
options = {
version = mkOption {
type = with types; nullOr str;
description = "Optional version of your runtime";
default = null;
example = "2025.10.16";
};
src = mkOption {
type = with types; either path attrs;
description = "Files to include in your runtime";
example = ./my-runtime;
};
extraOptions = mkOption {
type = types.attrs;
description = "Extra options to pass to `vimUtils.buildVimPlugin`";
default = {};
example.nvimRequireCheck = ["my-module.my-submodule"];
};
};
};
convertPkgToPlugin = pkg: {
plugin = pkg;
}; };
extraLuaPackagesType = extraLuaPackagesType =
(with types; functionTo package) (with types; functionTo (listOf package))
// { // {
merge = loc: defs: let merge = loc: defs: let
fnList = concatMap (def: def.value) defs; fnList = map (def: def.value) defs;
concatPackages = ps: fn: fn ps; concatPackages = ps: fn: fn ps;
in in
ps: concatMap (concatPackages ps) fnList; ps: concatMap (concatPackages ps) fnList;
}; };
mkDiffAlias = name:
(flip optionalString) ''
cat <<SH > $out/bin/${name}
#!${bash}/bin/bash
exec $out/bin/nvim -d "\''${@}"
SH
chmod 555 $out/bin/${name}
'';
defaultModule = { defaultModule = {
config, config,
lib, lib,
... ...
}: { }: let
pkg = pkgs.wrapNeovimUnstable config.package config.neovimOptions;
customLuaRC = modules.init.mkCustomLuaRc config.init;
plugins = concatLists [
(map modules.plugin.extract config.plugins)
(optional (! isNull config.runtime) (modules.runtime.mkPlugin config.runtime))
[(modules.sloth.mkPlugin version config.plugins)]
];
neovimOptions = pkgs.neovimUtils.makeNeovimConfig {
inherit (config) extraLuaPackages viAlias vimAlias;
inherit customLuaRC plugins;
wrapRc = customLuaRC != null;
# inherit customRC;
};
neovimPackage = pkg.overrideAttrs (final: super: {
postBuild =
super.postBuild
+ (mkDiffAlias "vimdiff" config.vimdiffAlias)
+ (mkDiffAlias "nvimdiff" config.nvimdiffAlias);
});
in {
options = { options = {
package = mkPackageOption pkgs "neovim-unwrapped" {}; package = mkPackageOption pkgs "neovim-unwrapped" {};
plugins = mkOption {
description = ''
List of plugins to enable in this installation
'';
type = with types; listOf (coercedTo package convertPkgToPlugin pluginModule);
default = [];
};
# Will probably be not needed # Will probably be not needed
# dependenciesExtraArgs = mkOption { # dependenciesExtraArgs = mkOption {
# default = {}; # default = {};
@ -192,23 +93,9 @@
default = _: []; default = _: [];
}; };
runtime = mkOption { init = modules.init.option;
type = types.nullOr runtimeModule; runtime = modules.runtime.option;
default = null; plugins = modules.plugin.option;
description = ''
Your runtime submodule. You can configure what files should be
indluded in your runtime
'';
};
init = mkOption {
default = null;
description = ''
init.lua configuration
'';
type = with types; nullOr (oneOf [path str initModule]);
example = ./init.lua;
};
viAlias = mkEnableOption "creation on `vi` alias"; viAlias = mkEnableOption "creation on `vi` alias";
vimAlias = mkEnableOption "creation on `vim` alias"; vimAlias = mkEnableOption "creation on `vim` alias";
@ -218,25 +105,21 @@
neovimOptions = mkOption { neovimOptions = mkOption {
type = types.attrs; type = types.attrs;
description = "The resulting configuration passed to `pkgs.wrapNeovimUnstable`"; description = "The resulting configuration passed to `pkgs.wrapNeovimUnstable`";
default = pkgs.neovimUtils.makeNeovimConfig { default = neovimOptions;
# inherit customRC;
# plugins = extractPlugins plugins;
# extraLuaPackages = ps:
# (extractLuaPackagesFn plugins ps) ++ (extraLuaPackages ps);
};
}; };
neovimPackage = mkOption { neovimPackage = mkOption {
type = types.package; type = types.package;
description = "The neovim package generated from your configuration."; description = "The neovim package generated from your configuration.";
# defaultText = lib.literalExpression "pkgs.hello"; # defaultText = lib.literalExpression "pkgs.hello";
default = pkgs.wrapNeovimUnstable config.package config.neovimOptions; default = neovimPackage;
}; };
}; };
}; };
in in
fix (sLib: { fix (sLib: {
inherit defaultModule; inherit defaultModule;
mkPluginsFromInputs = import ./mkPluginsFromInputs.nix {inherit pkgs;};
evalSlothModules = { evalSlothModules = {
modules ? [], modules ? [],

View File

@ -1,11 +1,11 @@
{ {pkgs, ...}: {
pkgs,
inputs, inputs,
predicate ? pkgs.lib.strings.hasPrefix "plugin-", predicate ? pkgs.lib.strings.hasPrefix "plugin-",
nameMap ? builtins.substring 7 (-1), nameMap ? builtins.substring 7 (-1),
buildVimPlugin ? pkgs.vimUtils.buildVimPlugin, buildVimPlugin ? pkgs.vimUtils.buildVimPlugin,
}: let }: let
inherit (builtins) attrNames filter foldl' mapAttrs; inherit (builtins) attrNames filter foldl' mapAttrs;
names = filter predicate (attrNames inputs); names = filter predicate (attrNames inputs);
mkPlugin = m: k: let mkPlugin = m: k: let
name = nameMap k; name = nameMap k;

View File

@ -0,0 +1,20 @@
{
pkgs,
inputs,
predicate ? pkgs.lib.strings.hasPrefix "plugin-",
nameMap ? builtins.substring 7 (-1),
buildVimPlugin ? pkgs.vimUtils.buildVimPlugin,
}: let
inherit (builtins) attrNames filter foldl' mapAttrs;
names = filter predicate (attrNames inputs);
mkPlugin = m: k: let
name = nameMap k;
pluginDef = {
inherit name;
src = inputs.${k};
};
in
m // {${name} = pluginDef;};
plugins = foldl' mkPlugin {} names;
in
mapAttrs (_: buildVimPlugin) plugins

136
lib/plugin/default.nix Normal file
View File

@ -0,0 +1,136 @@
{
pkgs,
callModule,
...
}: let
inherit (pkgs.lib) fix literalExample mkOption types;
modules = {
keymap = callModule ./keymap.nix {};
event = callModule ./event.nix {};
};
coercePkgToPlugin = pkg: {
plugin = pkg;
};
mkROBoolOption = default: description:
mkOption {
type = types.bool;
inherit default;
readOnly = true;
description = ''
Wether this plugin has ${description}.
'';
};
in
fix (self: {
module = types.submodule ({config, ...}: {
options = {
init = mkOption {
type = with types; nullOr (either path str);
default = null;
description = ''
The init configuration of your plugin.
This will be called before loading your plugin.
'';
};
config = mkOption {
type = with types; nullOr (either path str);
default = null;
description = ''
The configuration of your plugin.
This will be called after loading your plugin.
'';
};
optional = mkOption {
type = types.bool;
default = config.lazy || config.init != null;
example = true;
};
plugin = mkOption {
# TODO Type should allow `basicPluginType`
type = with types; nullOr package;
default = null;
description = ''
Ensure thoses plugins are loaded before the current one
'';
};
dependencies = mkOption {
type = with types; listOf package;
default = [];
description = ''
Ensure thoses plugins are loaded before the current one
'';
};
extraLuaPackages = mkOption {
type = with types; functionTo (listOf package);
example = literalExample "p: [p.nvim-nio]";
defaultText = literalExample "_: []";
default = _: [];
description = ''
Ensure those packages are available
'';
};
cmd = mkOption {
type = with types; listOf str;
default = [];
description = ''
List of commands on which the plugin should be loaded
'';
};
ft = mkOption {
type = with types; listOf str;
default = [];
description = ''
List of filetypes on which the plugin should be loaded
'';
};
keymaps = modules.keymap.option;
events = modules.event.option;
hasCommands = mkROBoolOption (config.cmd != []) "declared commands";
hasFileTypes = mkROBoolOption (config.ft != []) "file types";
hasKeymaps = mkROBoolOption (config.keymaps != []) "keymaps";
hasEvents = mkROBoolOption (config.events != []) "events";
lazy = mkOption {
type = types.bool;
default = with config; hasCommands || hasFileTypes || hasEvents || hasKeymaps;
example = true;
description = ''
Whether to enable loading lazily the plugin.
'';
};
# priority = mkOption {
# type = types.int;
# default = [];
# description = ''
# Priority of the module. Influence the order of loading plugins.
# Highest values get loaded before.
# '';
# };
};
});
option = mkOption {
description = ''
List of plugins to enable in this installation
'';
type = with types; listOf (coercedTo package coercePkgToPlugin self.module);
default = [];
};
extract = plugin: {
inherit (plugin) plugin optional;
};
})

60
lib/plugin/event.nix Normal file
View File

@ -0,0 +1,60 @@
{pkgs, ...}: let
inherit (builtins) elemAt isList isString mapAttrs match ;
inherit (pkgs.lib) fix mkOption splitString types ;
wrapArray = value:
if isList value
then value
else [value];
hasMatch = pattern: str: isList (match pattern str);
coerceToEvent = event: let
part = elemAt (splitString " " event);
value =
if ! isString event
then event
else if ! hasMatch ".* .*" event
then {name = event;}
else {
name = part 0;
pattern = part 1;
};
in
mapAttrs (_: wrapArray) value;
coerceToListOfEvent = events:
if isList events
then map coerceToEvent events
else [(coerceToEvent events)];
typeOrListOf = t: with types; coercedTo t (e: [e]) (listOf t);
in
fix (self: {
module = types.submodule {
options = {
name = mkOption {
type = typeOrListOf types.str;
default = "n";
example = "v";
description = ''
The name of the event
'';
};
pattern = mkOption {
type = typeOrListOf types.str;
example = "<C-n>";
description = ''
The pattern of the event
'';
};
};
};
option = mkOption {
type = with types; coercedTo (either str (listOf str)) coerceToListOfEvent (listOf self.module);
default = [];
description = ''
List of events on which the plugin should be loaded
'';
};
})

42
lib/plugin/keymap.nix Normal file
View File

@ -0,0 +1,42 @@
{pkgs, ...}: let
inherit (pkgs.lib) fix mkOption types;
coerceStrToKeymap = mapping: {
inherit mapping;
mode = ["n"];
};
typeOrListOf = t: with types; coercedTo t (e: [e]) (listOf t);
modeType = types.enum ["n" "v" "s" "i" "o" "x"];
in
fix (self: {
module = types.submodule {
options = {
mode = mkOption {
type = typeOrListOf modeType;
default = "n";
example = "v";
description = ''
The mode of the keymap.
'';
};
mapping = mkOption {
type = typeOrListOf types.str;
example = "<C-n>";
description = ''
The actual keymap
'';
};
};
};
option = mkOption {
type = with types; listOf (coercedTo str coerceStrToKeymap self.module);
default = [];
description = ''
List of keystrokes on which the plugin should be loaded
'';
};
})

53
lib/runtime.nix Normal file
View File

@ -0,0 +1,53 @@
{pkgs, ...}: let
inherit (pkgs) vimUtils;
inherit (pkgs.lib) fix mkOption types;
in fix (self: {
module = types.submodule {
options = {
src = mkOption {
type = with types; either path attrs;
description = "Files to include in your runtime";
example = ./my-runtime;
};
version = mkOption {
type = with types; nullOr str;
description = "Optional version of your runtime";
default = null;
example = "2025.10.16";
};
extraOptions = mkOption {
type = with types; attrsOf anything;
description = "Extra options to pass to `vimUtils.buildVimPlugin`";
default = {};
example.nvimRequireCheck = ["my-module.my-submodule"];
};
};
};
option = mkOption {
type = types.nullOr self.module;
default = null;
description = ''
Your runtime submodule. You can configure what files should be
indluded in your runtime
'';
};
mkPlugin = opts: let
inherit (opts) src version extraOptions;
in
vimUtils.buildVimPlugin (
extraOptions
// {inherit src;}
// (
if isNull version
then {name = "runtime";}
else {
pname = "runtime";
inherit version;
}
)
);
})

80
lib/sloth.nix Normal file
View File

@ -0,0 +1,80 @@
{pkgs, ...}: let
inherit (builtins) foldl' isPath;
inherit (pkgs) vimUtils;
inherit (pkgs.lib) fileContents fix optionalAttrs;
fs = pkgs.lib.fileset;
lua = import ./lua.nix {};
versionLua = version: with lua; nix2lua (return (lambda (return version)));
pluginsLuaDef = plugins:
with lua; nix2lua (return (foldl' pluginLuaDef {} plugins));
textOrContent = content:
if isPath content
then fileContents content
else content;
pluginLuaDef = memo: plugin: let
mkTypeFn = type: let
content = textOrContent plugin.${type};
in
optionalAttrs (! isNull plugin.${type}) {
${type} = with lua; lambda (raw content);
};
pluginName = plugin:
if plugin ? pname
then plugin.pname
else plugin.name;
name = pluginName plugin.plugin;
in
memo
// {
${name} =
{
name = pluginName plugin.plugin;
dependencies = map pluginName plugin.dependencies;
}
// (mkTypeFn "init")
// (mkTypeFn "config")
// (optionalAttrs plugin.lazy {
lazy = true;
})
// (optionalAttrs plugin.hasCommands {
inherit (plugin) cmd;
})
// (optionalAttrs plugin.hasFileTypes {
inherit (plugin) ft;
})
// (optionalAttrs plugin.hasEvents {
inherit (plugin) events;
})
// (optionalAttrs plugin.hasKeymaps {
inherit (plugin) keymaps;
});
};
in
fix (self: {
mkPlugin = version: plugins:
vimUtils.buildVimPlugin {
inherit version;
pname = "sloth-flake";
src = fs.toSource {
root = ../.;
fileset = ../lua/sloth-flake;
};
nvimRequireCheck = "sloth-flake";
buildPhase = ''
dir=lua/sloth-flake
cat <<'LUA' > $dir/dependencies.lua
${pluginsLuaDef plugins}
LUA
cat <<'LUA' > $dir/version.lua
${versionLua version}
LUA
'';
};
})