From f4df92749983d922b6aa39abc78f4e4f069d251f Mon Sep 17 00:00:00 2001 From: Tash Date: Sun, 1 Sep 2024 17:22:51 -0400 Subject: [PATCH] plugins/lazy.nvim: switch to mkNeovimPlugin --- lib/maintainers.nix | 6 + plugins/pluginmanagers/lazy.nix | 482 ++++++++++-------- .../plugins/pluginmanagers/lazy.nix | 336 +++++++++++- 3 files changed, 601 insertions(+), 223 deletions(-) diff --git a/lib/maintainers.nix b/lib/maintainers.nix index 275634dbce..3ce2298270 100644 --- a/lib/maintainers.nix +++ b/lib/maintainers.nix @@ -107,4 +107,10 @@ githubId = 6452260; name = "Andrew Plaza"; }; + my7h3le = { + email = "mandate-word-dupe@duck.com"; + github = "my7h3le"; + githubId = 7899547; + name = "Tash"; + }; } diff --git a/plugins/pluginmanagers/lazy.nix b/plugins/pluginmanagers/lazy.nix index 1c11c4af74..ce9f7183b1 100644 --- a/plugins/pluginmanagers/lazy.nix +++ b/plugins/pluginmanagers/lazy.nix @@ -1,222 +1,288 @@ -{ - lib, - helpers, - config, - pkgs, - ... -}: -with lib; +{ lib, pkgs, ... }: let - cfg = config.plugins.lazy; - lazyPlugins = cfg.plugins; + inherit (lib.nixvim) + defaultNullOpts + mkNullOrOption + mkNullOrLuaFn + mkNullOrStrLuaFnOr + ; + inherit (lib) types; +in +lib.nixvim.neovim-plugin.mkNeovimPlugin { + name = "lazy"; + originalName = "lazy.nvim"; + package = "lazy-nvim"; + + maintainers = with lib.maintainers; [ + MattSturgeon + my7h3le + ]; + + settingsOptions = { + git = { + url_format = defaultNullOpts.mkStr "https://github.com/%s.git" '' + The default url format that `lazy.nvim` expects short plugin urls to be + in. (See: upstream docs for `config.git.url_format` defined here + https://lazy.folke.io/configuration); + ''; + }; + + dev = { + fallback = defaultNullOpts.mkBool false '' + When false, `lazy.nvim` won't try to use git to fetch local plugins that + don't exist. + ''; + }; + + install = { + missing = defaultNullOpts.mkBool false '' + When false, `lazy.nvim` won't try to install missing plugins on startup. + Setting this to true won't increase startup time. + ''; + }; + }; - processPlugin = - plugin: + extraOptions = let - mkEntryFromDrv = - p: - if lib.isDerivation p then + # A plugin defined in the `nixvim.plugins.lazy.plugins` list can either + # be of `types.package` or `types.str`. Depending on the type of the + # given plugin this plugin will conditionally return an appropriate + # plugin spec. + coerceToLazyPluginSpec = + plugin: + if lib.isDerivation plugin then { - name = "${lib.getName p}"; - path = p; + dir = lib.mkDefault "${plugin}"; + name = lib.mkDefault "${lib.getName plugin}"; } + else if lib.isString plugin then + { __unkeyed = lib.mkDefault plugin; } else - { - name = "${lib.getName p.pkg}"; - path = p.pkg; - }; - processDependencies = - if plugin ? dependencies && plugin.dependencies != null then - builtins.concatMap processPlugin plugin.dependencies - else - [ ]; - in - [ (mkEntryFromDrv plugin) ] ++ processDependencies; + plugin; - processedPlugins = builtins.concatLists (builtins.map processPlugin lazyPlugins); - lazyPath = pkgs.linkFarm "lazy-plugins" processedPlugins; -in -{ - options = { - plugins.lazy = { - enable = mkEnableOption "lazy.nvim"; + lazyPluginCoercibleType = with types; either str package; - gitPackage = lib.mkPackageOption pkgs "git" { - nullable = true; - }; - - plugins = + lazyPluginType = with types; - let - pluginType = either package (submodule { - options = { - dir = helpers.mkNullOrOption str "A directory pointing to a local plugin"; - - pkg = mkOption { - type = package; - description = "Vim plugin to install"; - }; + types.coercedTo lazyPluginCoercibleType coerceToLazyPluginSpec ( + submodule ( + { config, ... }: - name = helpers.mkNullOrOption str "Name of the plugin to install"; - - dev = helpers.defaultNullOpts.mkBool false '' - When true, a local plugin directory will be used instead. - See config.dev - ''; - - lazy = helpers.defaultNullOpts.mkBool true '' - When true, the plugin will only be loaded when needed. - Lazy-loaded plugins are automatically loaded when their Lua modules are required, - or when one of the lazy-loading handlers triggers - ''; - - enabled = helpers.defaultNullOpts.mkStrLuaFnOr types.bool "`true`" '' - When false then this plugin will not be included in the spec. (accepts fun():boolean) - ''; - - cond = helpers.defaultNullOpts.mkStrLuaFnOr types.bool "`true`" '' - When false, or if the function returns false, - then this plugin will not be loaded. Useful to disable some plugins in vscode, - or firenvim for example. (accepts fun(LazyPlugin):boolean) - ''; - - dependencies = helpers.mkNullOrOption (helpers.nixvimTypes.eitherRecursive str listOfPlugins) "Plugin dependencies"; - - init = helpers.mkNullOrLuaFn "init functions are always executed during startup"; - - config = helpers.mkNullOrStrLuaFnOr (types.enum [ true ]) '' - config is executed when the plugin loads. - The default implementation will automatically run require(MAIN).setup(opts). - Lazy uses several heuristics to determine the plugin's MAIN module automatically based on the plugin's name. - See also opts. To use the default implementation without opts set config to true. - ''; - - main = helpers.mkNullOrOption str '' - You can specify the main module to use for config() and opts(), - in case it can not be determined automatically. See config() - ''; - - submodules = helpers.defaultNullOpts.mkBool true '' - When false, git submodules will not be fetched. - Defaults to true - ''; - - event = - with helpers.nixvimTypes; - helpers.mkNullOrOption (maybeRaw (either str (listOf str))) "Lazy-load on event. Events can be specified as BufEnter or with a pattern like BufEnter *.lua"; - - cmd = - with helpers.nixvimTypes; - helpers.mkNullOrOption (maybeRaw (either str (listOf str))) "Lazy-load on command"; - - ft = - with helpers.nixvimTypes; - helpers.mkNullOrOption (maybeRaw (either str (listOf str))) "Lazy-load on filetype"; - - keys = - with helpers.nixvimTypes; - helpers.mkNullOrOption (maybeRaw (either str (listOf str))) "Lazy-load on key mapping"; - - module = helpers.mkNullOrOption (enum [ false ]) '' - Do not automatically load this Lua module when it's required somewhere - ''; - - priority = helpers.mkNullOrOption number '' - Only useful for start plugins (lazy=false) to force loading certain plugins first. - Default priority is 50. It's recommended to set this to a high number for colorschemes. - ''; - - optional = helpers.defaultNullOpts.mkBool false '' - When a spec is tagged optional, it will only be included in the final spec, - when the same plugin has been specified at least once somewhere else without optional. - This is mainly useful for Neovim distros, to allow setting options on plugins that may/may not be part - of the user's plugins - ''; - - opts = - with helpers.nixvimTypes; - helpers.mkNullOrOption (maybeRaw (attrsOf anything)) '' - opts should be a table (will be merged with parent specs), - return a table (replaces parent specs) or should change a table. - The table will be passed to the Plugin.config() function. - Setting this value will imply Plugin.config() + let + cfg = config; + in + { + freeformType = attrsOf anything; + options = { + __unkeyed = mkNullOrOption str '' + The "__unkeyed" attribute can either be one of: + + - a local plugin directory path. + - a full plugin url. + - a short plugin url. + - a custom name for a plugin. + + If a short plugin url is given e.g. "echasnovski/mini.ai" it + will be expanded using `plugins.lazy.settings.git.url_format` + + If a custom name for a plugin is given, the plugin must be + defined somewhere else in the list of plugins. For example: + + plugins.lazy.plugins = with pkgs.vimPlugins; [ + # Custom name for vim plugin + "oil" + + # The plugin gets defined by using the custom name that was + # previously given. + { + name = "oil"; + pkg = oil-nvim; + } + ]; ''; - }; - }); - - listOfPlugins = types.listOf pluginType; - in - mkOption { - type = listOfPlugins; - default = [ ]; - description = "List of plugins"; - }; + + dir = mkNullOrOption str '' + A directory pointing to a local plugin path e.g. "~/plugins/trouble.nvim". + ''; + + url = mkNullOrOption str "A custom git url where the plugin is hosted"; + + pkg = mkNullOrOption package "Vim plugin to install"; + + name = mkNullOrOption str "Name of the plugin to install"; + + dev = defaultNullOpts.mkBool false '' + When true, `lazy.nvim` will look for this plugin in the local + plugin directory defined at `plugin.lazy.settings.dev.path`. + ''; + + lazy = defaultNullOpts.mkBool true '' + When true, the plugin will only be loaded when needed. + + Lazy-loaded plugins are automatically loaded when their Lua + modules are required, or when one of the lazy-loading + handlers are triggered. + + You can define which triggers load this plugin using + `plugins.lazy.plugins..[event|cmd|ft|keys]`. + ''; + + enabled = defaultNullOpts.mkStrLuaFnOr bool true '' + When false or if a function that returns false is defined + then this plugin will not be included in the final spec. The + plugin will also be uninstalled when true if the plugin is an + out of tree non nix package plugin. (accepts fun():boolean). + ''; + + cond = defaultNullOpts.mkStrLuaFnOr bool true '' + Behaves the same as enabled, but won't uninstall the plugin + when the condition is false. Useful to disable some plugins + in vscode, or firenvim for example. (this only affects out of + tree non nix package plugins as those are the only ones that + will be uninstalled by `lazy.nvim`). + ''; + + # WARNING: Be very careful if changing the type of + # `dependencies`. Choosing the wrong type may cause a stack + # overflow due to infinite recursion, and it's very possible + # that the test cases won't catch this problem. To be safe, + # perform thorough manual testing if you do change the type of + # `dependencies`. Also use `types.eitherRecursive` instead of + # `types.either` here, as using just `types.either` also leads + # to stack overflow. + dependencies = mkNullOrOption (eitherRecursive lazyPluginType lazyPluginsListType) '' + A list of plugin names or plugin specs that should be + loaded when the plugin loads. Dependencies are always + lazy-loaded unless specified otherwise. When specifying a + name, make sure the plugin spec has been defined somewhere + else. This can also be a single string such as for a short + plugin url (See: https://lazy.folke.io/spec). + ''; + + init = mkNullOrLuaFn "init functions are always executed during startup"; + + config = mkNullOrStrLuaFnOr (enum [ true ]) '' + config is executed when the plugin loads. + + The default implementation will automatically run require(MAIN).setup(opts). + Lazy uses several heuristics to determine the plugin's MAIN module automatically based on the plugin's name. + See also opts. To use the default implementation without opts set config to true. + ''; + + main = mkNullOrOption str '' + You can specify the main module to use for config() and opts(), + in case it can not be determined automatically. See config() + ''; + + submodules = defaultNullOpts.mkBool true '' + When false, git submodules will not be fetched. + Defaults to true + ''; + + event = + + mkNullOrOption (maybeRaw ( + either str (listOf str) + )) "Lazy-load on event. Events can be specified as BufEnter or with a pattern like BufEnter *.lua"; + + cmd = mkNullOrOption (maybeRaw (either str (listOf str))) "Lazy-load on command"; + + ft = mkNullOrOption (maybeRaw (either str (listOf str))) '' + Lazy-load plugin on filetype e.g.: + + plugins.lazy.plugins = with pkgs.vimPlugins; [ + { + pkg = neorg; + # Only load plugin on "norg" filetyes + ft = "norg"; + opts = { + load."['core.defaults']" = [ ]; + }; + } + ]; + ''; + + keys = mkNullOrOption (maybeRaw (either str (listOf str))) "Lazy-load on key mapping"; + + module = mkNullOrOption (enum [ false ]) '' + Do not automatically load this Lua module when it's required somewhere. + ''; + + priority = defaultNullOpts.mkInt 50 '' + Only useful for start plugins i.e. + `plugins.lazy.plugins..lazy = false` to force loading + certain plugins first. Default priority is 50. It's + recommended to set this to a high number for colorschemes. + ''; + + optional = defaultNullOpts.mkBool false '' + Optional specs are only included in the final configuration + if the corresponding plugin is also specified as a required + (non-optional) plugin elsewhere. This feature is + particularly helpful for Neovim distributions, allowing + them to pre-configure settings for plugins that users may + or may not have installed. + ''; + + opts = mkNullOrOption (maybeRaw (attrsOf anything)) '' + The opts value can be one of the following: + + - A table: This table will be merged with any existing + configuration settings from parent specifications. + - A function that returns a table: The returned table will + completely replace any existing configuration settings from + parent specifications. + - A function that modifies a table: This function will + receive the existing configuration table as an argument and + can modify it directly. + + In all cases, the resulting configuration table will be + passed to the Plugin.config() function. Setting the opts + value automatically implies that Plugin.config() will be + called. (See: https://lazy.folke.io/spec#spec-setup) + ''; + }; + + config.name = lib.mkIf (cfg.pkg != null) (lib.mkDefault "${lib.getName cfg.pkg}"); + config.dir = lib.mkIf (cfg.pkg != null) (lib.mkDefault "${cfg.pkg}"); + } + ) + ); + + lazyPluginsListType = types.listOf lazyPluginType; + in + { + gitPackage = lib.mkPackageOption pkgs "git" { nullable = true; }; + luarocksPackage = lib.mkPackageOption pkgs "luarocks" { nullable = true; }; + + plugins = lib.mkOption { + type = lazyPluginsListType; + default = [ ]; + description = "List of plugins"; + }; }; - }; - config = mkIf cfg.enable { - extraPlugins = [ pkgs.vimPlugins.lazy-nvim ]; - - extraPackages = [ cfg.gitPackage ]; - - extraConfigLua = - let - pluginToLua = - plugin: - let - keyExists = keyToCheck: attrSet: lib.elem keyToCheck (lib.attrNames attrSet); - in - if isDerivation plugin then - { dir = "${lazyPath}/${lib.getName plugin}"; } - else - { - "__unkeyed" = plugin.name; - - inherit (plugin) - cmd - cond - config - dev - enabled - event - ft - init - keys - lazy - main - module - name - optional - opts - priority - submodules - ; - - dependencies = helpers.ifNonNull' plugin.dependencies ( - if isList plugin.dependencies then (pluginListToLua plugin.dependencies) else plugin.dependencies - ); - - dir = - if plugin ? dir && plugin.dir != null then plugin.dir else "${lazyPath}/${lib.getName plugin.pkg}"; - }; - - pluginListToLua = map pluginToLua; - - plugins = pluginListToLua cfg.plugins; - - packedPlugins = if length plugins == 1 then head plugins else plugins; - in - mkIf (cfg.plugins != [ ]) '' - require('lazy').setup( - { - dev = { - path = "${lazyPath}", - patterns = {"."}, - fallback = false - }, - spec = ${helpers.toLuaObject packedPlugins} - } - ) - ''; - }; + extraConfig = + cfg: + let + # The `pkg` property isn't a part of the `lazy.nvim` plugin spec, while + # it shouldn't do any harm but it does take up unnecessary space in the + # init.lua file. Since we're done using it we will strip it from the + # final list of specs. + removePkgAttrFromPlugin = + plugin: + builtins.removeAttrs plugin [ "pkg" ] + // lib.optionalAttrs (((plugin.dependencies or null) != null) && lib.isList plugin.dependencies) { + dependencies = map removePkgAttrFromPlugin plugin.dependencies; + }; + + removePkgAttrFromPlugins = plugins: map removePkgAttrFromPlugin plugins; + in + { + extraPackages = [ + cfg.gitPackage + cfg.luarocksPackage + ]; + plugins.lazy.settings.spec = removePkgAttrFromPlugins cfg.plugins; + }; } diff --git a/tests/test-sources/plugins/pluginmanagers/lazy.nix b/tests/test-sources/plugins/pluginmanagers/lazy.nix index 90c0bdb39e..216665be66 100644 --- a/tests/test-sources/plugins/pluginmanagers/lazy.nix +++ b/tests/test-sources/plugins/pluginmanagers/lazy.nix @@ -1,17 +1,52 @@ { pkgs, ... }: + +# Note: do not use `plenary-nvim` or any plugin that is a `rockspec` for tests, +# this is because `lazy.nvim` by default uses the luarocks package manager to +# process rockspecs. It might be possible to use a rockspec in a test if the +# rockspec itself does not depend on any other rockspecs but this has not been +# tested (See: +# https://github.com/nix-community/nixvim/pull/2082#discussion_r1746585453). +# +# Also the plugins and dependency combinations used in the tests are +# arbitrary. + { # Empty configuration empty = { plugins.lazy.enable = true; }; - test = { + no-packages = { + plugins.lazy = { + enable = true; + gitPackage = null; + luarocksPackage = null; + }; + }; + + single-package = { + plugins.lazy = with pkgs.vimPlugins; { + enable = true; + + plugins = [ vim-closer ]; + }; + }; + + general-tests = { plugins.lazy = with pkgs.vimPlugins; { enable = true; plugins = [ vim-closer + # Test freeform + { + pkg = trouble-nvim; + # The below is not actually a property in the `lazy.nvim` plugin spec + # but is purely to test freeform capabilities of the `lazyPluginType`. + blah = "test"; + } + # Load on specific commands { pkg = vim-dispatch; @@ -50,6 +85,125 @@ cmd = "ALEEnable"; } + # Plugins can have post-install/update hooks + { + pkg = markdown-preview-nvim; + cmd = "MarkdownPreview"; + } + + # Post-install/update hook with neovim command + { + pkg = nvim-treesitter; + opts = { + ensure_installed = { }; + }; + } + + # Plugin names can also be passed by themselves so long as they're + # defined somewhere else in the `plugins` list. + "oil" + { + name = "oil"; + pkg = oil-nvim; + } + ]; + }; + }; + + single-dir-only-plugin = { + plugins.lazy = with pkgs.vimPlugins; { + enable = true; + plugins = [ { dir = "${LazyVim}"; } ]; + }; + }; + + multiple-dir-only-plugins = { + plugins.lazy = with pkgs.vimPlugins; { + enable = true; + plugins = [ + { dir = "${LazyVim}"; } + { dir = "${vim-closer}"; } + { dir = "${vim-dispatch}"; } + ]; + }; + }; + + disabling_plugins = { + plugins.lazy = + with pkgs.vimPlugins; + let + test_plugin1_path = "${yanky-nvim}"; + test_plugin2_path = "${whitespace-nvim}"; + in + { + enable = true; + plugins = [ + # Enable and then later disable a plugin using it's custom name. + { + name = "test-mini-nvim"; + pkg = mini-nvim; + enabled = true; + } + { + __unkeyed = "test-mini-nvim"; + enabled = false; + } + + # Enable and then later disable a plugin using `pkg`. + { + pkg = vim-closer; + enabled = true; + } + { + pkg = vim-closer; + enabled = false; + } + + # Enable plugin using `pkg` and then later disable it using the nix + # package's default name. + { + pkg = vim-dispatch; + enabled = true; + } + { + __unkeyed = "vim-dispatch"; + enabled = true; + } + + # Enable a plugin using it's path given to `dir` + { + dir = test_plugin1_path; + # We don't need to specify name to be able to disable it later, + # it's just here purely for the sake of the test case. + name = "test_plugin1"; + enabled = true; + } + # Disable previously enabled test_plugin1 using `dir`. + { + dir = test_plugin1_path; + enabled = false; + } + + # Enable a plugin using it's path given to `dir`, but not giving it a + # custom name. + { + dir = test_plugin2_path; + enabled = true; + } + # Disable previously enabled test_plugin2 using `dir`. + { + dir = test_plugin2_path; + enabled = false; + } + ]; + + }; + }; + + plugins-with-dependencies = { + plugins.lazy = { + enable = true; + plugins = with pkgs.vimPlugins; [ # Plugins can have dependencies on other plugins { pkg = completion-nvim; @@ -66,34 +220,186 @@ ]; } - # Plugins can have post-install/update hooks + # Use dependency and run lua function after load { - pkg = markdown-preview-nvim; - cmd = "MarkdownPreview"; + pkg = nvim-colorizer-lua; + dependencies = [ nvim-cursorline ]; + config = '' + function() + require("nvim-cursorline").setup{} + end ''; } - # Post-install/update hook with neovim command + # Dependencies can be a single package { - pkg = nvim-treesitter; - opts = { - ensure_installed = { }; - }; + pkg = LazyVim; + dependencies = trouble-nvim; } - # Use dependency and run lua function after load + # Dependencies can be multiple packages + { + pkg = nvim-cmp; + dependencies = [ + cmp-cmdline + cmp-vsnip + ]; + } + + # Dependencies can be a single name that is defined elsewhere + { + pkg = nvim-autopairs; + dependencies = "luasnip"; + } + { + name = "luasnip"; + pkg = luasnip; + } + + # Dependencies can be a list of names that are defined elsewhere + { + pkg = nvim-lightbulb; + dependencies = [ + "nvim-lspconfig" + "cmp-nvim-lua" + "cmp-nvim-lsp" + ]; + } + { + pkg = nvim-lspconfig; + name = "nvim-lspconfig"; + } + { + pkg = cmp-nvim-lua; + name = "cmp-nvim-lua"; + } { - pkg = gitsigns-nvim; - dependencies = [ plenary-nvim ]; - config = ''function() require("gitsigns").setup() end''; + pkg = cmp-nvim-lsp; + name = "cmp-nvim-lsp"; } + + # Dependencies can be a list of names that are defined elsewhere in the + # list of plugins. If the names given are just the default names of a + # package they need not be explicitly defined. + { + pkg = nui-nvim; + dependencies = [ + "nvim-web-devicons" + "lsp-colors.nvim" + ]; + } + nvim-web-devicons + lsp-colors-nvim ]; }; }; - no-packages = { + out-of-tree-plugins = { + # Don't run neovim for this test, as it's purely to test module evaluation. + test.runNvim = false; plugins.lazy = { enable = true; - gitPackage = null; + plugins = [ + # Short or long plugin URL's can be passed via the `__unkeyed` attribute. + { + "__unkeyed" = "echasnovski/mini.ai"; + name = "m.ai"; + enabled = true; + version = false; + } + + # Short plugin URL's can't be passed via the `url` attribute, unless + # it's also passed to the `__unkeyed` attribute. + { + url = "https://github.com/norcalli/nvim-colorizer.lua"; + name = "colorizer"; + enabled = true; + version = false; + } + + # Short or long plugin URL's can be passed by themselves + "ggandor/lightspeed.nvim" + "https://github.com/ggandor/leap.nvim" + # long URL's can be later referenced by their short URL + { + __unkeyed = "ggandor/leap.nvim"; + name = "leap"; + } + # long URL's can be later referenced by their short URL + { + __unkeyed = "ggandor/leap.nvim"; + name = "leap"; + } + # Long URL's can also be refernced as is + { + __unkeyed = "https://github.com/ggandor/leap.nvim"; + name = "lightspeed"; + } + + # Plugin names can also be passed by themselves so long as they're + # defined somewhere else in the `plugins` list. + "oil" + { + name = "oil"; + url = "https://github.com/stevearc/oil.nvim"; + } + ]; }; }; + + local-plugin-directory-plugins = { + plugins.lazy = + with pkgs.vimPlugins; + let + inherit (pkgs) lib; + mkEntryFromDrv = drv: { + name = "${lib.getName drv}"; + path = drv; + }; + + # Symlink a bunch of test packages to a path in the nix store + devPath = pkgs.linkFarm "dev-test-plugins" ( + map mkEntryFromDrv [ + nui-nvim + vim-vsnip-integ + vim-vsnip + completion-nvim + ] + ); + in + { + enable = true; + settings = { + dev = { + # Use `devPath` to simulate a local plugin directory path + path = "${devPath}"; + patterns = [ "." ]; + fallback = false; + }; + }; + + plugins = [ + # Use local plugin that resides in path specified in `devPath` i.e. + # `plugins.lazy.settings.dev.path` (See: https://lazy.folke.io/spec) + { + __unkeyed = "nui.nvim"; + dev = true; + } + # local plugins can have dependencies on other plugins + { + __unkeyed = "completion.nvim"; + dev = true; + dependencies = [ + { + __unkeyed = "vim.vsnip"; + dev = true; + } + { + __unkeyed = "vim.vsnip.integ"; + dev = true; + } + ]; + } + ]; + }; + }; }