{ pkgs, ... }: let inherit (pkgs) lib; # Count plugins of given type excluding 'build.extraFiles' pluginCount = pkg: files: type: builtins.length (builtins.filter (p: p != files) pkg.packpathDirs.myNeovimPackages.${type}); # Stub plugins mkPlugin = name: args: pkgs.vimUtils.buildVimPlugin ( { pname = name; version = "2025-04-27"; src = pkgs.runCommand "${name}-source" { } '' mkdir "$out" # Add some colliding files echo "# ${name}" > "$out/README.md" echo "Copyright (c) ${name}" > "$out/LICENSE" mkdir "$out/tests" echo "return '${name}'" > "$out/tests/test.lua" # Add import path mkdir -p "$out/lua/${name}" echo "return '${name}'" > "$out/lua/${name}/init.lua" # Add doc mkdir "$out/doc" echo "*${name}.txt* ${name}" > "$out/doc/${name}.txt" ''; } // args ); # Simple plugins without any features simplePlugin1 = mkPlugin "simple-plugin-1" { }; simplePlugin2 = mkPlugin "simple-plugin-2" { }; simplePlugin3 = mkPlugin "simple-plugin-3" { }; # Plugins with dependencies pluginWithDeps1 = mkPlugin "plugin-with-deps-1" { dependencies = [ simplePlugin1 ]; }; pluginWithDeps2 = mkPlugin "plugin-with-deps-2" { dependencies = [ simplePlugin2 ]; }; pluginWithDeps3 = mkPlugin "plugin-with-deps-3" { dependencies = [ simplePlugin3 ]; }; # Plugin with recursive dependencies pluginWithRecDeps = mkPlugin "plugin-with-rec-deps" { dependencies = [ pluginWithDeps1 pluginWithDeps2 ]; }; # Plugin with non-standard files pluginWithExtraFiles = mkPlugin "plugin-with-extra-files" { postInstall = '' mkdir "$out/_extra" touch "$out/_extra/test" ''; }; # Plugins with Python dependencies pluginWithPyDeps1 = mkPlugin "plugin-with-py-deps-1" { passthru.python3Dependencies = ps: [ ps.pyyaml ]; }; pluginWithPyDeps2 = mkPlugin "plugin-with-py-deps-2" { passthru.python3Dependencies = ps: [ ps.pyyaml ]; }; pluginWithPyDeps3 = mkPlugin "plugin-with-py-deps-3" { passthru.python3Dependencies = ps: [ ps.requests ]; }; in { # Test basic functionality default = { config, ... }: let extraPlugins = [ simplePlugin1 simplePlugin2 simplePlugin3 ]; in { performance.combinePlugins.enable = true; inherit extraPlugins; extraConfigLuaPost = lib.concatMapStringsSep "\n" ( name: # lua '' -- Plugin is loadable require("${name}") -- No separate plugin entry in vim.api.nvim_list_runtime_paths() assert(not vim.iter(vim.api.nvim_list_runtime_paths()):any(function(entry) return entry:find("${name}", 1, true) end), "plugin '${name}' found in runtime, expected to be combined") -- Help tags are generated assert(vim.fn.getcompletion("${name}", "help")[1], "no help tags for '${name}'") '') (map lib.getName extraPlugins); assertions = [ { assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" == 1; message = "More than one plugin is defined in packpathDirs, expected one plugin pack."; } ]; }; # Test disabled option disabled = { config, ... }: let extraPlugins = [ simplePlugin1 simplePlugin2 simplePlugin3 ]; in { performance.combinePlugins.enable = false; inherit extraPlugins; extraConfigLuaPost = lib.concatMapStringsSep "\n" ( name: # lua '' -- Separate plugin entry in vim.api.nvim_list_runtime_paths() assert(vim.iter(vim.api.nvim_list_runtime_paths()):any(function(entry) return entry:find("${name}", 1, true) end), "plugin '${name}' isn't found in runtime as a separate entry, expected not to be combined") '') (map lib.getName extraPlugins); assertions = [ { assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" >= 2; message = "Only one plugin is defined in packpathDirs, expected at least two."; } ]; }; # Test that plugin dependencies are handled dependencies = { config, ... }: { performance.combinePlugins.enable = true; extraPlugins = [ # Depends on pluginWithDeps1 and pluginWithDeps2 which themselves depend on simplePlugin1 and simplePlugin2 pluginWithRecDeps # Depends on simplePlugin3 pluginWithDeps3 # Duplicated dependency pluginWithDeps2 # Duplicated plugin simplePlugin2 ]; extraConfigLuaPost = '' -- Plugin 'pluginWithRecDeps' and its dependencies are loadable require("plugin-with-rec-deps") require("plugin-with-deps-1") require("plugin-with-deps-2") require("simple-plugin-1") require("simple-plugin-2") -- Plugin 'pluginWithDeps3' and its dependencies are loadable require("plugin-with-deps-3") require("simple-plugin-3") ''; assertions = [ { assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" == 1; message = "More than one plugin is defined in packpathDirs."; } ]; }; # Test that pathsToLink option works paths-to-link = { config, ... }: { performance.combinePlugins = { enable = true; pathsToLink = [ "/_extra" ]; }; extraPlugins = [ pluginWithExtraFiles ]; extraConfigLuaPost = '' -- Test file is in runtime assert( vim.api.nvim_get_runtime_file("_extra/test", false)[1], "'_extra/test' file isn't found in runtime, expected to be found" ) ''; assertions = [ { assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" == 1; message = "More than one plugin is defined in packpathDirs."; } ]; }; # Test that plugin python3 dependencies are handled python-dependencies = { config, ... }: { performance.combinePlugins.enable = true; extraPlugins = [ # No python3 dependencies simplePlugin1 # Duplicated python3-pyyaml dependency pluginWithPyDeps1 pluginWithPyDeps2 # Python3-requests dependency pluginWithPyDeps3 ]; extraConfigLuaPost = '' -- Python modules are importable vim.cmd.py3("import yaml") vim.cmd.py3("import requests") ''; assertions = [ { assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" == 1; message = "More than one plugin is defined in packpathDirs."; } ]; }; # Test that optional plugins are handled optional-plugins = { config, ... }: { performance.combinePlugins.enable = true; extraPlugins = [ # Start plugins simplePlugin1 simplePlugin2 # Optional plugin { plugin = simplePlugin3; optional = true; } # Optional plugin with dependency on simplePlugin1 # Dependencies should not be duplicated { plugin = pluginWithDeps1; optional = true; } ]; extraConfigLuaPost = '' -- Start plugins are loadable require("simple-plugin-1") require("simple-plugin-2") -- Opt plugins are not loadable local ok = pcall(require, "simple-plugin-3") assert(not ok, "simplePlugin3 is loadable, expected it to be an opt plugin") ok = pcall(require, "plugin-with-deps-1") assert(not ok, "pluginWithDeps1 is loadable, expected it to be an opt plugin") -- Load plugins vim.cmd.packadd("simple-plugin-3") vim.cmd.packadd("plugin-with-deps-1") -- Now opt plugins are loadable require("simple-plugin-3") require("plugin-with-deps-1") -- Only one copy of simplePlugin1 should be available local num_plugins = #vim.api.nvim_get_runtime_file("lua/simple-plugin-1/init.lua", true) assert(num_plugins == 1, "expected 1 copy of simplePlugin1, got " .. num_plugins) ''; assertions = [ { assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" == 1; message = "More than one start plugin is defined in packpathDirs"; } { assertion = pluginCount config.build.nvimPackage config.build.extraFiles "opt" == 2; message = "Less than two opt plugins are defined in packpathDirs"; } ]; }; # Test that plugin configs are handled configs = { config, ... }: { performance.combinePlugins.enable = true; extraPlugins = [ # A plugin without config simplePlugin1 # Plugin with config { plugin = simplePlugin2; config = "let g:simple_plugin_2 = 1"; } # Optional plugin with config { plugin = simplePlugin3; optional = true; config = "let g:simple_plugin_3 = 1"; } ]; extraConfigLuaPost = '' -- Configs are evaluated assert(vim.g.simple_plugin_2 == 1, "simplePlugin2's config isn't evaluated") assert(vim.g.simple_plugin_3 == 1, "simplePlugin3's config isn't evaluated") ''; assertions = [ { assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" == 1; message = "More than one start plugin is defined in packpathDirs"; } ]; }; # Test that config.build.extraFiles is not combined files-plugin = { config, ... }: { performance.combinePlugins.enable = true; extraPlugins = [ simplePlugin1 simplePlugin2 ]; # Ensure that build.extraFiles is added to extraPlugins wrapRc = true; # Extra user files colliding with plugins extraFiles = { "lua/simple-plugin-1/init.lua".text = "return 1"; }; # Another form of user files files = { "lua/simple-plugin-2/init.lua" = { extraConfigLua = "return 1"; }; }; extraConfigLuaPost = '' for _, file in ipairs({"lua/simple-plugin-1/init.lua", "lua/simple-plugin-2/init.lua"}) do local paths_found = vim.api.nvim_get_runtime_file(file, true) local num_found = #paths_found -- Both plugin and user version are available assert(num_found == 2, "expected exactly 2 versions of '" .. file .. "', got " .. num_found) -- First found file is from build.extraFiles assert( paths_found[1]:find("${lib.getName config.build.extraFiles}", 1, true), "expected first found '" .. file .. "' to be from build.extraFiles, got " .. paths_found[1] ) end ''; assertions = [ { assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" == 1; message = "More than one start plugin is defined in packpathDirs"; } ]; }; # Test that standalonePlugins option works standalone-plugins = { config, ... }: { performance.combinePlugins = { enable = true; standalonePlugins = [ # By plugin name "simple-plugin-1" # By package itself. Its dependency, simplePlugin2, not in this list, so will be combined pluginWithDeps2 # Dependency of other plugin "simple-plugin-3" ]; }; extraPlugins = [ simplePlugin1 pluginWithDeps2 pluginWithDeps3 pluginWithExtraFiles ]; extraConfigLuaPost = '' local tests = { {"simple-plugin-1", true}, {"plugin-with-deps-2", true}, {"simple-plugin-2", false}, {"plugin-with-deps-3", false}, {"simple-plugin-3", true}, {"plugin-with-extra-files", false}, } for _, test in ipairs(tests) do local name = test[1] local expected_standalone = test[2] -- Plugin is loadable require(test[1]) local paths = vim.api.nvim_get_runtime_file("lua/" .. name, true) -- Plugins shouldn't be duplicated assert(#paths == 1, "expected exactly 1 copy of '" .. name .. "' in runtime, got ", #paths) -- Test if plugin is standalone. This matches directory name before '/lua/'. local is_standalone = paths[1]:match("^(.+)/lua/"):find(name, 1, true) ~= nil local expected_text = expected_standalone and "standalone" or "combined" assert( is_standalone == expected_standalone, "expected '" .. name .. "' to be " .. expected_text .. ", found path: " .. paths[1] ) end ''; assertions = [ { # plugin-pack, simplePlugin1, pluginWithDeps2, simplePlugin3 assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" == 4; message = "Wrong number of plugins in packpathDirs"; } ]; }; # Test if plenary.filetype is working plenary-nvim = { performance.combinePlugins.enable = true; extraPlugins = [ pkgs.vimPlugins.plenary-nvim ]; extraConfigLuaPost = '' -- Plenary filetype detection is usable assert(require("plenary.filetype").detect(".bashrc") == "sh", "plenary.filetype is not working") ''; }; }