diff --git a/.gitignore b/.gitignore index f3bf9c3..8a2e0a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .direnv .envrc +result diff --git a/flake.lock b/flake.lock index 1333f2a..b627cd3 100644 --- a/flake.lock +++ b/flake.lock @@ -49,44 +49,10 @@ "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": { "inputs": { "flake-parts": "flake-parts", - "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" + "nixpkgs": "nixpkgs" } } }, diff --git a/flake.nix b/flake.nix index 4779fb8..0067efb 100644 --- a/flake.nix +++ b/flake.nix @@ -1,19 +1,20 @@ { - description = "My neovim configuration"; + description = "Sloth-flake - a nix power neovim plugin manager"; inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - yants.url = "github:divnix/yants"; flake-parts.url = "github:hercules-ci/flake-parts"; }; - outputs = {flake-parts, ...} @ inputs: let - in (flake-parts.lib.mkFlake {inherit inputs;} { + outputs = {flake-parts, ...} @ inputs: (flake-parts.lib.mkFlake {inherit inputs;} { systems = ["x86_64-linux"]; - imports = [ + imports = with flake-parts.flakeModules; [ + flakeModules ./dev + ./flakeModule.nix ./lib + ./tests ]; }); } diff --git a/flakeModule.nix b/flakeModule.nix new file mode 100644 index 0000000..8bac535 --- /dev/null +++ b/flakeModule.nix @@ -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 = "sloth’s 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; + }); + }; +} diff --git a/lib/default.nix b/lib/default.nix index 15f3192..9ebb611 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -1,19 +1,11 @@ -{ - self, - inputs, - ... -}: let +{self, ...}: let inherit (builtins) readFile replaceStrings; versionFile = replaceStrings ["\n"] [""] (readFile ../VERSION); - version = + dirty = if self.sourceInfo ? dirtyShortRev - then "${versionFile}-${self.sourceInfo.dirtyShortRev}" - else versionFile; - - types = import ./types.nix {inherit (inputs) yants;}; + then "-${self.sourceInfo.dirtyShortRev}" + else ""; + version = "${versionFile}${dirty}"; in { - flake.lib = { - mkNeovimPkg = import ./mkNeovimPkg.nix {inherit version types;}; - mkPluginsFromInputs = import ./mkPluginsFromInputs.nix; - }; + flake.mkLib = import ./lib.nix {inherit version;}; } diff --git a/lib/deps.nix b/lib/deps.nix deleted file mode 100644 index 8693ac6..0000000 --- a/lib/deps.nix +++ /dev/null @@ -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; -} diff --git a/lib/evalModules.nix b/lib/evalModules.nix new file mode 100644 index 0000000..c4d1803 --- /dev/null +++ b/lib/evalModules.nix @@ -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; +} diff --git a/lib/lib.nix b/lib/lib.nix new file mode 100644 index 0000000..1111993 --- /dev/null +++ b/lib/lib.nix @@ -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 + ]) diff --git a/lib/lua.nix b/lib/lua.nix index 5455d1e..942c5d8 100644 --- a/lib/lua.nix +++ b/lib/lua.nix @@ -1,92 +1,132 @@ -{...}: let - inherit (builtins) match isNull typeOf concatStringsSep attrNames concatMap; +{pkgs, ...}: let + 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 ", "; - 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 = { - raw = {data, ...}: data; - return = {data, ...}: "return ${nix2lua data}"; - fn = { + render = args: data: let + astType = data.__ast; + 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, name, args, ... - }: "function ${name}(${commaJoin args})\n${nix2lua data}\nend"; + }: "function ${name}(${commaJoin args})${introSpace}${renderLua innerArgs data}${outroSpace}end"; }; - type = rec { - null = _: "nil"; - string = data: ''"${data}"''; - path = string; - lambda = data: builtins.trace "Skipping function" null; - int = data: toString data; + generatedBindings = assert assertMsg (badBindingNames == []) "Bad Lua var names: ${toPretty {} badBindingNames}"; + concatStrings (mapAttrsToList (key: value: "${indent}${key} = ${renderLua innerArgs value}\n") v); - bool = data: - if data - then "true" - else "false"; + concatItems = concatStringsSep ",${introSpace}"; + varNameRe = "[[:alpha:]_][[:alnum:]_]*"; + matchBindingName = match "${varNameRe}(\\.${varNameRe})*"; + badBindingNames = filter (name: matchBindingName name == null) (attrNames v); - ast = data: let - astType = data.__ast; - in - if toLua.ast ? ${astType} - then toLua.ast.${astType} data - else abort ''Unknown ast type ${astType}''; + luaKey = key: + if match varNameRe key == null + then "[${toJSON key}]" + else key; - list = data: let - nix2luaList = val: wrapNotNull (nix2lua val); - listContent = commaJoin (concatMap nix2luaList data); - in "{ ${listContent} }"; + withSpaces = val: optionalString (val != "") "${introSpace}${val}${outroSpace}"; - 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; - luaValue = nix2lua value; - in - wrapNotNull' luaValue '' - ${luaKey} = ${luaValue}, - ''; - attrsContent = concatMap mkKeyValue (attrNames data); - in '' - { - ${concatStringsSep "" attrsContent}} - ''; - }; - }; + mkKV = key: value: "${luaKey key} = ${renderLua innerArgs value}"; + in + if asBindings + then generatedBindings + else if isNull v + then "nil" + else if isInt v || isFloat v || isString v || isBool v + then toJSON v + 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;}; +in + fix (lua: { + inherit renderLua; - nix2lua = data: let - type = typeOf data; - in - if data ? __ast - then toLua.type.ast data - else if toLua.type ? ${type} - then toLua.type.${type} data - else abort ''Type "${type}"''; -in rec { - inherit nix2lua; + writeLua = name: value: pkgs.writeText name (lua.renderLua {} value); - raw = data: newAst "raw" {inherit data;}; + raw = data: newAst "raw" {inherit data;}; - functionWithArgs = name: args: data: - newAst "fn" { - inherit name data args; - }; + function = name: args: data: 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;}; + }) diff --git a/lib/mkNeovimPkg.nix b/lib/mkNeovimPkg.nix deleted file mode 100644 index c1366f8..0000000 --- a/lib/mkNeovimPkg.nix +++ /dev/null @@ -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 < $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); - })) diff --git a/lib/mkPluginsFromInputs.nix b/lib/mkPluginsFromInputs.nix index df450aa..194cd37 100644 --- a/lib/mkPluginsFromInputs.nix +++ b/lib/mkPluginsFromInputs.nix @@ -1,20 +1,22 @@ -{ - pkgs, - inputs, - predicate ? pkgs.lib.strings.hasPrefix "plugin-", - nameMap ? builtins.substring 7 (-1), - buildVimPlugin ? pkgs.vimUtils.buildVimPlugin, -}: let +{pkgs, ...}: 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 { + mkPluginsFromInputs = { + inputs, + predicate ? pkgs.lib.strings.hasPrefix "plugin-", + nameMap ? builtins.substring 7 (-1), + buildVimPlugin ? pkgs.vimUtils.buildVimPlugin, + }: let + 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 - m // {${name} = pluginDef;}; - plugins = foldl' mkPlugin {} names; -in - mapAttrs (_: buildVimPlugin) plugins + mapAttrs (_: buildVimPlugin) plugins; +} diff --git a/lib/modules/default.nix b/lib/modules/default.nix new file mode 100644 index 0000000..6ab0267 --- /dev/null +++ b/lib/modules/default.nix @@ -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 < $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; + }; + }; + }; +} diff --git a/lib/modules/init.nix b/lib/modules/init.nix new file mode 100644 index 0000000..da255a2 --- /dev/null +++ b/lib/modules/init.nix @@ -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; + }) diff --git a/lib/modules/plugin/default.nix b/lib/modules/plugin/default.nix new file mode 100644 index 0000000..0007891 --- /dev/null +++ b/lib/modules/plugin/default.nix @@ -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:, src:})? + 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; + }; + }) diff --git a/lib/modules/plugin/event.nix b/lib/modules/plugin/event.nix new file mode 100644 index 0000000..85ca2ac --- /dev/null +++ b/lib/modules/plugin/event.nix @@ -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 = ""; + 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 + ''; + }; + }) diff --git a/lib/modules/plugin/keymap.nix b/lib/modules/plugin/keymap.nix new file mode 100644 index 0000000..326e248 --- /dev/null +++ b/lib/modules/plugin/keymap.nix @@ -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 = ""; + 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 + ''; + }; + }) diff --git a/lib/modules/runtime.nix b/lib/modules/runtime.nix new file mode 100644 index 0000000..efed9d8 --- /dev/null +++ b/lib/modules/runtime.nix @@ -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; + } + ) + ); + }) diff --git a/lib/modules/sloth.nix b/lib/modules/sloth.nix new file mode 100644 index 0000000..e92718a --- /dev/null +++ b/lib/modules/sloth.nix @@ -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); + }) diff --git a/lib/types.nix b/lib/types.nix deleted file mode 100644 index acb3bd6..0000000 --- a/lib/types.nix +++ /dev/null @@ -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; - }; -} diff --git a/tests/default.nix b/tests/default.nix new file mode 100644 index 0000000..276448a --- /dev/null +++ b/tests/default.nix @@ -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") + ''; + }; + }; +} diff --git a/tests/luaRender.lua b/tests/luaRender.lua new file mode 100644 index 0000000..d21ae3a --- /dev/null +++ b/tests/luaRender.lua @@ -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 +} \ No newline at end of file