diff --git a/lib/default.nix b/lib/default.nix index 3d357d7c66..d7534078aa 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -66,6 +66,7 @@ lib.fix ( defaultNullOpts mkCompositeOption mkCompositeOption' + mkLazyLoadOption mkNullOrLua mkNullOrLua' mkNullOrLuaFn diff --git a/lib/neovim-plugin.nix b/lib/neovim-plugin.nix index 4fafb436ff..e15856d4dc 100644 --- a/lib/neovim-plugin.nix +++ b/lib/neovim-plugin.nix @@ -89,6 +89,7 @@ options.${namespace}.${name} = { enable = lib.mkEnableOption originalName; + lazyLoad = lib.nixvim.mkLazyLoadOption originalName; package = if lib.isOption package then package @@ -161,8 +162,39 @@ # Add the plugin setup code `require('foo').setup(...)` to the lua configuration (lib.optionalAttrs callSetup { ${namespace}.${name}.luaConfig.content = setupCode; }) - # Write the lua configuration `luaConfig.content` to the config file - (setLuaConfig cfg.luaConfig.content) + # Write the lua configuration `luaConfig.content` to the config file when lazy loading is not enabled + (lib.mkIf (!cfg.lazyLoad.enable) (setLuaConfig cfg.luaConfig.content)) + + # When lazy loading is enabled for this plugin, route its configuration to the enabled provider + (lib.mkIf cfg.lazyLoad.enable { + assertions = [ + { + assertion = (isColorscheme && colorscheme != null) || cfg.lazyLoad.settings != { }; + message = "You have enabled lazy loading for ${originalName} but have not provided any configuration."; + } + ]; + plugins.lz-n = { + plugins = [ + ( + { + __unkeyed-1 = originalName; + # Use provided after, otherwise fallback to normal function wrapped lua content + after = + let + after = cfg.lazyLoad.settings.after or null; + default = "function()\n " + cfg.luaConfig.content + " \nend"; + in + if (lib.isString after || lib.types.rawLua.check after) then after else default; + colorscheme = lib.mkIf isColorscheme (cfg.lazyLoad.settings.colorscheme or colorscheme); + } + // lib.removeAttrs cfg.lazyLoad.settings [ + "after" + "colorscheme" + ] + ) + ]; + }; + }) ]) ) ); diff --git a/lib/options.nix b/lib/options.nix index 02ce903acf..a004087486 100644 --- a/lib/options.nix +++ b/lib/options.nix @@ -333,5 +333,53 @@ rec { else example; }; + + mkLazyLoadOption = + originalName: + lib.mkOption { + description = '' + Lazy-load settings for ${originalName}. + + > [!WARNING] + > This is an experimental option and may not work as expected with all plugins. + > The API may change without notice. + > Please report any issues you encounter. + ''; + default = { }; + type = types.submodule ( + { config, ... }: + { + options = { + enable = lib.mkOption { + default = lib.any (x: x != null) (builtins.attrValues config.settings); + defaultText = lib.literalMD '' + `true` when `settings` has a non-null attribute + ''; + description = '' + lazy-loading for ${originalName} + ''; + }; + + settings = lib.nixvim.mkSettingsOption { + description = '' + Lazy provider configuration settings. + + Check your lazy loading provider's documentation on settings to configure. + ''; + example = { + cmd = "Neotest"; + keys = [ + { + __unkeyed-1 = "nt"; + __unkeyed-3 = "Neotest summary"; + desc = "Summary toggle"; + } + ]; + }; + }; + }; + } + ); + }; } // removed diff --git a/modules/lazyload.nix b/modules/lazyload.nix index 17bb325f9a..832d425796 100644 --- a/modules/lazyload.nix +++ b/modules/lazyload.nix @@ -26,5 +26,33 @@ in ''; } ]; + warnings = + let + ignoredPackages = [ + # removed + "treesitter-playground" + # renamed + "surround" + "null-ls" + "wilder-nvim" + ]; + + pluginsWithLazyLoad = builtins.filter ( + x: + !(lib.elem x ignoredPackages) + && lib.hasAttr "lazyLoad" config.plugins.${x} + && config.plugins.${x}.lazyLoad.enable + ) (builtins.attrNames config.plugins); + count = builtins.length pluginsWithLazyLoad; + in + lib.optionals (count > 0 && !config.plugins.lz-n.enable) [ + '' + You have enabled lazy loading support for the following plugins but have not enabled a lazy loading provider. + ${lib.concatImapStringsSep "\n" (i: x: "${toString i}. plugins.${x}") pluginsWithLazyLoad} + + Currently supported lazy providers: + - lz-n + '' + ]; }; } diff --git a/plugins/by-name/typescript-tools/default.nix b/plugins/by-name/typescript-tools/default.nix index ec1d861ba3..1c905603fe 100644 --- a/plugins/by-name/typescript-tools/default.nix +++ b/plugins/by-name/typescript-tools/default.nix @@ -221,6 +221,7 @@ lib.nixvim.neovim-plugin.mkNeovimPlugin { ]; in { + # TODO: handle lazy loading properly plugins.lsp.postConfig = let # TODO:: introduced 10-22-2024: remove after 24.11 diff --git a/tests/test-sources/plugins/lazyloading/lz-n.nix b/tests/test-sources/plugins/lazyloading/lz-n.nix new file mode 100644 index 0000000000..f5a50a9154 --- /dev/null +++ b/tests/test-sources/plugins/lazyloading/lz-n.nix @@ -0,0 +1,345 @@ +let + getFirstLznPlugin = + config: + let + inherit (config.plugins.lz-n) plugins; + in + if plugins == [ ] then null else builtins.head plugins; + + getPluginKeys = plugin: if plugin != null && builtins.isList plugin.keys then plugin.keys else [ ]; +in +{ + lazy-load-neovim-plugin-configured = + { config, lib, ... }: + { + test.runNvim = false; + plugins = { + lz-n = { + enable = true; + }; + + neotest = { + enable = true; + lazyLoad = { + enable = true; + settings = { + keys = [ + { + __unkeyed-1 = "nt"; + __unkeyed-3 = "Neotest summary"; + desc = "Summary toggle"; + } + ]; + }; + }; + }; + }; + + assertions = + let + plugin = getFirstLznPlugin config; + keys = getPluginKeys plugin; + in + [ + { + assertion = (builtins.length config.plugins.lz-n.plugins) == 1; + message = "`lz-n.plugins` should have contained a single plugin configuration, but contained ${builtins.toJSON config.plugins.lz-n.plugins}"; + } + { + assertion = (builtins.length keys) == 1; + message = "`lz-n.plugins[0].keys` should have contained a configuration."; + } + { + assertion = + plugin != null && lib.hasInfix config.plugins.neotest.luaConfig.content plugin.after.__raw; + message = "`lz-n.plugins[0].after` should have contained `neotest` lua content."; + } + ]; + }; + + lazy-load-lz-n-configured = + { config, lib, ... }: + { + test.runNvim = false; + plugins = { + codesnap = { + enable = true; + }; + lz-n = { + enable = true; + plugins = [ + { + __unkeyed-1 = "codesnap"; + after.__raw = '' + function() + ${config.plugins.codesnap.luaConfig.content} + end + ''; + cmd = [ + "CodeSnap" + "CodeSnapSave" + "CodeSnapHighlight" + "CodeSnapSaveHighlight" + ]; + keys = [ + { + __unkeyed-1 = "cc"; + __unkeyed-3 = "CodeSnap"; + desc = "Copy"; + mode = "v"; + } + { + __unkeyed-1 = "cs"; + __unkeyed-3 = "CodeSnapSave"; + desc = "Save"; + mode = "v"; + } + { + __unkeyed-1 = "ch"; + __unkeyed-3 = "CodeSnapHighlight"; + desc = "Highlight"; + mode = "v"; + } + { + __unkeyed-1 = "cH"; + __unkeyed-3 = "CodeSnapSaveHighlight"; + desc = "Save Highlight"; + mode = "v"; + } + ]; + } + ]; + }; + }; + assertions = + let + plugin = getFirstLznPlugin config; + keys = getPluginKeys plugin; + cmd = plugin.cmd or null; + cmd' = lib.optionals (builtins.isList cmd) cmd; + in + [ + { + assertion = (builtins.length config.plugins.lz-n.plugins) == 1; + message = "`lz-n.plugins` should have contained a single plugin configuration, but contained ${builtins.toJSON config.plugins.lz-n.plugins}"; + } + { + assertion = (builtins.length keys) == 4; + message = "`lz-n.plugins[0].keys` should have contained 4 key configurations, but contained ${builtins.toJSON plugin.keys}"; + } + { + assertion = (builtins.length cmd') == 4; + message = "`lz-n.plugins[0].cmd` should have contained 4 cmd configurations, but contained ${builtins.toJSON plugin.cmd}"; + } + { + assertion = lib.hasInfix config.plugins.codesnap.luaConfig.content plugin.after.__raw; + message = "`lz-n.plugins[0].after` should have contained `codesnap` lua content."; + } + ]; + }; + + dont-lazy-load-colorscheme-automatically = + { config, ... }: + { + test.runNvim = false; + colorschemes.catppuccin.enable = true; + plugins = { + lz-n = { + enable = true; + }; + }; + + assertions = [ + { + assertion = (builtins.length config.plugins.lz-n.plugins) == 0; + message = "`lz-n.plugins` should have contained no plugin configuration, but contained ${builtins.toJSON config.plugins.lz-n.plugins}"; + } + ]; + }; + + dont-lazy-load-unconfigured = + { config, ... }: + { + test.runNvim = false; + plugins = { + neotest = { + enable = true; + # Empty attrset shouldn't trigger lazy loading + lazyLoad = { }; + }; + lz-n = { + enable = true; + }; + }; + + assertions = [ + { + assertion = (builtins.length config.plugins.lz-n.plugins) == 0; + message = "`lz-n.plugins` should have contained no plugin configuration, but contained ${builtins.toJSON config.plugins.lz-n.plugins}"; + } + ]; + }; + + lazy-load-colorscheme-properly = + { config, lib, ... }: + { + test.runNvim = false; + colorschemes.catppuccin = { + enable = true; + lazyLoad.enable = true; + }; + + plugins = { + lz-n = { + enable = true; + }; + }; + + assertions = + let + plugin = getFirstLznPlugin config; + in + [ + { + assertion = (builtins.length config.plugins.lz-n.plugins) == 1; + message = "`lz-n.plugins` should have contained no plugin configuration, but contained ${builtins.toJSON config.plugins.lz-n.plugins}"; + } + { + assertion = plugin.colorscheme == "catppuccin"; + message = "`lz-n.plugins[0].colorscheme` should have been `catppuccin`, but contained ${builtins.toJSON plugin.colorscheme}"; + } + { + assertion = lib.hasInfix config.colorschemes.catppuccin.luaConfig.content plugin.after.__raw; + message = "`lz-n.plugins[0].after` should have contained `catppuccin` lua content."; + } + ]; + }; + + lazy-load-enabled-automatically = + { config, ... }: + { + test.runNvim = false; + plugins = { + lz-n = { + enable = true; + }; + neotest = { + enable = true; + lazyLoad = { + # Not setting lazyLoad.enable with configuration should enable + settings = { + keys = [ + { + __unkeyed-1 = "nt"; + __unkeyed-3 = "Neotest summary"; + desc = "Summary toggle"; + } + ]; + }; + }; + }; + }; + + assertions = + let + plugin = getFirstLznPlugin config; + keys = getPluginKeys plugin; + in + [ + { + assertion = (builtins.length config.plugins.lz-n.plugins) == 1; + message = "`lz-n.plugins` should have contained a single plugin configuration, but contained ${builtins.toJSON config.plugins.lz-n.plugins}"; + } + { + assertion = (builtins.length keys) == 1; + message = "`lz-n.plugins[0].keys` should have contained a configuration."; + } + ]; + }; + + wrap-functionless-luaConfig = + { config, ... }: + { + test.runNvim = false; + plugins = { + lz-n = { + enable = true; + }; + web-devicons.enable = false; + telescope = { + enable = true; + lazyLoad = { + enable = true; + settings = { + cmd = [ "Telescope" ]; + }; + }; + }; + }; + + assertions = + let + plugin = getFirstLznPlugin config; + in + [ + { + assertion = (builtins.length config.plugins.lz-n.plugins) == 1; + message = "`lz-n.plugins` should have contained a single plugin configuration, but contained ${builtins.toJSON config.plugins.lz-n.plugins}"; + } + { + assertion = + plugin.after.__raw == "function()\n " + config.plugins.telescope.luaConfig.content + " \nend"; + message = "`lz-n.plugins[0].after` should have contained a function wrapped `telescope` lua content."; + } + ]; + }; + + use-provided-raw-after = + { config, ... }: + { + test.runNvim = false; + plugins = { + lz-n = { + enable = true; + }; + web-devicons.enable = false; + telescope = { + enable = true; + lazyLoad = { + enable = true; + settings = { + after.__raw = '' + function() + -- test string + ${config.plugins.telescope.luaConfig.content} + end + ''; + cmd = [ "Telescope" ]; + }; + }; + }; + }; + + assertions = + let + plugin = getFirstLznPlugin config; + in + [ + { + assertion = (builtins.length config.plugins.lz-n.plugins) == 1; + message = "`lz-n.plugins` should have contained a single plugin configuration, but contained ${builtins.toJSON config.plugins.lz-n.plugins}"; + } + { + assertion = + plugin.after.__raw == '' + function() + -- test string + ${config.plugins.telescope.luaConfig.content} + end + ''; + message = "`lz-n.plugins[0].after` should have contained a function wrapped `telescope` lua content."; + } + ]; + }; + +}