diff --git a/.busted b/.busted new file mode 100644 index 0000000..6c370f4 --- /dev/null +++ b/.busted @@ -0,0 +1,12 @@ +return { + _all = { + coverage = false, + lpath = "lua/?.lua;lua/?/init.lua", + }, + default = { + verbose = true + }, + tests = { + verbose = true + }, +} diff --git a/.editorconfig b/.editorconfig index b328442..d88c643 100644 --- a/.editorconfig +++ b/.editorconfig @@ -69,3 +69,7 @@ insert_final_newline = unset trim_trailing_whitespace = unset indent_style = unset indent_size = unset + +[spec/*.lua] +indent_size = unset +indent_style = unset diff --git a/README.md b/README.md index db39d11..f3b7ccd 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,11 @@ Where `rocks.lazy.KeysSpec` is a table with the following fields: > [!NOTE] > -> If unspecified, the default `mode` is `n`. +> - If unspecified, the default `mode` is `n`. +> - The `lhs` and `rhs` fields differ +> from [the `lz.n.PluginSpec`](https://github.com/nvim-neorocks/lz.n?tab=readme-ov-file#plugin-spec)[^2]. + +[^2]: This is because toml tables are stricter than Lua tables. Examples: @@ -216,47 +220,49 @@ colorscheme = [ ### Lua configuration If you prefer using Lua for configuration, -you can use the `vim.g.rocks_nvim.lz_spec` field, which can be - -- A [`lz.n.PluginSpec`](https://github.com/nvim-neorocks/lz.n?tab=readme-ov-file#plugin-spec). -- A Lua module name (`string`) that contains your plugin spec. - See [the `lz.n` documentation](https://github.com/nvim-neorocks/lz.n?tab=readme-ov-file#structuring-your-plugins). - -> [!IMPORTANT] -> -> If you use a module name to import your plugin specs -> and you also use `rocks-config.nvim`, -> the `lz_spec` module name **must not clash** with the `rocks-config` `plugins_dir`. +you can add a `import` option to your `rocks.toml`: -Examples: +```toml +[rocks_lazy] +import = "lazy_specs/" +``` -```lua -vim.g.rocks_nvim = { - -- ... - lz_spec = { - { - "crates.nvim", - -- lazy-load when opening a toml file - ft = "toml", - }, - { - "sweetie.nvim", - -- lazy-load when setting the `sweetie` colorscheme - colorscheme = "sweetie", - }, - }, -} +This is a subdirectory (relative to `nvim/lua`) +to search for plugin specs. +In this example, you can add a `lua/lazy_specs/` directory +to your `nvim` config, with a lua script for each plugin. + +```sh +── nvim + ├── lua + │ └── lazy_specs # Your plugin specs go here. + │ └── init.lua # Optional top-level module returning a list of specs + │ └── neorg.lua # Single spec + │ └── sweetie.lua + ├── init.lua ``` Or -```lua -vim.g.rocks_nvim = { - -- ... - lz_spec = "lazy_specs", -- Spec modules in nvim/lua/lazy_specs/.lua -} + +```sh +── nvim + ├── lua + │ └── lazy_specs.lua # Optional top-level module returning a list of specs + ├── init.lua ``` +- See [the `lz.n` documentation](https://github.com/nvim-neorocks/lz.n?tab=readme-ov-file#structuring-your-plugins). +- The Lua plugins specs must be configured according to + the [`lz.n.PluginSpec`](https://github.com/nvim-neorocks/lz.n?tab=readme-ov-file#plugin-spec). + +> [!IMPORTANT] +> +> If you use a module to import your plugin specs +> and you also use `rocks-config.nvim`, +> the `rocks-lazy` `import` module name +> **must not clash** with the `rocks-config` `plugins_dir`. + > [!TIP] > > You can use both `rocks.toml` entries and a Lua config to configure diff --git a/flake.nix b/flake.nix index 71094c4..dd369da 100644 --- a/flake.nix +++ b/flake.nix @@ -75,10 +75,10 @@ type-check-nightly = pre-commit-hooks.lib.${system}.run { src = self; hooks = { - lua-ls.enable = true; - }; - settings = { - lua-ls.config = luarc; + lua-ls = { + enable = true; + settings.configuration = luarc; + }; }; }; @@ -92,7 +92,7 @@ }; }; - devShell = pkgs.mkShell { + devShell = pkgs.nvim-nightly-tests.overrideAttrs (oa: { name = "rocks-lazy.nvim devShell"; shellHook = '' ${pre-commit-check.shellHook} @@ -100,11 +100,12 @@ ''; buildInputs = self.checks.${system}.pre-commit-check.enabledPackages + ++ oa.buildInputs ++ (with pkgs; [ busted-nightly lua-language-server ]); - }; + }); in { packages = rec { default = neovim-with-rocks; @@ -122,6 +123,11 @@ pre-commit-check type-check-nightly ; + inherit + (pkgs) + nvim-stable-tests + nvim-nightly-tests + ; }; }; flake = { diff --git a/lua/rocks-lazy/internal.lua b/lua/rocks-lazy/internal.lua new file mode 100644 index 0000000..3e460b2 --- /dev/null +++ b/lua/rocks-lazy/internal.lua @@ -0,0 +1,89 @@ +---@mod rocks-lazy.internal lazy-loading module for rocks.nvim + +local rocks_lazy = {} + +function rocks_lazy.load() + local api = require("rocks.api") + local lz_n = require("lz.n") + + local user_rocks = api.get_user_rocks() + + local has_rocks_config, rocks_config = pcall(require, "rocks-config") + ---@type fun(name: string) | nil + local config_hook = has_rocks_config and type(rocks_config.configure) == "function" and rocks_config.configure + or nil + + --- HACK: For some reason, if a RockSpec contains a list + --- (e.g. colorscheme = [ .. ]) then vim.deepcopy errors + --- if we don't fix it. This is possibly a toml_edit.lua bug! + --- + ---@generic T + ---@param value T | T[] + ---@return T | T[] + local function clone_toml_list(value) + if vim.islist(value) then + ---@cast value string[] + return vim.list_extend({}, value) + end + return value + end + + ---@param keys string | string[] | rocks.lazy.KeysSpec[] | nil + ---@return string | string[] | lz.n.KeysSpec[] | nil + local function to_lz_n_keys_spec(keys) + if not keys or type(keys) == "string" then + return keys + end + return vim.iter(keys) + :map(function(value) + if type(value) ~= "table" then + return value + end + local result = vim.deepcopy(value) + result[1] = value.lhs + result.lhs = nil + result[2] = value.rhs + result.rhs = nil + return result + end) + :totable() + end + + ---@type lz.n.PluginSpec[] + local specs = vim.iter(user_rocks) + :filter(function(_, rock) + ---@cast rock rocks.lazy.RockSpec + return rock.opt == true + or rock.event ~= nil + or rock.cmd ~= nil + or rock.keys ~= nil + or rock.ft ~= nil + or rock.colorscheme ~= nil + end) + :map(function(_, rock) + ---@cast rock rocks.lazy.RockSpec + ---@type lz.n.PluginSpec + return { + rock.name, + before = config_hook, + lazy = rock.opt, + event = clone_toml_list(rock.event), + cmd = clone_toml_list(rock.cmd), + keys = to_lz_n_keys_spec(clone_toml_list(rock.keys)), + ft = clone_toml_list(rock.ft), + colorscheme = clone_toml_list(rock.colorscheme), + } + end) + :totable() + + local rocks_toml = api.get_rocks_toml() + local config = rocks_toml.rocks_lazy or {} + local lz_specs = config.import + if type(lz_specs) == "string" then + table.insert(specs, { import = lz_specs }) + end + + lz_n.load(specs) +end + +return rocks_lazy diff --git a/lua/rocks-lazy/meta.lua b/lua/rocks-lazy/meta.lua index 076694c..023a177 100644 --- a/lua/rocks-lazy/meta.lua +++ b/lua/rocks-lazy/meta.lua @@ -30,6 +30,3 @@ error("Cannot require a meta module") ---@field nowait? boolean ---@field ft? string|string[] ---@field mode? string|string[] - ----@type lz.n.Spec -vim.g.rocks_nvim.lz_spec = vim.g.rocks_nvim.lz_spec diff --git a/nix/overlay.nix b/nix/overlay.nix index 3001d6a..3f8275c 100644 --- a/nix/overlay.nix +++ b/nix/overlay.nix @@ -109,6 +109,41 @@ + " " + ''--set NVIM_APPNAME "nvimrocks"''; }); + + nvim-nightly = final.neovim-nightly; + + mkNeorocksTest = { + name, + nvim ? final.neovim-unwrapped, + extraPkgs ? [], + }: let + nvim-wrapped = final.pkgs.wrapNeovim nvim { + configure = { + packages.myVimPackage = { + start = []; + }; + }; + }; + in + final.pkgs.neorocksTest { + inherit name; + pname = "rocks-lazy.nvim"; + src = self; + neovim = nvim-wrapped; + luaPackages = ps: [ + luajitPackages.lz-n + luajitPackages.rocks-nvim + ]; + + preCheck = '' + export HOME=$(realpath .) + ''; + + buildPhase = '' + mkdir -p $out + cp -r spec $out + ''; + }; in { inherit lua5_1 @@ -129,4 +164,10 @@ in { }; rocks-lazy-nvim = lua51Packages.rocks-lazy-nvim; + + nvim-stable-tests = mkNeorocksTest {name = "neovim-stable-tests";}; + nvim-nightly-tests = mkNeorocksTest { + name = "neovim-nightly-tests"; + nvim = nvim-nightly; + }; } diff --git a/plugin/rocks-lazy.lua b/plugin/rocks-lazy.lua index 13934e3..fe86458 100644 --- a/plugin/rocks-lazy.lua +++ b/plugin/rocks-lazy.lua @@ -4,88 +4,4 @@ end vim.g.rocks_lazy_nvim_loaded = true -local api = require("rocks.api") -local lz_n = require("lz.n") - -local user_rocks = api.get_user_rocks() - -local has_rocks_config, rocks_config = pcall(require, "rocks-config") ----@type fun(name: string) | nil -local config_hook = has_rocks_config and type(rocks_config.configure) == "function" and rocks_config.configure or nil - ---- HACK: For some reason, if a RockSpec contains a list ---- (e.g. colorscheme = [ .. ]) then vim.deepcopy errors ---- if we don't fix it. This is possibly a toml_edit.lua bug! ---- ----@generic T ----@param value T | T[] ----@return T | T[] -local function clone_toml_list(value) - if vim.islist(value) then - ---@cast value string[] - return vim.list_extend({}, value) - end - return value -end - ----@param keys string | string[] | rocks.lazy.KeysSpec[] | nil ----@return string | string[] | lz.n.KeysSpec[] | nil -local function to_lz_n_keys_spec(keys) - if not keys or type(keys) == "string" then - return keys - end - return vim.iter(keys) - :map(function(value) - if type(value) ~= "table" then - return value - end - local result = vim.deepcopy(value) - result[1] = value.lhs - result.lhs = nil - result[2] = value.rhs - result.rhs = nil - return result - end) - :totable() -end - ----@type lz.n.PluginSpec[] -local specs = vim.iter(user_rocks) - :filter(function(_, rock) - ---@cast rock rocks.lazy.RockSpec - return rock.opt == true - or rock.event ~= nil - or rock.cmd ~= nil - or rock.keys ~= nil - or rock.ft ~= nil - or rock.colorscheme ~= nil - end) - :map(function(_, rock) - ---@cast rock rocks.lazy.RockSpec - ---@type lz.n.PluginSpec - return { - rock.name, - before = config_hook, - lazy = rock.opt, - event = clone_toml_list(rock.event), - cmd = clone_toml_list(rock.cmd), - keys = to_lz_n_keys_spec(clone_toml_list(rock.keys)), - ft = clone_toml_list(rock.ft), - colorscheme = clone_toml_list(rock.colorscheme), - } - end) - :totable() - -local lz_spec = vim.g.rocks_nvim and vim.g.rocks_nvim.lz_spec or {} -if type(lz_spec) == "string" then - table.insert(specs, { import = lz_spec }) -else - local is_single_plugin_spec = type(lz_spec[1]) == "string" - if is_single_plugin_spec then - table.insert(specs, lz_spec) - elseif vim.islist(lz_spec) then - vim.list_extend(specs, lz_spec) - end -end - -lz_n.load(specs) +require("rocks-lazy.internal").load() diff --git a/spec/rocks_lazy_spec.lua b/spec/rocks_lazy_spec.lua new file mode 100644 index 0000000..18306d4 --- /dev/null +++ b/spec/rocks_lazy_spec.lua @@ -0,0 +1,54 @@ +vim.g.lz_n = { + load = function() end, +} + +local tempdir = vim.fn.tempname() +local config_path = vim.fs.joinpath(tempdir, "rocks.toml") + +vim.system({ "rm", "-r", tempdir }):wait() +vim.system({ "mkdir", "-p", tempdir .. "/lua/lazy_specs" }):wait() + +local rocks_lazy = require("rocks-lazy.internal") +local loader = require("lz.n.loader") +local spy = require("luassert.spy") + +describe("rocks-lazy.nvim", function() + it("toml + lua plugin dir", function() + vim.g.lz_n_did_load = false + vim.g.rocks_nvim = { + rocks_path = tempdir, + config_path = config_path, + } + local config_content = [[ +[plugins."dial.nvim"] +version = "0.4.0" +opt = true +keys = [ "", { lhs = "", mode = "n" }] + +[rocks_lazy] +import = "lazy_specs/" +]] + local config = require("rocks.config.internal") + local fh = assert(io.open(config.config_path, "w"), "Could not open rocks.toml for writing") + fh:write(config_content) + fh:close() + local plugin_config_content = [[ +return { + "telescope.nvim", + cmd = "Telescope", +} +]] + local spec_file = vim.fs.joinpath(tempdir, "lua", "lazy_specs", "telescope.lua") + fh = assert(io.open(spec_file, "w"), "Could not open config file for writing") + fh:write(plugin_config_content) + fh:close() + vim.opt.runtimepath:append(tempdir) + local spy_load = spy.on(loader, "_load") + rocks_lazy.load() + vim.cmd.Telescope() + assert.spy(spy_load).called(1) + local feed = vim.api.nvim_replace_termcodes("", true, true, true) + vim.api.nvim_feedkeys(feed, "ix", false) + assert.spy(spy_load).called(2) + end) +end)