Compare commits

...

14 Commits

22 changed files with 944 additions and 597 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
.direnv .direnv
.envrc .envrc
result

View File

@ -1 +1 @@
0.0.10 0.1.0

View File

@ -49,44 +49,10 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_2": {
"locked": {
"lastModified": 1660438583,
"narHash": "sha256-rJUTYxFKlWUJI3njAwEc1pKAVooAViZGJvsgqfh/q/E=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "bbd8f7cd87d0b29294ef3072ffdbd61d60f05da4",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"root": { "root": {
"inputs": { "inputs": {
"flake-parts": "flake-parts", "flake-parts": "flake-parts",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs"
"yants": "yants"
}
},
"yants": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1686863218,
"narHash": "sha256-kooxYm3/3ornWtVBNHM3Zh020gACUyFX2G0VQXnB+mk=",
"owner": "divnix",
"repo": "yants",
"rev": "8f0da0dba57149676aa4817ec0c880fbde7a648d",
"type": "github"
},
"original": {
"owner": "divnix",
"repo": "yants",
"type": "github"
} }
} }
}, },

View File

@ -1,19 +1,20 @@
{ {
description = "My neovim configuration"; description = "Sloth-flake - a nix power neovim plugin manager";
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
yants.url = "github:divnix/yants";
flake-parts.url = "github:hercules-ci/flake-parts"; flake-parts.url = "github:hercules-ci/flake-parts";
}; };
outputs = {flake-parts, ...} @ inputs: let outputs = {flake-parts, ...} @ inputs: (flake-parts.lib.mkFlake {inherit inputs;} {
in (flake-parts.lib.mkFlake {inherit inputs;} {
systems = ["x86_64-linux"]; systems = ["x86_64-linux"];
imports = [ imports = with flake-parts.flakeModules; [
flakeModules
./dev ./dev
./flakeModule.nix
./lib ./lib
./tests
]; ];
}); });
} }

67
flakeModule.nix Normal file
View File

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

View File

@ -1,19 +1,11 @@
{ {self, ...}: let
self,
inputs,
...
}: let
inherit (builtins) readFile replaceStrings; inherit (builtins) readFile replaceStrings;
versionFile = replaceStrings ["\n"] [""] (readFile ../VERSION); versionFile = replaceStrings ["\n"] [""] (readFile ../VERSION);
version = dirty =
if self.sourceInfo ? dirtyShortRev if self.sourceInfo ? dirtyShortRev
then "${versionFile}-${self.sourceInfo.dirtyShortRev}" then "-${self.sourceInfo.dirtyShortRev}"
else versionFile; else "";
version = "${versionFile}${dirty}";
types = import ./types.nix {inherit (inputs) yants;};
in { in {
flake.lib = { flake.mkLib = import ./lib.nix {inherit version;};
mkNeovimPkg = import ./mkNeovimPkg.nix {inherit version types;};
mkPluginsFromInputs = import ./mkPluginsFromInputs.nix;
};
} }

View File

@ -1,200 +0,0 @@
{
pkgs,
lib,
vimUtils,
dependenciesExtraArgs,
types,
...
}: let
inherit (builtins) foldl' isPath isList isString mapAttrs match elemAt;
inherit (lib.attrsets) attrNames optionalAttrs;
inherit (lib.lists) concatMap;
inherit (lib.strings) fileContents splitString;
lua = callPackage ./lua.nix {};
callPackage = lib.callPackageWith (pkgs // dependenciesExtraArgs);
hasMatch = pattern: str: isList (match pattern str);
wrapArray = value:
if isList value
then value
else [value];
defaultPlugin = {
enabled = true;
init = null;
config = null;
dependencies = [];
lazy = false;
cmd = [];
ft = [];
events = [];
keymaps = [];
};
remotePluginToNeovimPlugin = p:
vimUtils.buildVimPlugin rec {
inherit (p) src name;
pname = name;
};
defaultKeymap = {mode = "n";};
normalizeKeymap = keymap: let
value = (
if isString keymap
then {mapping = keymap;}
else keymap
);
in
mapAttrs (_: wrapArray) (defaultKeymap // value);
normalizeKeymaps = keymaps:
if isList keymaps
then map normalizeKeymap keymaps
else [(normalizeKeymap keymaps)];
normalizeEvent = event: let
value =
if ! isString event
then event
else if ! hasMatch ".* .*" event
then {name = event;}
else let
part = elemAt (splitString " " event);
in {
name = part 0;
pattern = part 1;
};
in
mapAttrs (_: wrapArray) value;
normalizeEvents = events:
if isList events
then map normalizeEvent events
else [(normalizeEvent events)];
withPluginDefaults = dep: defaultPlugin // dep;
normalizePlugin = d: let
dep = types.dependency d;
plugin =
if ! dep ? plugin
then {plugin = dep;}
else let
inherit (dep) plugin;
in
if attrNames plugin == ["name" "src"]
then dep // {plugin = remotePluginToNeovimPlugin plugin;}
else dep;
p = withPluginDefaults plugin;
in
p
// rec {
hasCommands = p.cmd != [];
hasFileTypes = p.ft != [];
keymaps = normalizeKeymaps p.keymaps;
hasKeymaps = p.keymaps != [];
events = normalizeEvents p.events;
hasEvents = p.events != [];
lazy = p.lazy || hasCommands || hasFileTypes || hasEvents || hasKeymaps;
optional = lazy || p.init != null;
};
normalizeOrImportPlugin = dep:
if isPath dep
then normalizePlugins (callPackage dep {})
else [(normalizePlugin dep)];
normalizePlugins = concatMap normalizeOrImportPlugin;
mkRuntimePlugin = {
src,
version,
...
}@opts:
vimUtils.buildVimPlugin ({
inherit src;
}
// (optionalAttrs (opts ? nvimRequireCheck) {
inherit (opts) nvimRequireCheck;
})
// (optionalAttrs (isNull version) {
name = "runtime";
})
// (optionalAttrs (! isNull version) {
inherit version;
pname = "runtime";
}));
mkSlothFlakePlugin = version: plugins:
vimUtils.buildVimPlugin {
inherit version;
pname = "sloth-flake";
src = with lib.fileset;
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
'';
};
versionLua = version: with lua; nix2lua (return (lambda (return version)));
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;
});
};
pluginsLuaDef = plugins:
with lua; nix2lua (return (foldl' pluginLuaDef {} plugins));
in {
inherit normalizePlugin;
inherit normalizePlugins;
inherit mkSlothFlakePlugin;
inherit mkRuntimePlugin;
inherit textOrContent;
}

26
lib/evalModules.nix Normal file
View File

@ -0,0 +1,26 @@
{
pkgs,
sloth,
...
}: let
inherit (pkgs.lib) evalModules;
sLib = sloth.lib;
in {
evalSlothModules = {
modules ? [],
specialArgs ? {},
}: let
moduleConfig = evalModules {
specialArgs = specialArgs // {inherit pkgs;};
modules = modules ++ [sLib.defaultModule];
class = "sloth";
};
in
moduleConfig.config;
mkNeovimPkg = {
modules ? [],
specialArgs ? {},
}:
(sLib.evalSlothModules {inherit modules specialArgs;}).neovimPackage;
}

16
lib/lib.nix Normal file
View File

@ -0,0 +1,16 @@
{version, ...}: {pkgs, ...}:
pkgs.lib.fix (lib: let
inherit (pkgs.lib) callPackageWith mergeAttrsList;
callModule = callPackageWith {
inherit pkgs callModule;
sloth = {inherit lib version;};
};
callModules = modules: mergeAttrsList (map (path: callModule path {}) modules);
in
callModules [
./evalModules.nix
./mkPluginsFromInputs.nix
./modules
])

View File

@ -1,92 +1,132 @@
{...}: let {pkgs, ...}: let
inherit (builtins) match isNull typeOf concatStringsSep attrNames concatMap; inherit
(builtins)
attrNames
concatStringsSep
filter
isAttrs
isBool
isFloat
isInt
isList
isPath
isNull
isString
match
toJSON
typeOf
;
inherit
(pkgs.lib)
assertMsg
concatStrings
fix
imap0
isDerivation
mapAttrsToList
optionalString
splitString
toPretty
;
commaJoin = concatStringsSep ", "; commaJoin = concatStringsSep ", ";
wrapNotNull' = val: value:
if isNull val
then []
else [value];
wrapNotNull = val: wrapNotNull' val val;
toLua = { renderLua = {
multiline ? true,
indent ? "",
asBindings ? false,
} @ args: v: let
innerIndent = "${indent} ";
introSpace =
if multiline
then "\n${innerIndent}"
else " ";
outroSpace =
if multiline
then "\n${indent}"
else " ";
innerArgs =
args
// {
indent =
if asBindings
then indent
else innerIndent;
asBindings = false;
};
ast = { ast = {
raw = {data, ...}: data; render = args: data: let
return = {data, ...}: "return ${nix2lua data}"; astType = data.__ast;
fn = { in
if ast ? ${astType}
then ast.${astType} args data
else abort ''Unknown ast type ${astType}'';
raw = args: {
data,
__no_indent ? false,
...
}: let
content =
if __no_indent
then data
else concatStringsSep "\n" (imap0 (idx: line: "${optionalString (idx != 0) args.indent}${line}") (splitString "\n" data));
in
content;
return = args: {data, ...}: "return ${renderLua args data}";
fn = args: {
data, data,
name, name,
args, args,
... ...
}: "function ${name}(${commaJoin args})\n${nix2lua data}\nend"; }: "function ${name}(${commaJoin args})${introSpace}${renderLua innerArgs data}${outroSpace}end";
}; };
type = rec { generatedBindings = assert assertMsg (badBindingNames == []) "Bad Lua var names: ${toPretty {} badBindingNames}";
null = _: "nil"; concatStrings (mapAttrsToList (key: value: "${indent}${key} = ${renderLua innerArgs value}\n") v);
string = data: ''"${data}"'';
path = string;
lambda = data: builtins.trace "Skipping function" null;
int = data: toString data;
bool = data: concatItems = concatStringsSep ",${introSpace}";
if data varNameRe = "[[:alpha:]_][[:alnum:]_]*";
then "true" matchBindingName = match "${varNameRe}(\\.${varNameRe})*";
else "false"; badBindingNames = filter (name: matchBindingName name == null) (attrNames v);
ast = data: let luaKey = key:
astType = data.__ast; if match varNameRe key == null
in then "[${toJSON key}]"
if toLua.ast ? ${astType}
then toLua.ast.${astType} data
else abort ''Unknown ast type ${astType}'';
list = data: let
nix2luaList = val: wrapNotNull (nix2lua val);
listContent = commaJoin (concatMap nix2luaList data);
in "{ ${listContent} }";
set = data: let
mkKeyValue = key: let
value = data.${key};
luaKey =
if isNull (match "[a-zA-Z_][a-zA-Z_0-9]+" key)
then ''["${key}"]''
else key; else key;
luaValue = nix2lua value;
withSpaces = val: optionalString (val != "") "${introSpace}${val}${outroSpace}";
mkKV = key: value: "${luaKey key} = ${renderLua innerArgs value}";
in in
wrapNotNull' luaValue '' if asBindings
${luaKey} = ${luaValue}, then generatedBindings
''; else if isNull v
attrsContent = concatMap mkKeyValue (attrNames data); then "nil"
in '' else if isInt v || isFloat v || isString v || isBool v
{ then toJSON v
${concatStringsSep "" attrsContent}} else if isPath v || isDerivation v
''; then toJSON "${v}"
}; else if isList v
}; then "{${withSpaces (concatItems (map (renderLua innerArgs) v))}}"
else if v ? __ast
then ast.render args v
else if isAttrs v
then "{${withSpaces (concatItems (mapAttrsToList mkKV v))}}"
else abort "generators.renderLua: type ${typeOf v} is unsupported";
newAst = type: set: set // {__ast = type;}; newAst = type: set: set // {__ast = type;};
nix2lua = data: let
type = typeOf data;
in in
if data ? __ast fix (lua: {
then toLua.type.ast data inherit renderLua;
else if toLua.type ? ${type}
then toLua.type.${type} data writeLua = name: value: pkgs.writeText name (lua.renderLua {} value);
else abort ''Type "${type}"'';
in rec {
inherit nix2lua;
raw = data: newAst "raw" {inherit data;}; raw = data: newAst "raw" {inherit data;};
functionWithArgs = name: args: data: function = name: args: data: newAst "fn" {inherit name data args;};
newAst "fn" {
inherit name data args;
};
function = name: data: functionWithArgs name [] data; lambda = lua.function "";
lambda = function "";
return = data: newAst "return" {inherit data;}; return = data: newAst "return" {inherit data;};
} })

View File

@ -1,115 +0,0 @@
{
version,
types,
}: {
pkgs,
package ? pkgs.neovim-unwrapped,
dependencies ? [],
dependenciesExtraArgs ? {},
extraLuaPackages ? (_: []),
runtime ? null,
init ? null,
viAlias ? false,
vimAlias ? false,
vimdiffAlias ? false,
nvimdiffAlias ? false,
...
} @ config: let
inherit (builtins) isString isPath map;
inherit (pkgs) callPackage bash lib;
inherit (lib.strings) optionalString;
inherit (lib.lists) concatMap optional;
inherit (lib.trivial) flip;
inherit (lib.attrsets) optionalAttrs;
deps = callPackage ./deps.nix {inherit dependenciesExtraArgs types;};
normalizedPlugins = deps.normalizePlugins dependencies;
sloth-flake = deps.mkSlothFlakePlugin version normalizedPlugins;
runtimePlugin = deps.mkRuntimePlugin runtime;
plugins =
normalizedPlugins
++ (
deps.normalizePlugins
([sloth-flake] ++ (optional (runtime != null) runtimePlugin))
);
extractPlugin = p: {inherit (p) optional plugin;};
extractPlugins = map extractPlugin;
extractLuaPackageFn = plugin:
optional (plugin ? extraLuaPackages) plugin.extraLuaPackages;
extractLuaPackagesFn = plugins: let
fnList = concatMap extractLuaPackageFn plugins;
concatPackages = ps: fn: fn ps;
in
ps: concatMap (concatPackages ps) fnList;
buildInit = {
init ? null,
postInit ? null,
config ? null,
}: let
initStr = optionalString (! isNull init) ''
(function()
${deps.textOrContent init}
end)();
'';
slothCall =
if isNull postInit
then "require('sloth-flake').setup {}"
else ''
require('sloth-flake').setup {
post_init = function()
${deps.textOrContent postInit}
end,
};
'';
configStr = optionalString (! isNull config) ''
(function()
${deps.textOrContent config}
end)()
'';
in ''
-- Generated by sloth-flake
${initStr}
${slothCall}
${configStr}
'';
customRC =
if isString init || isPath init
then deps.textOrContent init
else buildInit (optionalAttrs (! isNull init) init);
neovimConfig =
pkgs.neovimUtils.makeNeovimConfig {
inherit customRC;
plugins = extractPlugins plugins;
extraLuaPackages = ps:
(extractLuaPackagesFn plugins ps) ++ (extraLuaPackages ps);
}
// {luaRcContent = customRC;};
params =
removeAttrs neovimConfig ["manifestRc" "neovimRcContent"]
// {inherit viAlias vimAlias;};
pkg = pkgs.wrapNeovimUnstable package params;
mkDiffAlias = name:
(flip optionalString) ''
cat <<SH > $out/bin/${name}
#!${bash}/bin/bash
exec $out/bin/nvim -d "\''${@}"
SH
chmod 555 $out/bin/${name}
'';
in
builtins.seq (types.mkNeovimPkgOptions config) (pkg.overrideAttrs (final: super: {
postBuild =
super.postBuild
+ (mkDiffAlias "vimdiff" vimdiffAlias)
+ (mkDiffAlias "nvimdiff" nvimdiffAlias);
}))

View File

@ -1,11 +1,12 @@
{ {pkgs, ...}: let
pkgs, inherit (builtins) attrNames filter foldl' mapAttrs;
in {
mkPluginsFromInputs = {
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;
names = filter predicate (attrNames inputs); names = filter predicate (attrNames inputs);
mkPlugin = m: k: let mkPlugin = m: k: let
name = nameMap k; name = nameMap k;
@ -17,4 +18,5 @@
m // {${name} = pluginDef;}; m // {${name} = pluginDef;};
plugins = foldl' mkPlugin {} names; plugins = foldl' mkPlugin {} names;
in in
mapAttrs (_: buildVimPlugin) plugins mapAttrs (_: buildVimPlugin) plugins;
}

133
lib/modules/default.nix Normal file
View File

@ -0,0 +1,133 @@
{
pkgs,
callModule,
...
}: let
inherit (builtins) concatLists;
inherit (pkgs) bash lib;
inherit
(lib)
concatMap
flip
literalExample
mkEnableOption
mkOption
mkPackageOption
optional
optionalString
types
;
modules = {
init = callModule ./init.nix {};
plugin = callModule ./plugin {};
runtime = callModule ./runtime.nix {};
sloth = callModule ./sloth.nix {};
};
extraLuaPackagesType =
(with types; functionTo (listOf package))
// {
merge = loc: defs: let
fnList = map (def: def.value) defs;
concatPackages = ps: fn: fn ps;
in
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}
'';
in {
defaultModule = {
config,
lib,
...
}: let
pkg = pkgs.wrapNeovimUnstable config.package config.neovimOptions;
neovimOptions = pkgs.neovimUtils.makeNeovimConfig rec {
inherit (config) extraLuaPackages viAlias vimAlias;
customLuaRC = config.init.finalContent;
wrapRc = customLuaRC != null;
plugins = concatLists [
(map modules.plugin.extract config.plugins)
(optional (! isNull config.runtime) config.runtime.package)
[config.slothPlugin]
];
};
neovimPackage = pkg.overrideAttrs (final: super: {
postBuild =
super.postBuild
+ (mkDiffAlias "vimdiff" config.vimdiffAlias)
+ (mkDiffAlias "nvimdiff" config.nvimdiffAlias);
});
in {
options = {
package = mkPackageOption pkgs "neovim-unwrapped" {};
extraLuaPackages = mkOption {
description = ''
function to define extra lua packages that should be included in
neovim environment.
'';
type = extraLuaPackagesType;
example = literalExample "p: [p.nvim-nio]";
defaultText = literalExample "_: []";
default = _: [];
};
init = modules.init.option;
runtime = modules.runtime.option;
plugins = modules.plugin.option;
viAlias = mkEnableOption "creation on `vi` alias";
vimAlias = mkEnableOption "creation on `vim` alias";
vimdiffAlias = mkEnableOption "creation on `vimdiff` alias";
nvimdiffAlias = mkEnableOption "creation on `nvimdiff` alias";
pluginLuaDefinitions = mkOption {
description = ''
All lua definitions of plugins. Used by sloth vim plugin
'';
type = with types; attrsOf anything;
readOnly = true;
internal = true;
default = modules.sloth.mkPluginLuaDefinitions config.plugins;
};
slothPlugin = mkOption {
description = ''
The resulted sloth plugin
'';
type = types.package;
readOnly = true;
internal = true;
default = modules.sloth.mkSlothPlugin config.pluginLuaDefinitions;
};
neovimOptions = mkOption {
type = types.attrs;
readOnly = true;
description = "The resulting configuration passed to `pkgs.wrapNeovimUnstable`";
default = neovimOptions;
};
neovimPackage = mkOption {
type = types.package;
description = "The neovim package generated from your configuration.";
readOnly = true;
# defaultText = lib.literalExpression "pkgs.hello";
default = neovimPackage;
};
};
};
}

107
lib/modules/init.nix Normal file
View File

@ -0,0 +1,107 @@
{
pkgs,
callModule,
...
}: let
inherit (builtins) isAttrs isPath isString readFile;
inherit (pkgs.lib) fileContents fix mkOption optionalAttrs optionalString types;
lua = callModule ../lua.nix {};
coerceToModule = value:
if isAttrs value
then value
else {finalContent = textOrContent value;};
textOrContent = content:
if isPath content
then fileContents content
else content;
buildInit = {
init ? null,
postInit ? null,
config ? null,
...
}: let
initStr = optionalString (! isNull init) ''
(function()
${textOrContent init}
end)();
'';
postInitContent = optionalAttrs (! isNull postInit) {
post_init = lua.lambda [] (lua.raw (textOrContent postInit));
};
slothCall = "require('sloth-flake').setup ${lua.renderLua {} postInitContent};";
configStr = optionalString (! isNull config) ''
(function()
${textOrContent config}
end)()
'';
in ''
-- Generated by sloth-flake
${initStr}
${slothCall}
${configStr}
'';
in
fix (self: {
module = types.submodule ({config, ...}: {
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
'';
};
finalContent = mkOption {
type = with types; nullOr str;
default = self.mkCustomLuaRc config;
description = ''
The resulted init package.
'';
};
};
});
option = mkOption {
default = null;
description = ''
init.lua configuration
'';
type = with types; coercedTo (nullOr (oneOf [path str])) coerceToModule self.module;
example = ./init.lua;
};
mkCustomLuaRc = init:
if isString init
then init
else if isPath init
then readFile init
else if isAttrs init
then buildInit init
else null;
})

View File

@ -0,0 +1,189 @@
{
pkgs,
callModule,
...
}: let
inherit (builtins) isPath;
inherit (pkgs.lib) fileContents fix literalExample mergeAttrsList mkOption optionalAttrs types;
lua = callModule ../../lua.nix {};
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}.
'';
};
textOrContent = content:
if isPath content
then fileContents content
else content;
getPluginName = plugin: plugin.pname or plugin.name;
mkLuaDefinition = plugin: let
mkTypeFn = type: let
content = textOrContent plugin.${type};
in
optionalAttrs (! isNull plugin.${type}) {
${type} = with lua; lambda [] (raw content);
};
name = getPluginName plugin.plugin;
in
mergeAttrsList ([
{
inherit name;
dependencies = map getPluginName plugin.dependencies;
}
(mkTypeFn "init")
(mkTypeFn "config")
]
++ (with plugin; [
(optionalAttrs lazy {lazy = true;})
(optionalAttrs hasCommands {inherit cmd;})
(optionalAttrs hasFileTypes {inherit ft;})
(optionalAttrs hasEvents {inherit events;})
(optionalAttrs hasKeymaps {inherit keymaps;})
]));
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 old `basicPluginType` ({name:<str>, src:<source>})?
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.
'';
};
pluginName = mkOption {
type = types.str;
readOnly = true;
internal = true;
default = getPluginName config.plugin;
description = ''
Name of the plugin.
'';
};
luaDefinition = mkOption {
type = with types; attrsOf anything;
readOnly = true;
internal = true;
default = mkLuaDefinition config;
description = ''
Lua definition of 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;
};
})

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
'';
};
})

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
'';
};
})

62
lib/modules/runtime.nix Normal file
View File

@ -0,0 +1,62 @@
{pkgs, ...}: let
inherit (pkgs) vimUtils;
inherit (pkgs.lib) fix mkOption types;
in
fix (self: {
module = types.submodule ({config, ...}: {
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"];
};
package = mkOption {
type = types.package;
description = "The resulted runtime package";
readOnly = true;
default = self.mkPlugin config;
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;
}
)
);
})

36
lib/modules/sloth.nix Normal file
View File

@ -0,0 +1,36 @@
{
pkgs,
sloth,
callModule,
...
}: let
inherit (pkgs) vimUtils;
inherit (pkgs.lib) fix listToAttrs nameValuePair;
fs = pkgs.lib.fileset;
lua = callModule ../lua.nix {};
defsFile = luaDefs: with lua; writeLua "sloth-plugins-definitions.lua" (return luaDefs);
versionFile = with lua; writeLua "sloth-version.lua" (return (lambda [] (return sloth.version)));
in
fix (self: {
mkSlothPlugin = luaDefs:
vimUtils.buildVimPlugin {
inherit (sloth) version;
pname = "sloth-flake";
src = fs.toSource {
root = ../..;
fileset = ../../lua/sloth-flake;
};
nvimRequireCheck = "sloth-flake";
buildPhase = ''
dir=lua/sloth-flake
ln -s ${defsFile luaDefs} $dir/dependencies.lua
ln -s ${versionFile} $dir/version.lua
'';
};
mkPluginLuaDefinitions = plugins: let
extractDef = plugin: nameValuePair plugin.pluginName plugin.luaDefinition;
in
listToAttrs (map extractDef plugins);
})

View File

@ -1,138 +0,0 @@
{yants, ...}: let
stringList = with yants; list string;
stringOrStringList = with yants; either string stringList;
stringOrStringListOr = type:
with yants;
option (eitherN [string type (list (either string type))]);
in rec {
# The runtime object
runtimeType = with yants;
openStruct "runtime" {
# The version of the runtime
version = option string;
# The content of the runtime directory
src = any;
};
neovimInitType = with yants;
struct "neovimInit" {
# Lua code to call before plugins loaded
init = option (either string path);
# Lua code called after init but before import
postInit = option (either string path);
# Lua code called after all plugins are loaded
config = option (either string path);
};
# As simple remote plugin definition
basicPluginType = with yants;
struct "basicPlugin" {
# The name of your plugin.
name = string;
# The sources of your plugin
# TODO What is the type of a source ?
src = any;
};
eventType = with yants;
struct "event" {
# The name of the event
name = stringOrStringList;
# The pattern of the event
pattern = stringOrStringList;
};
keymapType = with yants;
struct "keymap" {
# The mode of the keymap
mode = option stringOrStringList;
# The mapping of the keymap
mapping = stringOrStringList;
};
# The plugin type of dependencies
pluginType = with yants;
struct "plugin" {
# Whether this plugin should be enabled. This option allows specific
# plugins to be disabled.
# enable = option bool;
# The init configuration of your plugin.
# This should be called before loading your plugin.
init = option (either path string);
# The configuration of your plugin.
# This should be called after loading your plugin.
config = option (either path string);
# Ensure thoses plugins are loaded before the current one
plugin = either drv basicPluginType;
# Ensure thoses plugins are loaded before the current one
dependencies = option (list drv);
# Ensure those packages are available
extraLuaPackages = option function;
# Should this plugin be load lazily ?
lazy = option bool;
# List of events on which the plugin should be loaded
events = option (stringOrStringListOr eventType);
# List of commands on which the plugin should be loaded
cmd = option stringList;
# List of filetypes on which the plugin should be loaded
ft = option stringList;
# List of keystrokes on which the plugin should be loaded
keymaps = option (stringOrStringListOr keymapType);
# Priority of the module. Influence the order of loading plugins.
# Highest values get loaded before.
# priority = option int;
};
# A dependency.
# TODO Complete doc
dependency = with yants; eitherN [path drv pluginType];
mkNeovimPkgOptions = with yants;
struct "mkNeovimPkgOptions" {
# The configuration of mkNeovimPkg
pkgs = attrs any;
# The neovim package to wrap with your conifguration.
# Default is pkgs.neovim-unwrapped
package = option drv;
# init.lua configuration
init = option (eitherN [string path neovimInitType]);
# An array of dependencies.
dependencies = option (list dependency);
# Extra argument to pass to dependencies files
dependenciesExtraArgs = option (attrs any);
# Ensure those packages are available
extraLuaPackages = option function;
# Runtime configuration
runtime = option runtimeType;
# Create a vi alias
viAlias = option bool;
# Create a vim alias
vimAlias = option bool;
# Create a vimdiff alias to run neovim in diff mode
vimdiffAlias = option bool;
# Create a nvimdiff alias to run neovim in diff mode
nvimdiffAlias = option bool;
};
}

32
tests/default.nix Normal file
View File

@ -0,0 +1,32 @@
{
perSystem = {pkgs, ...}: let
lua = import ../lib/lua.nix {inherit pkgs;};
diffFiles = actual: expected:
pkgs.runCommandWith {
name = "diffFiles";
derivationArgs.nativeBuildInputs = [pkgs.diffutils];
}
''
diff -u ${expected} ${actual}
echo "Comparaison successful."
touch $out
'';
in {
checks.luaRender =
diffFiles ./luaRender.lua
<| lua.writeLua "debug.lua" {
a.b.c = 42;
list = [42 4.2 true false null];
fn = lua.lambda [] <| lua.raw "print('hello world')";
math.succ = lua.function "" ["n"] <| lua.return <| lua.raw "n + 1";
non-var-name = 42;
multilineFunc =
lua.function "" []
<| lua.raw ''
print("hello world")
print("goodbye world")
'';
};
};
}

28
tests/luaRender.lua Normal file
View File

@ -0,0 +1,28 @@
{
a = {
b = {
c = 42
}
},
fn = function ()
print('hello world')
end,
list = {
42,
4.2,
true,
false,
nil
},
math = {
succ = function (n)
return n + 1
end
},
multilineFunc = function ()
print("hello world")
print("goodbye world")
end,
["non-var-name"] = 42
}