diff --git a/pkgs/lazyUtils.nix b/pkgs/lazyUtils.nix new file mode 100644 index 0000000..5e3a1dd --- /dev/null +++ b/pkgs/lazyUtils.nix @@ -0,0 +1,256 @@ +{ + 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.isString plugin.dir) || (lib.isDerivation plugin.dir)); + 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 :: String -> Set + pluginWrapper :: Derivation -> Set + pluginWrapper :: Set -> Set + */ + pluginWrapper = + pluginOrPackage: + if (builtins.isString pluginOrPackage) then + { short = pluginOrPackage; } + else if + assert (lib.isDerivation pluginOrPackage || builtins.isAttrs pluginOrPackage); + (lib.isDerivation 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; + in + lib.pipe plugin [ + ( + plugin: + plugin + // { + __posArgs = runIfSet lib.flatten "short"; + #if (builtins.isNull short) then short else [ short ]; + name = getName plugin; + enabled = runIfSet stringLiteral "enabled"; + cond = runIfSet stringLiteral "cond"; + dependencies = runIfSet (map ( + pluginOrPackage: getName (pluginWrapper pluginOrPackage) + )) "dependencies"; + 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 + # normalie plugin file name + name = lib.replaceStrings [ "." ] [ "-" ] (helpers.getName (helpers.pluginWrapper plugin)); + in + luaUtils.writeLuaFileDir "lua/lazyWrapper/plugins/${name}" (lazyPluginFile plugin); + + /** + 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) + } ) + ''; +}