{ lib, pkgs, vPlug ? pkgs.vimPlugins, luaUtils ? import ./luaUtils { inherit lib pkgs; }, }: let helpers = rec { /** Determines the name of a plugin. Priority: 1. plugin.name 2. baseNameOf plugin.short 3. (derivation) plugin.dir.pname 3. (path) baseNameOf plugin.dir Type: getName :: Plugin -> String */ getName = plugin: if (plugin ? name && plugin.name != null) then assert builtins.isString plugin.name; plugin.name else if (plugin ? short && plugin.short != null) then assert builtins.isString plugin.short; # TODO: Check how lazy handles this normally baseNameOf plugin.short else if (plugin ? dir && plugin.dir != null) then ( assert builtins.any (isType: isType plugin.dir) [ lib.isDerivation builtins.isString builtins.isPath ]; if (lib.isDerivation plugin.dir) then plugin.dir.pname else (baseNameOf plugin.dir) ) else throw "Could not determine a plugin name.\n${builtins.toJSON plugin}"; /** Transforms a set describing a keybind into a list (lazy-key). Takes a set containing: - bind: (string) the key to bind - cmd: (optional string) the command to execute - cmdIsFunction: (optional bool) interpret cmd as a function instead of a string - opts: (optional set) the option set For further information on the opts argument take a look at the lazy.nvim LazyKeysSpec definition. Type: getKey :: Set -> List */ getKey = { bind, cmd ? null, cmdIsFunction ? false, opts ? null, }: lib.filter (option: !builtins.isNull option) [ bind (if (cmdIsFunction == true) && (builtins.isString cmd) then (_: cmd) else cmd) (if builtins.isAttrs opts then opts // { __unpack = true; } else null) ]; /** Helper function to ensure a plugin input is correctly wrapped. Correct inputs are: - short plugin url (string) - a plugin package (derivation) - plugin (set) Type: pluginWrapper :: (Lambda -> String) -> Set pluginWrapper :: (Lambda -> Derivation) -> Set pluginWrapper :: (Lambda -> Set) -> Set pluginWrapper :: String -> Set pluginWrapper :: Derivation -> Set pluginWrapper :: Set -> Set */ pluginWrapper = pluginOrPackage: if (builtins.isString pluginOrPackage) then { short = pluginOrPackage; } else if assert (builtins.isAttrs pluginOrPackage || builtins.isFunction pluginOrPackage); (lib.isDerivation pluginOrPackage || builtins.isFunction pluginOrPackage) then { dir = pluginOrPackage; } else pluginOrPackage; /** Transforms plugin inputs into Valid plugin attributes are: - short, dir, url, name, dev, lazy, enabled, cond, dependencies, init, opts, config, main, build, branch, tag, commit, version, pin, submodules, event, cmd, ft, keys, module, priority, optional Type: pluginNormalize :: Set -> Set */ pluginNormalize = pluginOrPackage: let plugin = pluginWrapper pluginOrPackage; runIfSet = fn: key: let val = if (plugin ? "${key}") then plugin."${key}" else null; in if (builtins.isNull val) then val else fn val; # Wrap function arguments in function so they are inserted as is when parsed. stringLiteral = value: if (builtins.isString value) then (_: value) else value; resolvePlugin = value: if (builtins.isFunction value) then value vPlug else value; in lib.pipe plugin [ # Resolve dir and dependencies first so getName can resolve names ( plugin: plugin // { dir = runIfSet resolvePlugin "dir"; dependencies = runIfSet ( pluginListFn: (map (pluginOrPackage: getName (pluginWrapper pluginOrPackage))) (resolvePlugin pluginListFn) ) "dependencies"; } ) ( plugin: plugin // { __posArgs = runIfSet lib.flatten "short"; name = getName plugin; enabled = runIfSet stringLiteral "enabled"; cond = runIfSet stringLiteral "cond"; init = runIfSet stringLiteral "init"; opts = runIfSet stringLiteral "opts"; config = runIfSet stringLiteral "config"; build = runIfSet stringLiteral "build"; cmd = runIfSet stringLiteral "cmd"; ft = runIfSet stringLiteral "ft"; keys = runIfSet (map getKey) "keys"; } ) # Remove unused attributes (lib.filterAttrs (key: value: !builtins.isNull value)) # short is a positional argument so it has do be removed (plugin: builtins.removeAttrs plugin [ "short" ]) ]; }; in rec { inherit helpers; /** Takes a plugin definition normalizes it and convets it into Lua. Returns only the plugin table so it can be inlined. Type: lazyPluginClosure :: String -> String lazyPluginClosure :: Derivation -> String lazyPluginClosure :: Set -> String */ lazyPluginClosure = plugin: luaUtils.setToLua (helpers.pluginNormalize plugin); /** Takes a plugin definition normalizes it and convets it into Lua. Wraps the result of lazyPluginClosure and adds a return statement in front of it. Type: lazyPluginFile :: String -> String lazyPluginFile :: Derivation -> String lazyPluginFile :: Set -> String */ lazyPluginFile = plugin: # lua ''return ${lazyPluginClosure plugin}''; /** Takes a plugin and passes it to lazyPluginFile. The output is formatted and written to "lua/lazyWrapper/plugins/${name}" Note: ${name} is the Plugin name but all '.' are replaced with '-'. Type: writeLazyPluginDir :: String -> Derivation writeLazyPluginDir :: Derivation -> Derivation writeLazyPluginDir :: Set -> Derivation */ writeLazyPluginDir = plugin: let normalizedPlugin = helpers.pluginNormalize plugin; name = lib.replaceStrings [ "." ] [ "-" ] normalizedPlugin.name; in luaUtils.writeLuaFileDir "lua/lazyWrapper/plugins/${name}" ''return ${luaUtils.setToLua normalizedPlugin}''; /** Builds and configures the lazy plugin directory. Takes a set containing: - lazyPlugins: (list) the list of plugins to link - lazyConfig: (set) lazy config attributes Type: lazyPluginsJoin :: Set -> Derivation */ lazyPluginsJoin = { lazyPlugins ? [ ], lazyConfig ? { }, }: let init = lazyLuaInit { inherit lazyConfig; hasPlugins = (lib.length lazyPlugins > 0); }; in pkgs.symlinkJoin { name = "nvim-lazy-plugins"; paths = [ init ] ++ (map (plugin: writeLazyPluginDir plugin) lazyPlugins); }; /** Generates the init function for nvim.lazy. Takes a set containing: - lazyConfig: (set) lazy config attributes - hasPlugins: (bool) will not load any plugins if set to false Type: lazyLuaInit :: Set -> Derivation */ lazyLuaInit = { lazyConfig ? { }, hasPlugins ? false, }: let lazyBaseConfig = { checker = { notify = false; }; change_detection = { enabled = false; notify = false; }; performance = { # TODO: Check if this is really needed reset_packpath = false; rtp = { reset = false; }; }; }; in pkgs.writeTextDir "lua/lazyWrapper/init.lua" # lua '' vim.opt.rtp:prepend("${vPlug.lazy-nvim}") require("lazy").setup( ${ # Plugin import on an empty/non existent directory causes an error # Only specify import path if there are plugins to load if hasPlugins then ''"lazyWrapper.plugins"'' else "{}" } , ${ # Merge defaults with user config and generate the lua table luaUtils.setToLua (lib.recursiveUpdate lazyBaseConfig lazyConfig) } ) ''; }