diff --git a/flake.lock b/flake.lock index 1851eb7..56d380b 100644 --- a/flake.lock +++ b/flake.lock @@ -843,6 +843,38 @@ "type": "github" } }, + "plugin-lz-n": { + "flake": false, + "locked": { + "lastModified": 1719989949, + "narHash": "sha256-oHwmlLgdJJDz5+gs1KLAa2MHQAadM/JYmHGfep9yl28=", + "owner": "nvim-neorocks", + "repo": "lz.n", + "rev": "4c790ba2c3789f580aa019712bbe3112f85e73a0", + "type": "github" + }, + "original": { + "owner": "nvim-neorocks", + "repo": "lz.n", + "type": "github" + } + }, + "plugin-lzn-auto-require": { + "flake": false, + "locked": { + "lastModified": 1723928740, + "narHash": "sha256-CMWy+btqUaXUWSO4jZYDfBcNiYMFyRb8jtIdi072WP8=", + "owner": "horriblename", + "repo": "lzn-auto-require", + "rev": "1b9f6527e32bf196ce239f7a8b9a54d342605511", + "type": "github" + }, + "original": { + "owner": "horriblename", + "repo": "lzn-auto-require", + "type": "github" + } + }, "plugin-mind-nvim": { "flake": false, "locked": { @@ -1872,6 +1904,8 @@ "plugin-lspkind": "plugin-lspkind", "plugin-lspsaga": "plugin-lspsaga", "plugin-lualine": "plugin-lualine", + "plugin-lz-n": "plugin-lz-n", + "plugin-lzn-auto-require": "plugin-lzn-auto-require", "plugin-mind-nvim": "plugin-mind-nvim", "plugin-minimap-vim": "plugin-minimap-vim", "plugin-modes-nvim": "plugin-modes-nvim", diff --git a/flake.nix b/flake.nix index c00e9b8..8468bd2 100644 --- a/flake.nix +++ b/flake.nix @@ -102,6 +102,17 @@ }; ## Plugins + # Lazy loading + plugin-lz-n = { + url = "github:nvim-neorocks/lz.n"; + flake = false; + }; + + plugin-lzn-auto-require = { + url = "github:horriblename/lzn-auto-require"; + flake = false; + }; + # LSP plugins plugin-nvim-lspconfig = { url = "github:neovim/nvim-lspconfig"; diff --git a/lib/binds.nix b/lib/binds.nix index 8c9e9a6..f8f915e 100644 --- a/lib/binds.nix +++ b/lib/binds.nix @@ -3,6 +3,7 @@ inherit (lib.modules) mkIf mkDefault; inherit (lib.types) nullOr str; inherit (lib.attrsets) isAttrs mapAttrs; + inherit (lib.generators) mkLuaInline; binds = rec { mkLuaBinding = key: action: desc: @@ -67,6 +68,22 @@ mkLuaBinding binding.value action binding.description; pushDownDefault = attr: mapAttrs (_: mkDefault) attr; + + mkLznBinding = mode: lhs: rhs: desc: { + inherit mode lhs rhs desc; + }; + + mkSetLznBinding = binding: action: { + lhs = binding.value; + rhs = action; + desc = binding.description; + }; + + mkSetLuaLznBinding = binding: action: { + lhs = binding.value; + rhs = mkLuaInline "function() ${action} end"; + desc = binding.description; + }; }; in binds diff --git a/lib/types/default.nix b/lib/types/default.nix index 928bbae..170667d 100644 --- a/lib/types/default.nix +++ b/lib/types/default.nix @@ -9,7 +9,7 @@ typesCustom = import ./custom.nix {inherit lib;}; in { inherit (typesDag) dagOf; - inherit (typesPlugin) pluginsOpt extraPluginType mkPluginSetupOption luaInline pluginType; + inherit (typesPlugin) pluginsOpt extraPluginType mkPluginSetupOption luaInline pluginType lznPluginType lznPluginTableType; inherit (typesLanguage) diagnostics mkGrammarOption; inherit (typesCustom) anythingConcatLists char; } diff --git a/lib/types/plugins.nix b/lib/types/plugins.nix index c0e89d6..d83c48f 100644 --- a/lib/types/plugins.nix +++ b/lib/types/plugins.nix @@ -6,8 +6,7 @@ inherit (lib.options) mkOption; inherit (lib.attrsets) attrNames mapAttrs' filterAttrs nameValuePair; inherit (lib.strings) hasPrefix removePrefix; - inherit (lib.types) submodule either package enum str lines attrsOf anything listOf nullOr; - + inherit (lib.types) submodule either package enum str lines attrsOf anything listOf nullOr oneOf bool int; # Get the names of all flake inputs that start with the given prefix. fromInputs = { inputs, @@ -51,8 +50,188 @@ }; }; }; + + luaInline = lib.mkOptionType { + name = "luaInline"; + check = x: lib.nvim.lua.isLuaInline x; + }; + + lznKeysSpec = submodule { + options = { + desc = mkOption { + description = "Description of the key map"; + type = nullOr str; + default = null; + }; + + noremap = mkOption { + description = "TBD"; + type = bool; + default = false; + }; + + expr = mkOption { + description = "TBD"; + type = bool; + default = false; + }; + + nowait = mkOption { + description = "TBD"; + type = bool; + default = false; + }; + + ft = mkOption { + description = "TBD"; + type = nullOr (listOf str); + default = null; + }; + + lhs = mkOption { + type = str; + description = "Key to bind to"; + }; + + rhs = mkOption { + type = nullOr (either str luaInline); + default = null; + # FIXME: use a separate flag to indicate lua instead of luaInline + description = "Action to trigger. luaInline code will be wrapped in a function."; + }; + + mode = mkOption { + description = "Modes to bind in"; + type = listOf str; + default = ["n" "x" "s" "o"]; + }; + }; + }; + + lznPluginTableType = attrsOf lznPluginType; + lznPluginType = submodule { + options = { + ## Should probably infer from the actual plugin somehow + ## In general this is the name passed to packadd, so the dir name of the plugin + # name = mkOption { + # type= str; + # } + + # Non-lz.n options + + package = mkOption { + type = pluginType; + description = "Plugin package"; + }; + + setupModule = mkOption { + type = nullOr str; + description = "Lua module to run setup function on."; + default = null; + }; + + setupOpts = mkOption { + type = submodule {freeformType = attrsOf anything;}; + description = "Options to pass to the setup function"; + default = {}; + }; + + # lz.n options + + enabled = mkOption { + type = nullOr (either bool str); + description = "When false, or if the lua function returns false, this plugin will not be included in the spec"; + default = null; + }; + + beforeAll = mkOption { + type = nullOr str; + description = "Lua code to run before any plugins are loaded. This will be wrapped in a function."; + default = null; + }; + + before = mkOption { + type = nullOr str; + description = "Lua code to run before plugin is loaded. This will be wrapped in a function."; + default = null; + }; + + after = mkOption { + type = nullOr str; + description = "Lua code to run after plugin is loaded. This will be wrapped in a function."; + default = null; + }; + + event = mkOption { + description = "Lazy-load on event"; + default = null; + type = let + event = submodule { + options = { + event = mkOption { + type = nullOr (either str (listOf str)); + description = "Exact event name"; + example = "BufEnter"; + }; + pattern = mkOption { + type = nullOr (either str (listOf str)); + description = "Event pattern"; + example = "BufEnter *.lua"; + }; + }; + }; + in + nullOr (oneOf [str (listOf str) event]); + }; + + cmd = mkOption { + description = "Lazy-load on command"; + default = null; + type = nullOr (either str (listOf str)); + }; + + ft = mkOption { + description = "Lazy-load on filetype"; + default = null; + type = nullOr (either str (listOf str)); + }; + + keys = mkOption { + description = "Lazy-load on key mapping"; + default = null; + type = nullOr (oneOf [str (listOf lznKeysSpec) (listOf str)]); + example = '' + keys = [ + {lhs = "s"; rhs = ":NvimTreeToggle"; desc = "Toggle NvimTree"} + ] + ''; + }; + + colorscheme = mkOption { + description = "Lazy-load on colorscheme."; + type = nullOr (either str (listOf str)); + default = null; + }; + + priority = mkOption { + type = nullOr int; + description = "Only useful for stat plugins (not lazy-loaded) to force loading certain plugins first."; + default = null; + }; + + load = mkOption { + type = nullOr str; + default = null; + description = '' + Lua code to override the `vim.g.lz_n.load()` function for a single plugin. + + This will be wrapped in a function + ''; + }; + }; + }; in { - inherit extraPluginType fromInputs pluginType; + inherit extraPluginType fromInputs pluginType luaInline lznPluginType lznPluginTableType; pluginsOpt = { description, @@ -64,11 +243,6 @@ in { type = pluginsType; }; - luaInline = lib.mkOptionType { - name = "luaInline"; - check = x: lib.nvim.lua.isLuaInline x; - }; - /* opts is a attrset of options, example: ``` diff --git a/modules/default.nix b/modules/default.nix index 1ae3b03..8a436d5 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -69,10 +69,7 @@ inputs: { # built (or "normalized") plugins that are modified builtStartPlugins = buildConfigPlugins vimOptions.startPlugins; - builtOptPlugins = map (package: { - plugin = package; - optional = true; - }) (buildConfigPlugins vimOptions.optPlugins); + builtOptPlugins = map (package: package // {optional = true;}) (buildConfigPlugins vimOptions.optPlugins); # additional Lua and Python3 packages, mapped to their respective functions # to conform to the format mnw expects. end user should diff --git a/modules/modules.nix b/modules/modules.nix index a00cea6..a995754 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -50,6 +50,7 @@ wrapper = map (p: ./wrapper + "/${p}") [ "build" "rc" + "lazy" "warnings" ]; diff --git a/modules/neovim/mappings/config.nix b/modules/neovim/mappings/config.nix index 365e124..a620e37 100644 --- a/modules/neovim/mappings/config.nix +++ b/modules/neovim/mappings/config.nix @@ -3,32 +3,56 @@ lib, ... }: let - inherit (lib.modules) mkIf; + inherit (lib.modules) mkIf mkMerge; + inherit (builtins) mapAttrs; + + processLegacyMap = modes: legacyMap: [(legacyMap // {mode = modes;})]; cfg = config.vim; in { config = { - vim.maps = mkIf cfg.disableArrows { - "" = { - mode = ["n" "i"]; - action = ""; - noremap = false; - }; - "" = { - mode = ["n" "i"]; - action = ""; - noremap = false; - }; - "" = { - mode = ["n" "i"]; - action = ""; - noremap = false; - }; - "" = { - mode = ["n" "i"]; - action = ""; - noremap = false; - }; - }; + vim.keymaps = mkMerge [ + (mkIf cfg.disableArrows { + "" = [ + { + mode = ["n" "i"]; + action = ""; + noremap = false; + } + ]; + "" = [ + { + mode = ["n" "i"]; + action = ""; + noremap = false; + } + ]; + "" = [ + { + mode = ["n" "i"]; + action = ""; + noremap = false; + } + ]; + "" = [ + { + mode = ["n" "i"]; + action = ""; + noremap = false; + } + ]; + }) + (mapAttrs (_key: processLegacyMap ["n"]) cfg.maps.normal) + (mapAttrs (_key: processLegacyMap ["i"]) cfg.maps.insert) + (mapAttrs (_key: processLegacyMap ["s"]) cfg.maps.select) + (mapAttrs (_key: processLegacyMap ["v"]) cfg.maps.visual) + (mapAttrs (_key: processLegacyMap ["t"]) cfg.maps.terminal) + (mapAttrs (_key: processLegacyMap ["n" "v" "o"]) cfg.maps.normalVisualOp) + (mapAttrs (_key: processLegacyMap ["n" "x"]) cfg.maps.visualOnly) + (mapAttrs (_key: processLegacyMap ["o"]) cfg.maps.operator) + (mapAttrs (_key: processLegacyMap ["i" "c"]) cfg.maps.insertCommand) + (mapAttrs (_key: processLegacyMap ["l"]) cfg.maps.lang) + (mapAttrs (_key: processLegacyMap ["c"]) cfg.maps.command) + ]; }; } diff --git a/modules/neovim/mappings/options.nix b/modules/neovim/mappings/options.nix index f422991..e16b787 100644 --- a/modules/neovim/mappings/options.nix +++ b/modules/neovim/mappings/options.nix @@ -38,6 +38,7 @@ See `:help map-modes` for a list of modes. ''; + example = ''"nvc" for normal, visual and command mode''; }; }; }; @@ -55,25 +56,10 @@ }; in { options.vim = { - maps = mkOption { + keymaps = mkOption { type = submodule { - freeformType = attrsOf mapType; - options = { - normal = mapOptions "normal"; - insert = mapOptions "insert"; - select = mapOptions "select"; - visual = mapOptions "visual and select"; - terminal = mapOptions "terminal"; - normalVisualOp = mapOptions "normal, visual, select and operator-pending (same as plain 'map')"; - - visualOnly = mapOptions "visual only"; - operator = mapOptions "operator-pending"; - insertCommand = mapOptions "insert and command-line"; - lang = mapOptions "insert, command-line and lang-arg"; - command = mapOptions "command-line"; - }; + freeformType = attrsOf (listOf mapType); }; - default = {}; description = "Custom keybindings."; example = '' maps = { @@ -84,6 +70,22 @@ in { }; # Same as nnoremap m make }; ''; + default = {}; + }; + + maps = { + normal = mapOptions "normal"; + insert = mapOptions "insert"; + select = mapOptions "select"; + visual = mapOptions "visual and select"; + terminal = mapOptions "terminal"; + normalVisualOp = mapOptions "normal, visual, select and operator-pending (same as plain 'map')"; + + visualOnly = mapOptions "visual only"; + operator = mapOptions "operator-pending"; + insertCommand = mapOptions "insert and command-line"; + lang = mapOptions "insert, command-line and lang-arg"; + command = mapOptions "command-line"; }; }; } diff --git a/modules/plugins/filetree/nvimtree/config.nix b/modules/plugins/filetree/nvimtree/config.nix index b97b1e4..47a85d6 100644 --- a/modules/plugins/filetree/nvimtree/config.nix +++ b/modules/plugins/filetree/nvimtree/config.nix @@ -4,11 +4,11 @@ pkgs, ... }: let + inherit (builtins) filter; inherit (lib.strings) optionalString; - inherit (lib.modules) mkIf mkMerge; - inherit (lib.nvim.binds) mkBinding; + inherit (lib.modules) mkIf; + inherit (lib.nvim.binds) mkLznBinding; inherit (lib.nvim.dag) entryAnywhere; - inherit (lib.nvim.lua) toLuaObject; inherit (lib.nvim.binds) pushDownDefault; cfg = config.vim.filetree.nvimTree; @@ -16,19 +16,28 @@ inherit (self.options.vim.filetree.nvimTree) mappings; in { config = mkIf cfg.enable { - vim.startPlugins = ["nvim-tree-lua"]; - - vim.maps.normal = mkMerge [ - (mkBinding cfg.mappings.toggle ":NvimTreeToggle" mappings.toggle.description) - (mkBinding cfg.mappings.refresh ":NvimTreeRefresh" mappings.refresh.description) - (mkBinding cfg.mappings.findFile ":NvimTreeFindFile" mappings.findFile.description) - (mkBinding cfg.mappings.focus ":NvimTreeFocus" mappings.focus.description) - ]; - vim.binds.whichKey.register = pushDownDefault { "t" = "+NvimTree"; }; + vim.lazy = { + plugins = { + nvim-tree-lua = { + package = "nvim-tree-lua"; + setupModule = "nvim-tree"; + inherit (cfg) setupOpts; + cmd = ["NvimTreeClipboard" "NvimTreeClose" "NvimTreeCollapse" "NvimTreeCollapseKeepBuffers" "NvimTreeFindFile" "NvimTreeFindFileToggle" "NvimTreeFocus" "NvimTreeHiTest" "NvimTreeOpen" "NvimTreeRefresh" "NvimTreeResize" "NvimTreeToggle"]; + + keys = filter ({lhs, ...}: lhs != null) [ + (mkLznBinding ["n"] cfg.mappings.toggle ":NvimTreeToggle" mappings.toggle.description) + (mkLznBinding ["n"] cfg.mappings.refresh ":NvimTreeRefresh" mappings.refresh.description) + (mkLznBinding ["n"] cfg.mappings.findFile ":NvimTreeFindFile" mappings.findFile.description) + (mkLznBinding ["n"] cfg.mappings.focus ":NvimTreeFocus" mappings.focus.description) + ]; + }; + }; + }; + vim.pluginRC.nvimtreelua = entryAnywhere '' ${ optionalString cfg.setupOpts.disable_netrw '' @@ -38,10 +47,9 @@ in { '' } - require'nvim-tree'.setup(${toLuaObject cfg.setupOpts}) - ${ optionalString cfg.openOnSetup '' + require('lz.n').trigger_load("nvim-tree-lua") -- autostart behaviour -- Open on startup has been deprecated -- see https://github.com/nvim-tree/nvim-tree.lua/wiki/Open-At-Startup diff --git a/modules/wrapper/lazy/config.nix b/modules/wrapper/lazy/config.nix new file mode 100644 index 0000000..7baefa4 --- /dev/null +++ b/modules/wrapper/lazy/config.nix @@ -0,0 +1,74 @@ +{ + lib, + config, + ... +}: let + inherit (builtins) toJSON typeOf head length; + inherit (lib.modules) mkIf; + inherit (lib.attrsets) mapAttrsToList; + inherit (lib.generators) mkLuaInline; + inherit (lib.strings) optionalString; + inherit (lib.nvim.lua) toLuaObject; + inherit (lib.nvim.dag) entryBefore; + cfg = config.vim.lazy; + + toLuzLznKeySpec = { + desc, + noremap, + expr, + nowait, + ft, + lhs, + rhs, + mode, + }: { + "@1" = lhs; + "@2" = rhs; + inherit desc noremap expr nowait ft mode; + }; + + toLuaLznSpec = name: spec: + (removeAttrs spec ["package" "setupModule" "setupOpts" "keys"]) + // { + "@1" = name; + before = + if spec.before != null + then + mkLuaInline '' + function() + ${spec.before} + end + '' + else null; + + after = + if spec.setupModule == null && spec.after == null + then null + else + mkLuaInline '' + function() + ${ + optionalString (spec.setupModule != null) + "require(${toJSON spec.setupModule}).setup(${toLuaObject spec.setupOpts})" + } + ${optionalString (spec.after != null) spec.after} + end + ''; + + keys = + if typeOf spec.keys == "list" && length spec.keys > 0 && typeOf (head spec.keys) == "set" + then map toLuzLznKeySpec spec.keys + else spec.keys; + }; + lznSpecs = mapAttrsToList toLuaLznSpec cfg.plugins; +in { + config.vim = mkIf cfg.enable { + startPlugins = ["lz-n" "lzn-auto-require"]; + + optPlugins = mapAttrsToList (_: plugin: plugin.package) cfg.plugins; + + luaConfigRC.lzn-load = entryBefore ["pluginConfigs"] '' + require('lz.n').load(${toLuaObject lznSpecs}) + ''; + }; +} diff --git a/modules/wrapper/lazy/default.nix b/modules/wrapper/lazy/default.nix new file mode 100644 index 0000000..fa40127 --- /dev/null +++ b/modules/wrapper/lazy/default.nix @@ -0,0 +1,6 @@ +_: { + imports = [ + ./lazy.nix + ./config.nix + ]; +} diff --git a/modules/wrapper/lazy/lazy.nix b/modules/wrapper/lazy/lazy.nix new file mode 100644 index 0000000..151c087 --- /dev/null +++ b/modules/wrapper/lazy/lazy.nix @@ -0,0 +1,29 @@ +{lib, ...}: let + inherit (lib.options) mkOption mkEnableOption; + inherit (lib.types) enum; + inherit (lib.nvim.types) lznPluginTableType; +in { + options.vim.lazy = { + enable = mkEnableOption "plugin lazy-loading" // {default = true;}; + loader = mkOption { + description = "Lazy loader to use"; + type = enum ["lz.n"]; + default = "lz.n"; + }; + + plugins = mkOption { + default = {}; + type = lznPluginTableType; + description = "list of plugins to lazy load"; + example = '' + { + toggleterm-nvim = { + package = "toggleterm-nvim"; + after = lib.generators.mkLuaInline "function() require('toggleterm').setup{} end"; + cmd = ["ToggleTerm"]; + }; + } + ''; + }; + }; +} diff --git a/modules/wrapper/rc/config.nix b/modules/wrapper/rc/config.nix index b3fd2e4..67731fa 100644 --- a/modules/wrapper/rc/config.nix +++ b/modules/wrapper/rc/config.nix @@ -3,10 +3,10 @@ lib, ... }: let - inherit (builtins) map mapAttrs filter removeAttrs attrNames; - inherit (lib.attrsets) mapAttrsToList filterAttrs attrsToList; - inherit (lib.strings) concatLines concatMapStringsSep; - inherit (lib.trivial) showWarnings; + inherit (builtins) map mapAttrs filter; + inherit (lib.attrsets) mapAttrsToList filterAttrs; + inherit (lib.strings) concatLines concatMapStringsSep optionalString; + inherit (lib.trivial) showWarnings pipe; inherit (lib.generators) mkLuaInline; inherit (lib.nvim.dag) entryAfter mkLuarcSection resolveDag entryAnywhere; inherit (lib.nvim.lua) toLuaObject; @@ -40,40 +40,19 @@ in { inherit (keymap) desc silent nowait script expr unique noremap; }; - toLuaKeymap = { - name, - value, - }: "vim.keymap.set(${toLuaObject value.mode}, ${toLuaObject name}, ${toLuaObject (getAction value)}, ${toLuaObject (getOpts value)})"; - - namedModes = { - "normal" = ["n"]; - "insert" = ["i"]; - "select" = ["s"]; - "visual" = ["v"]; - "terminal" = ["t"]; - "normalVisualOp" = ["n" "v" "o"]; - "visualOnly" = ["n" "x"]; - "operator" = ["o"]; - "insertCommand" = ["i" "c"]; - "lang" = ["l"]; - "command" = ["c"]; - }; + toLuaKeymap = key: bind: "vim.keymap.set(${toLuaObject bind.mode}, ${toLuaObject key}, ${toLuaObject (getAction bind)}, ${toLuaObject (getOpts bind)})"; maps = - removeAttrs cfg.maps (attrNames namedModes) - // mapAttrs (_: legacyMap: legacyMap // {mode = namedModes.normal;}) cfg.maps.normal - // mapAttrs (_: legacyMap: legacyMap // {mode = namedModes.insert;}) cfg.maps.insert - // mapAttrs (_: legacyMap: legacyMap // {mode = namedModes.select;}) cfg.maps.select - // mapAttrs (_: legacyMap: legacyMap // {mode = namedModes.visual;}) cfg.maps.visual - // mapAttrs (_: legacyMap: legacyMap // {mode = namedModes.terminal;}) cfg.maps.terminal - // mapAttrs (_: legacyMap: legacyMap // {mode = namedModes.normalVisualOp;}) cfg.maps.normalVisualOp - // mapAttrs (_: legacyMap: legacyMap // {mode = namedModes.visualOnly;}) cfg.maps.visualOnly - // mapAttrs (_: legacyMap: legacyMap // {mode = namedModes.operator;}) cfg.maps.operator - // mapAttrs (_: legacyMap: legacyMap // {mode = namedModes.insertCommand;}) cfg.maps.insertCommand - // mapAttrs (_: legacyMap: legacyMap // {mode = namedModes.lang;}) cfg.maps.lang - // mapAttrs (_: legacyMap: legacyMap // {mode = namedModes.command;}) cfg.maps.command; + pipe + # attrsOf (listOf mapOption) + cfg.keymaps + [ + (mapAttrsToList (key: binds: + concatLines (map (toLuaKeymap key) binds))) + concatLines + ]; - keymaps = concatLines (map toLuaKeymap (attrsToList (filterNonNull maps))); + keymaps = maps; in { vim = { luaConfigRC = { @@ -82,6 +61,8 @@ in { pluginConfigs = entryAfter ["theme"] pluginConfigs; extraPluginConfigs = entryAfter ["pluginConfigs"] extraPluginConfigs; mappings = entryAfter ["extraPluginConfigs"] keymaps; + # FIXME: put this somewhere less stupid + footer = entryAfter ["mappings"] (optionalString config.vim.lazy.enable "require('lzn-auto-require.loader').register_loader()"); }; builtLuaConfigRC = let