diff --git a/modules/default.nix b/modules/default.nix index 7b2c2c18..739a30db 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -19,6 +19,7 @@ ./lua-loader.nix ./opts.nix ./output.nix + ./performance.nix ./plugins.nix ]; } diff --git a/modules/performance.nix b/modules/performance.nix new file mode 100644 index 00000000..9709b3f0 --- /dev/null +++ b/modules/performance.nix @@ -0,0 +1,54 @@ +{ lib, ... }: +let + inherit (lib) types; +in +{ + options.performance = { + combinePlugins = { + enable = lib.mkEnableOption "combinePlugins" // { + description = '' + Whether to enable EXPERIMENTAL option to combine all plugins + into a single plugin pack. It can significantly reduce startup time, + but all your plugins must have unique filenames and doc tags. + Any collision will result in a build failure. + Only standard neovim runtime directories are linked to the combined plugin. + If some of your plugins contain important files outside of standard + directories, add these paths to `pathsToLink` option. + ''; + }; + pathsToLink = lib.mkOption { + type = with types; listOf str; + default = [ ]; + example = [ "/data" ]; + description = "List of paths to link into a combined plugin pack."; + }; + }; + }; + + config.performance = { + # Set option value with default priority so that values are appended by default + combinePlugins.pathsToLink = [ + # :h rtp + "/autoload" + "/colors" + "/compiler" + "/doc" + "/ftplugin" + "/indent" + "/keymap" + "/lang" + "/lua" + "/pack" + "/parser" + "/plugin" + "/queries" + "/rplugin" + "/spell" + "/syntax" + "/tutor" + "/after" + # ftdetect + "/ftdetect" + ]; + }; +} diff --git a/modules/top-level/output.nix b/modules/top-level/output.nix index c227c667..06b8d90b 100644 --- a/modules/top-level/output.nix +++ b/modules/top-level/output.nix @@ -74,15 +74,54 @@ in config = let + # Plugin list extended with dependencies + allPlugins = + let + pluginWithItsDeps = p: [ p ] ++ builtins.concatMap pluginWithItsDeps p.dependencies or [ ]; + in + lib.unique (builtins.concatMap pluginWithItsDeps config.extraPlugins); + + # All plugins with doc tags removed + allPluginsOverridden = map ( + plugin: + plugin.overrideAttrs (prev: { + nativeBuildInputs = lib.remove pkgs.vimUtils.vimGenDocHook prev.nativeBuildInputs or [ ]; + configurePhase = builtins.concatStringsSep "\n" ( + builtins.filter (s: s != ":") [ + prev.configurePhase or ":" + "rm -vf doc/tags" + ] + ); + }) + ) allPlugins; + + # Combine all plugins into a single pack + pluginPack = pkgs.vimUtils.toVimPlugin ( + pkgs.buildEnv { + name = "plugin-pack"; + paths = allPluginsOverridden; + inherit (config.performance.combinePlugins) pathsToLink; + # Remove empty directories and activate vimGenDocHook + postBuild = '' + find $out -type d -empty -delete + runHook preFixup + ''; + } + ); + + # Combined plugins + combinedPlugins = [ pluginPack ]; + + # Plugins to use in finalPackage + plugins = if config.performance.combinePlugins.enable then combinedPlugins else config.extraPlugins; + defaultPlugin = { plugin = null; config = ""; optional = false; }; - normalizedPlugins = map ( - x: defaultPlugin // (if x ? plugin then x else { plugin = x; }) - ) config.extraPlugins; + normalizedPlugins = map (x: defaultPlugin // (if x ? plugin then x else { plugin = x; })) plugins; neovimConfig = pkgs.neovimUtils.makeNeovimConfig ( { diff --git a/tests/test-sources/modules/performance/combine-plugins.nix b/tests/test-sources/modules/performance/combine-plugins.nix new file mode 100644 index 00000000..8a084923 --- /dev/null +++ b/tests/test-sources/modules/performance/combine-plugins.nix @@ -0,0 +1,105 @@ +{ pkgs, ... }: +{ + # Test basic functionality + default.module = + { config, ... }: + { + performance.combinePlugins.enable = true; + extraPlugins = with pkgs.vimPlugins; [ + nvim-lspconfig + nvim-treesitter + ]; + extraConfigLuaPost = '' + -- Plugins are loadable + require("lspconfig") + require("nvim-treesitter") + + -- No separate plugin entries in nvim_list_runtime_paths + assert(not vim.iter(vim.api.nvim_list_runtime_paths()):any(function(entry) + return entry:find("treesitter") or entry:find("lspconfig") + end), "separate plugins are found in runtime") + + -- Help tags are generated + assert(vim.fn.getcompletion("lspconfig", "help")[1], "no help tags for nvim-lspconfig") + assert(vim.fn.getcompletion("nvim-treesitter", "help")[1], "no help tags for nvim-treesitter") + ''; + assertions = [ + { + assertion = builtins.length config.finalPackage.packpathDirs.myNeovimPackages.start == 1; + message = "More than one plugin is defined in packpathDirs, expected one plugin pack."; + } + ]; + }; + + # Test disabled option + disabled.module = + { config, ... }: + { + performance.combinePlugins.enable = false; + extraPlugins = with pkgs.vimPlugins; [ + nvim-lspconfig + nvim-treesitter + ]; + assertions = [ + { + assertion = builtins.length config.finalPackage.packpathDirs.myNeovimPackages.start >= 2; + message = "Only one plugin is defined in packpathDirs, expected at least two."; + } + ]; + }; + + # Test that plugin dependencies are handled + dependencies.module = + { config, ... }: + { + performance.combinePlugins.enable = true; + extraPlugins = with pkgs.vimPlugins; [ + # Depends on nvim-cmp + cmp-dictionary + # Depends on telescope-nvim which itself depends on plenary-nvim + telescope-undo-nvim + ]; + extraConfigLuaPost = '' + -- Plugins and its dependencies are loadable + require("cmp_dictionary") + require("cmp") + require("telescope-undo") + require("telescope") + require("plenary") + ''; + assertions = [ + { + assertion = builtins.length config.finalPackage.packpathDirs.myNeovimPackages.start == 1; + message = "More than one plugin is defined in packpathDirs."; + } + ]; + }; + + # Test that pathsToLink option works + paths-to-link.module = + { config, ... }: + { + performance.combinePlugins = { + enable = true; + # fzf native library is in build directory + pathsToLink = [ "/build" ]; + }; + extraPlugins = [ pkgs.vimPlugins.telescope-fzf-native-nvim ]; + extraConfigLuaPost = '' + -- Native library is in runtimepath + assert( + vim.api.nvim_get_runtime_file("build/libfzf.so", false)[1], + "build/libfzf.so is not found in runtimepath" + ) + + -- Native library is loadable + require("fzf_lib") + ''; + assertions = [ + { + assertion = builtins.length config.finalPackage.packpathDirs.myNeovimPackages.start == 1; + message = "More than one plugin is defined in packpathDirs."; + } + ]; + }; +}