{ pkgs, callModule, ... }: let inherit (builtins) concatMap elemAt isPath length pathExists readDir; inherit (pkgs.lib) assertMsg attrsToList concatStringsSep fileContents fix hasSuffix literalExample mergeAttrsList mkOption optional optionalAttrs pipe removeSuffix 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; findLuaRequire = plugin: let luaDir = "${plugin.plugin}/lua"; readOptionalDir = dir: optionalAttrs (pathExists dir) (readDir dir); convertPath = attr: if attr.value == "directory" then optional (pathExists "${luaDir}/${attr.name}/init.lua") attr.name else optional (hasSuffix ".lua" attr.name) (removeSuffix ".lua" attr.name); requireNames = pipe plugin.plugin [ (path: "${path}/lua") readOptionalDir attrsToList (concatMap convertPath) ]; in if length requireNames == 1 then elemAt requireNames 0 else null; 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 assert assertMsg (with plugin; hasSetup -> hasLuaRequire) (concatStringsSep " " [ ''Solth is unable to find the lib to require for plugin "${name}".'' ''Either specify it with `luaRequire = "plugin-name";` or call setup'' ''in a config option: `config = "require('plugin-name').setup { ... }";`.'' ]); 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;}) (optionalAttrs hasLuaRequire {inherit luaRequire;}) (optionalAttrs hasSetup {inherit setup;}) ])); 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"; hasSetup = mkROBoolOption (config.setup != null) "setup"; hasLuaRequire = mkROBoolOption (config.luaRequire != null) "luaRequire"; 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. ''; }; luaRequire = mkOption { type = with types; nullOr str; default = findLuaRequire config; description = '' Name of the lua module to require. An heuristic should find it, but may fail sometimes. ''; }; setup = mkOption { type = with types; nullOr (attrsOf anything); default = null; description = '' Option to pass the the setup function. Will call `require("''${config.luaRequire}").setup(''${toLua config.setup})`. ''; }; # 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; }; })