From 3841102f120a72a00aaa504705f50ce4eefc5654 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Wed, 3 Jul 2024 00:26:07 +0200 Subject: [PATCH 01/10] feat: support extending the default luarocks config with a table --- .gitignore | 2 +- doc/rocks.txt | 35 +++++++++++++++++++++------- lua/rocks/config/init.lua | 44 ++++++++++++++++++++++++++++------- lua/rocks/config/internal.lua | 37 +++++++++++++++++------------ spec/luarocks_config_spec.lua | 34 +++++++++++++++++++++++++++ 5 files changed, 118 insertions(+), 34 deletions(-) create mode 100644 spec/luarocks_config_spec.lua diff --git a/.gitignore b/.gitignore index 7c39bd03..9488cdd9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -result +result* https___* .pre-commit-config.yaml .direnv diff --git a/doc/rocks.txt b/doc/rocks.txt index 8647389e..507097b7 100644 --- a/doc/rocks.txt +++ b/doc/rocks.txt @@ -97,15 +97,32 @@ rocks.nvim configuration *rocks-config* RocksOpts *RocksOpts* Fields: ~ - {rocks_path?} (string) Local path in your filesystem to install rocks. Defaults to a `rocks` directory in `vim.fn.stdpath("data")`. - {config_path?} (string) Rocks declaration file path. Defaults to `rocks.toml` in `vim.fn.stdpath("config")`. - {luarocks_binary?} (string) Luarocks binary path. Defaults to `{rocks_path}/bin/luarocks`. - {lazy?} (boolean) Whether to query luarocks.org lazily. Defaults to `false`. Setting this to `true` may improve startup time, but features like auto-completion will lag initially. - {dynamic_rtp?} (boolean) Whether to automatically add freshly installed plugins to the 'runtimepath'. Defaults to `true` for the best default experience. - {generate_help_pages?} (boolean) Whether to re-generate plugins help pages after installation/upgrade. Defaults to `true`. - {reinstall_dev_rocks_on_update?} (boolean) Whether to reinstall 'dev' rocks on update (Default: `true`, as rocks.nvim cannot determine if 'dev' rocks are up to date). - {enable_luarocks_loader?} (boolean) Whether to use the luarocks loader to support multiple dependencies (Default: `true`). - {luarocks_config?} (string) Path to the luarocks config. If not set, rocks.nvim will create one in `rocks_path`. Warning: You should include the settings in the default luarocks-config.lua before overriding this. + {rocks_path?} (string) + Local path in your file system to install rocks + (Default: a `rocks` directory in `vim.fn.stdpath("data")`). + {config_path?} (string) + Rocks declaration file path (Default: `rocks.toml`) in `vim.fn.stdpath("config")`. + {luarocks_binary?} (string) + Luarocks binary path (Default: `{rocks_path}/bin/luarocks`). + {lazy?} (boolean) + Whether to query luarocks.org lazily (Default: `false`). + Setting this to `true` may improve startup time, + but features like auto-completion will lag initially. + {dynamic_rtp?} (boolean) + Whether to automatically add freshly installed plugins to the 'runtimepath'. + (Default: `true` for the best default experience). + {generate_help_pages?} (boolean) + Whether to re-generate plugins help pages after installation/upgrade. (Default: `true`). + {reinstall_dev_rocks_on_update?} (boolean) + Whether to reinstall 'dev' rocks on update + (Default: `true`, as rocks.nvim cannot determine if 'dev' rocks are up to date). + {enable_luarocks_loader?} (boolean) + Whether to use the luarocks loader to support multiple dependencies (Default: `true`). + {luarocks_config?} (string|table) + Path to the luarocks config file or table of extra luarocks config options. + If a table or not set, rocks.nvim will create a default luarocks config in `rocks_path` + and merge it with this table. + Warning: this is a file path, You should include the settings in the default luarocks-config.lua before overriding this. ============================================================================== diff --git a/lua/rocks/config/init.lua b/lua/rocks/config/init.lua index 09050c3b..7d9764b2 100644 --- a/lua/rocks/config/init.lua +++ b/lua/rocks/config/init.lua @@ -16,15 +16,41 @@ local config = {} ---@tag vim.g.rocks_nvim ---@tag g:rocks_nvim ---@class RocksOpts ----@field rocks_path? string Local path in your filesystem to install rocks. Defaults to a `rocks` directory in `vim.fn.stdpath("data")`. ----@field config_path? string Rocks declaration file path. Defaults to `rocks.toml` in `vim.fn.stdpath("config")`. ----@field luarocks_binary? string Luarocks binary path. Defaults to `{rocks_path}/bin/luarocks`. ----@field lazy? boolean Whether to query luarocks.org lazily. Defaults to `false`. Setting this to `true` may improve startup time, but features like auto-completion will lag initially. ----@field dynamic_rtp? boolean Whether to automatically add freshly installed plugins to the 'runtimepath'. Defaults to `true` for the best default experience. ----@field generate_help_pages? boolean Whether to re-generate plugins help pages after installation/upgrade. Defaults to `true`. ----@field reinstall_dev_rocks_on_update? boolean Whether to reinstall 'dev' rocks on update (Default: `true`, as rocks.nvim cannot determine if 'dev' rocks are up to date). ----@field enable_luarocks_loader? boolean Whether to use the luarocks loader to support multiple dependencies (Default: `true`). ----@field luarocks_config? string Path to the luarocks config. If not set, rocks.nvim will create one in `rocks_path`. Warning: You should include the settings in the default luarocks-config.lua before overriding this. +--- +--- Local path in your file system to install rocks +--- (Default: a `rocks` directory in `vim.fn.stdpath("data")`). +---@field rocks_path? string +--- +--- Rocks declaration file path (Default: `rocks.toml`) in `vim.fn.stdpath("config")`. +---@field config_path? string +--- +--- Luarocks binary path (Default: `{rocks_path}/bin/luarocks`). +---@field luarocks_binary? string +--- +--- Whether to query luarocks.org lazily (Default: `false`). +--- Setting this to `true` may improve startup time, +--- but features like auto-completion will lag initially. +---@field lazy? boolean +--- +--- Whether to automatically add freshly installed plugins to the 'runtimepath'. +--- (Default: `true` for the best default experience). +---@field dynamic_rtp? boolean +--- +--- Whether to re-generate plugins help pages after installation/upgrade. (Default: `true`). +---@field generate_help_pages? boolean +--- +--- Whether to reinstall 'dev' rocks on update +--- (Default: `true`, as rocks.nvim cannot determine if 'dev' rocks are up to date). +---@field reinstall_dev_rocks_on_update? boolean +--- +--- Whether to use the luarocks loader to support multiple dependencies (Default: `true`). +---@field enable_luarocks_loader? boolean +--- +--- Path to the luarocks config file or table of extra luarocks config options. +--- If a table or not set, rocks.nvim will create a default luarocks config in `rocks_path` +--- and merge it with this table. +--- Warning: this is a file path, You should include the settings in the default luarocks-config.lua before overriding this. +---@field luarocks_config? string | table ---@type RocksOpts | fun():RocksOpts vim.g.rocks_nvim = vim.g.rocks_nvim diff --git a/lua/rocks/config/internal.lua b/lua/rocks/config/internal.lua index 0dc0f994..a33a7108 100644 --- a/lua/rocks/config/internal.lua +++ b/lua/rocks/config/internal.lua @@ -153,7 +153,7 @@ if #config.debug_info.unrecognized_configs > 0 then ) end -if opts.luarocks_config then +if type(opts.luarocks_config) == "string" then -- luarocks_config override if vim.uv.fs_stat(opts.luarocks_config) then ---@diagnostic disable-next-line: inject-field @@ -163,21 +163,28 @@ if opts.luarocks_config then opts.luarocks_config = nil end end -if not opts.luarocks_config then +if not opts.luarocks_config or type(opts.luarocks_config) == "table" then local luarocks_config_path = vim.fs.joinpath(config.rocks_path, "luarocks-config.lua") - fs.write_file( - luarocks_config_path, - "w+", - ([==[ -lua_version = "5.1" -rocks_trees = { - { - name = "rocks.nvim", - root = "%s", - }, -} -]==]):format(config.rocks_path) - ) + local default_luarocks_config = { + lua_version = "5.1", + rocks_trees = { + { + name = "rocks.nvim", + root = config.rocks_path, + }, + }, + } + local luarocks_config = vim.tbl_deep_extend("force", default_luarocks_config, opts.luarocks_config or {}) + + ---@type string + local config_str = vim.iter(luarocks_config):fold("", function(acc, k, v) + return ([[ +%s +%s = %s +]]):format(acc, k, vim.inspect(v)) + end) + + fs.write_file(luarocks_config_path, "w+", config_str) ---@diagnostic disable-next-line: inject-field config.luarocks_config = ("%s"):format(luarocks_config_path) diff --git a/spec/luarocks_config_spec.lua b/spec/luarocks_config_spec.lua new file mode 100644 index 00000000..8e2d22c9 --- /dev/null +++ b/spec/luarocks_config_spec.lua @@ -0,0 +1,34 @@ +local nio = require("nio") + +vim.env.PLENARY_TEST_TIMEOUT = 60000 + +describe("luarocks config", function() + nio.tests.it("extra luarocks_config", function() + local tempdir = vim.fn.tempname() + + local external_deps_dirs = { + "/some/path", + } + + vim.g.rocks_nvim = { + luarocks_binary = "luarocks", + rocks_path = tempdir, + luarocks_config = { + external_deps_dirs = external_deps_dirs, + }, + } + + local config = require("rocks.config.internal") + nio.sleep(2000) + assert.is_not_nil(vim.uv.fs_stat(config.luarocks_config)) + local luarocks_config = {} + loadfile(config.luarocks_config, "t", luarocks_config)() + assert.same({ + lua_version = "5.1", + external_deps_dirs = external_deps_dirs, + rocks_trees = { + { name = "rocks.nvim", root = tempdir }, + }, + }, luarocks_config) + end) +end) From 1cb494492b1e96d9380f3075f56ad90dd2f678c3 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Wed, 3 Jul 2024 23:58:32 +0200 Subject: [PATCH 02/10] perf: don't write config at startup + more asynchronicity --- lua/rocks/config/internal.lua | 51 ++++++++++++++++----------- lua/rocks/loader.lua | 60 ++++++++++++++++++++------------ lua/rocks/luarocks.lua | 6 ++-- lua/rocks/operations/helpers.lua | 4 +-- plugin/rocks.lua | 2 +- spec/loader_spec.lua | 4 ++- spec/luarocks_config_spec.lua | 6 ++-- 7 files changed, 81 insertions(+), 52 deletions(-) diff --git a/lua/rocks/config/internal.lua b/lua/rocks/config/internal.lua index a33a7108..aa71ae16 100644 --- a/lua/rocks/config/internal.lua +++ b/lua/rocks/config/internal.lua @@ -117,8 +117,8 @@ local default_config = { vim.tbl_deep_extend("force", vim.empty_dict(), rocks_toml.rocks or {}, rocks_toml.plugins or {}) return config.apply_rock_spec_modifiers(user_rocks) end, - ---@type string - luarocks_config = nil, + ---@type fun():string + luarocks_config_path = nil, } ---@type RocksOpts @@ -156,38 +156,49 @@ end if type(opts.luarocks_config) == "string" then -- luarocks_config override if vim.uv.fs_stat(opts.luarocks_config) then + local luarocks_config_path = ("%s"):format(opts.luarocks_config) ---@diagnostic disable-next-line: inject-field - config.luarocks_config = ("%s"):format(opts.luarocks_config) + config.luarocks_config_path = function() + return luarocks_config_path + end else vim.notify("rocks.nvim: luarocks_config does not exist!", vim.log.levels.ERROR) opts.luarocks_config = nil end end if not opts.luarocks_config or type(opts.luarocks_config) == "table" then - local luarocks_config_path = vim.fs.joinpath(config.rocks_path, "luarocks-config.lua") - local default_luarocks_config = { - lua_version = "5.1", - rocks_trees = { - { - name = "rocks.nvim", - root = config.rocks_path, + local nio = require("nio") + local semaphore = nio.control.semaphore(1) + ---@diagnostic disable-next-line: inject-field + config.luarocks_config_path = nio.create(function() + semaphore.acquire() + local luarocks_config_path = vim.fs.joinpath(config.rocks_path, "luarocks-config.lua") + local default_luarocks_config = { + lua_version = "5.1", + rocks_trees = { + { + name = "rocks.nvim", + root = config.rocks_path, + }, }, - }, - } - local luarocks_config = vim.tbl_deep_extend("force", default_luarocks_config, opts.luarocks_config or {}) + } + local luarocks_config = vim.tbl_deep_extend("force", default_luarocks_config, opts.luarocks_config or {}) - ---@type string - local config_str = vim.iter(luarocks_config):fold("", function(acc, k, v) - return ([[ + ---@type string + local config_str = vim.iter(luarocks_config):fold("", function(acc, k, v) + return ([[ %s %s = %s ]]):format(acc, k, vim.inspect(v)) - end) + end) - fs.write_file(luarocks_config_path, "w+", config_str) + fs.write_file_await(luarocks_config_path, "w+", config_str) - ---@diagnostic disable-next-line: inject-field - config.luarocks_config = ("%s"):format(luarocks_config_path) + semaphore.release() + + ---@diagnostic disable-next-line: inject-field + return ("%s"):format(luarocks_config_path) + end) end return config diff --git a/lua/rocks/loader.lua b/lua/rocks/loader.lua index 0569a3f5..eaa313d6 100644 --- a/lua/rocks/loader.lua +++ b/lua/rocks/loader.lua @@ -2,43 +2,59 @@ local loader = {} local log = require("rocks.log") local config = require("rocks.config.internal") +local nio = require("nio") ---@return string | nil local function get_luarocks_lua_dir_from_luarocks() - local ok, so = pcall(vim.system, { config.luarocks_binary, "--lua-version=5.1", "which", "luarocks.loader" }) + local future = nio.control.future() + local ok = pcall( + vim.system, + { config.luarocks_binary, "--lua-version=5.1", "which", "luarocks.loader" }, + nil, + function(sc) + future.set(sc) + end + ) if not ok then log.error(("Could not invoke luarocks at %s"):format(config.luarocks_binary)) return end - local sc = so:wait() + ---@type vim.SystemCompleted + local sc = future.wait() local result = sc.stdout and sc.stdout:match(vim.fs.joinpath("(%S+)", "5.1", "luarocks", "loader.lua")) return result end ----@return boolean -function loader.enable() +---@type async fun():boolean +loader.enable = nio.create(function() log.trace("Enabling luarocks loader") + local luarocks_config_path = config.luarocks_config_path() local luarocks_lua_dir = config.luarocks_binary == config.default_luarocks_binary and vim.fs.joinpath(config.rocks_path, "share", "lua") or get_luarocks_lua_dir_from_luarocks() - if luarocks_lua_dir then - package.path = package.path - .. ";" - .. table.concat({ - vim.fs.joinpath(luarocks_lua_dir, "5.1", "?.lua"), - vim.fs.joinpath(luarocks_lua_dir, "5.1", "init.lua"), - }, ";") - vim.env.LUAROCKS_CONFIG = config.luarocks_config - local ok, err = pcall(require, "luarocks.loader") - if ok then - return true + local future = nio.control.future() + vim.schedule(function() + if luarocks_lua_dir then + package.path = package.path + .. ";" + .. table.concat({ + vim.fs.joinpath(luarocks_lua_dir, "5.1", "?.lua"), + vim.fs.joinpath(luarocks_lua_dir, "5.1", "init.lua"), + }, ";") + vim.env.LUAROCKS_CONFIG = luarocks_config_path + local ok, err = pcall(require, "luarocks.loader") + if ok then + future.set(true) + return + end + log.error(err or "Unknown error initializing luarocks loader") + vim.notify("Failed to initialize luarocks loader: " .. err, vim.log.levels.WARN, { + title = "rocks.nvim", + }) end - log.error(err or "Unknown error initializing luarocks loader") - vim.notify("Failed to initialize luarocks loader: " .. err, vim.log.levels.WARN, { - title = "rocks.nvim", - }) - end - return false -end + future.set(false) + end) + return future.wait() +end) return loader diff --git a/lua/rocks/luarocks.lua b/lua/rocks/luarocks.lua index ae5e258a..08b757e8 100644 --- a/lua/rocks/luarocks.lua +++ b/lua/rocks/luarocks.lua @@ -49,7 +49,7 @@ end --- asynchronously. Receives SystemCompleted object, see return of SystemObj:wait(). ---@param opts? LuarocksCliOpts ---@see vim.system -luarocks.cli = function(args, on_exit, opts) +luarocks.cli = nio.create(function(args, on_exit, opts) opts = opts or {} ---@cast opts LuarocksCliOpts opts.synchronized = opts.synchronized ~= nil and opts.synchronized or false @@ -72,7 +72,7 @@ luarocks.cli = function(args, on_exit, opts) semaphore.acquire() end opts.env = vim.tbl_deep_extend("force", opts.env or {}, { - LUAROCKS_CONFIG = config.luarocks_config, + LUAROCKS_CONFIG = config.luarocks_config_path(), TREE_SITTER_LANGUAGE_VERSION = tostring(vim.treesitter.language_version), LUA_PATH = ('"%s"'):format(package.path), LUA_CPATH = ('"%s"'):format(package.cpath), @@ -97,7 +97,7 @@ luarocks.cli = function(args, on_exit, opts) } on_exit_wrapped(sc) end -end +end, 3) ---@class LuarocksSearchOpts ---@field dev? boolean Include dev manifest? Default: false diff --git a/lua/rocks/operations/helpers.lua b/lua/rocks/operations/helpers.lua index 9aee3cfa..00d88003 100644 --- a/lua/rocks/operations/helpers.lua +++ b/lua/rocks/operations/helpers.lua @@ -30,7 +30,7 @@ local helpers = {} ---@param rock_spec RockSpec ---@param progress_handle? ProgressHandle ---@return nio.control.Future -helpers.install = function(rock_spec, progress_handle) +helpers.install = nio.create(function(rock_spec, progress_handle) cache.invalidate_removable_rocks() local name = rock_spec.name:lower() local version = rock_spec.version @@ -103,7 +103,7 @@ helpers.install = function(rock_spec, progress_handle) servers = servers, }) return future -end +end, 2) ---Removes a rock ---@param name string diff --git a/plugin/rocks.lua b/plugin/rocks.lua index cbc20bbb..a6378505 100644 --- a/plugin/rocks.lua +++ b/plugin/rocks.lua @@ -28,7 +28,7 @@ log.debug("Using luarocks config " .. config.luarocks_config) -- Initialize the luarocks loader if config.enable_luarocks_loader then - require("rocks.loader").enable() + nio.run(require("rocks.loader").enable) end -- Set up the Rocks user command diff --git a/spec/loader_spec.lua b/spec/loader_spec.lua index 296b865a..dcd1f1ff 100644 --- a/spec/loader_spec.lua +++ b/spec/loader_spec.lua @@ -1,10 +1,12 @@ +vim.env.PLENARY_TEST_TIMEOUT = 60000 vim.g.rocks_nvim = { luarocks_binary = "luarocks", } local loader = require("rocks.loader") +local nio = require("nio") describe("rocks.loader", function() - it("Can enable luarocks.loader", function() + nio.tests.it("Can enable luarocks.loader", function() assert.True(loader.enable()) end) end) diff --git a/spec/luarocks_config_spec.lua b/spec/luarocks_config_spec.lua index 8e2d22c9..fd60cf94 100644 --- a/spec/luarocks_config_spec.lua +++ b/spec/luarocks_config_spec.lua @@ -19,10 +19,10 @@ describe("luarocks config", function() } local config = require("rocks.config.internal") - nio.sleep(2000) - assert.is_not_nil(vim.uv.fs_stat(config.luarocks_config)) + local luarocks_config_path = config.luarocks_config_path() + assert.is_not_nil(vim.uv.fs_stat(luarocks_config_path)) local luarocks_config = {} - loadfile(config.luarocks_config, "t", luarocks_config)() + loadfile(luarocks_config_path, "t", luarocks_config)() assert.same({ lua_version = "5.1", external_deps_dirs = external_deps_dirs, From 27563a665e04f55fbcc45fe8a55015a0f2507e2f Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Thu, 4 Jul 2024 00:35:56 +0200 Subject: [PATCH 03/10] chore: deprecate `g:rocks_nvim.luarocks_config` `string` option --- .github/workflows/integration.yml | 4 ++-- doc/rocks.txt | 34 +++++++++++++++---------------- flake.nix | 1 - lua/rocks/config/init.lua | 8 +++----- lua/rocks/config/internal.lua | 21 +++++++++++++------ lua/rocks/loader.lua | 2 +- nix/test-overlay.nix | 1 - plugin/rocks.lua | 5 +++-- 8 files changed, 40 insertions(+), 36 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 15070169..ab58ac1d 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -35,7 +35,7 @@ jobs: uses: rhysd/action-setup-vim@v1 with: neovim: true - version: nightly + version: stable - name: Install rocks.nvim run: | mkdir rocks @@ -54,7 +54,7 @@ jobs: cat rocks/rocks.log echo "vim.cmd.colorscheme('sweetie')" >> .github/resources/init-integration.lua echo "vim.cmd.e('success')" >> .github/resources/init-integration.lua - nvim -u .github/resources/init-integration.lua -c +wq + nvim -u .github/resources/init-integration.lua -c 'set noswapfile' +wq || true if [ ! -f success ]; then echo "Integration test failed!" exit 1 diff --git a/doc/rocks.txt b/doc/rocks.txt index 507097b7..c7e5e352 100644 --- a/doc/rocks.txt +++ b/doc/rocks.txt @@ -98,31 +98,29 @@ RocksOpts *RocksOpts* Fields: ~ {rocks_path?} (string) - Local path in your file system to install rocks - (Default: a `rocks` directory in `vim.fn.stdpath("data")`). + Local path in your file system to install rocks + (Default: a `rocks` directory in `vim.fn.stdpath("data")`). {config_path?} (string) - Rocks declaration file path (Default: `rocks.toml`) in `vim.fn.stdpath("config")`. + Rocks declaration file path (Default: `rocks.toml`) in `vim.fn.stdpath("config")`. {luarocks_binary?} (string) - Luarocks binary path (Default: `{rocks_path}/bin/luarocks`). + Luarocks binary path (Default: `{rocks_path}/bin/luarocks`). {lazy?} (boolean) - Whether to query luarocks.org lazily (Default: `false`). - Setting this to `true` may improve startup time, - but features like auto-completion will lag initially. + Whether to query luarocks.org lazily (Default: `false`). + Setting this to `true` may improve startup time, + but features like auto-completion will lag initially. {dynamic_rtp?} (boolean) - Whether to automatically add freshly installed plugins to the 'runtimepath'. - (Default: `true` for the best default experience). + Whether to automatically add freshly installed plugins to the 'runtimepath'. + (Default: `true` for the best default experience). {generate_help_pages?} (boolean) - Whether to re-generate plugins help pages after installation/upgrade. (Default: `true`). + Whether to re-generate plugins help pages after installation/upgrade. (Default: `true`). {reinstall_dev_rocks_on_update?} (boolean) - Whether to reinstall 'dev' rocks on update - (Default: `true`, as rocks.nvim cannot determine if 'dev' rocks are up to date). + Whether to reinstall 'dev' rocks on update + (Default: `true`, as rocks.nvim cannot determine if 'dev' rocks are up to date). {enable_luarocks_loader?} (boolean) - Whether to use the luarocks loader to support multiple dependencies (Default: `true`). - {luarocks_config?} (string|table) - Path to the luarocks config file or table of extra luarocks config options. - If a table or not set, rocks.nvim will create a default luarocks config in `rocks_path` - and merge it with this table. - Warning: this is a file path, You should include the settings in the default luarocks-config.lua before overriding this. + Whether to use the luarocks loader to support multiple dependencies (Default: `true`). + {luarocks_config?} (table) + Extra luarocks config options. + rocks.nvim will create a default luarocks config in `rocks_path` and merge it with this table (if set). ============================================================================== diff --git a/flake.nix b/flake.nix index 4ac2a8f7..15605c6c 100644 --- a/flake.nix +++ b/flake.nix @@ -72,7 +72,6 @@ inherit nvim; plugins = with pkgs.lua51Packages; [ toml-edit - toml fidget-nvim fzy nvim-nio diff --git a/lua/rocks/config/init.lua b/lua/rocks/config/init.lua index 7d9764b2..15f7ccaa 100644 --- a/lua/rocks/config/init.lua +++ b/lua/rocks/config/init.lua @@ -46,11 +46,9 @@ local config = {} --- Whether to use the luarocks loader to support multiple dependencies (Default: `true`). ---@field enable_luarocks_loader? boolean --- ---- Path to the luarocks config file or table of extra luarocks config options. ---- If a table or not set, rocks.nvim will create a default luarocks config in `rocks_path` ---- and merge it with this table. ---- Warning: this is a file path, You should include the settings in the default luarocks-config.lua before overriding this. ----@field luarocks_config? string | table +--- Extra luarocks config options. +--- rocks.nvim will create a default luarocks config in `rocks_path` and merge it with this table (if set). +---@field luarocks_config? table ---@type RocksOpts | fun():RocksOpts vim.g.rocks_nvim = vim.g.rocks_nvim diff --git a/lua/rocks/config/internal.lua b/lua/rocks/config/internal.lua index aa71ae16..f1bf8180 100644 --- a/lua/rocks/config/internal.lua +++ b/lua/rocks/config/internal.lua @@ -117,7 +117,7 @@ local default_config = { vim.tbl_deep_extend("force", vim.empty_dict(), rocks_toml.rocks or {}, rocks_toml.plugins or {}) return config.apply_rock_spec_modifiers(user_rocks) end, - ---@type fun():string + ---@type async fun():string luarocks_config_path = nil, } @@ -154,6 +154,12 @@ if #config.debug_info.unrecognized_configs > 0 then end if type(opts.luarocks_config) == "string" then + vim.deprecate( + "g:rocks_nvim.luarocks_config (string)", + "g:rocks_nvim.luarocks_config (table)", + "3.0.0", + "rocks.nvim" + ) -- luarocks_config override if vim.uv.fs_stat(opts.luarocks_config) then local luarocks_config_path = ("%s"):format(opts.luarocks_config) @@ -168,11 +174,13 @@ if type(opts.luarocks_config) == "string" then end if not opts.luarocks_config or type(opts.luarocks_config) == "table" then local nio = require("nio") - local semaphore = nio.control.semaphore(1) + local luarocks_config_path ---@diagnostic disable-next-line: inject-field config.luarocks_config_path = nio.create(function() - semaphore.acquire() - local luarocks_config_path = vim.fs.joinpath(config.rocks_path, "luarocks-config.lua") + if luarocks_config_path then + return luarocks_config_path + end + luarocks_config_path = vim.fs.joinpath(config.rocks_path, "luarocks-config.lua") local default_luarocks_config = { lua_version = "5.1", rocks_trees = { @@ -194,8 +202,9 @@ if not opts.luarocks_config or type(opts.luarocks_config) == "table" then fs.write_file_await(luarocks_config_path, "w+", config_str) - semaphore.release() - + vim.schedule(function() + require("rocks.log").debug("Using luarocks config " .. config_str) + end) ---@diagnostic disable-next-line: inject-field return ("%s"):format(luarocks_config_path) end) diff --git a/lua/rocks/loader.lua b/lua/rocks/loader.lua index eaa313d6..0deff1f2 100644 --- a/lua/rocks/loader.lua +++ b/lua/rocks/loader.lua @@ -27,13 +27,13 @@ end ---@type async fun():boolean loader.enable = nio.create(function() - log.trace("Enabling luarocks loader") local luarocks_config_path = config.luarocks_config_path() local luarocks_lua_dir = config.luarocks_binary == config.default_luarocks_binary and vim.fs.joinpath(config.rocks_path, "share", "lua") or get_luarocks_lua_dir_from_luarocks() local future = nio.control.future() vim.schedule(function() + log.trace("Enabling luarocks loader") if luarocks_lua_dir then package.path = package.path .. ";" diff --git a/nix/test-overlay.nix b/nix/test-overlay.nix index 206a00cd..afa7222f 100644 --- a/nix/test-overlay.nix +++ b/nix/test-overlay.nix @@ -13,7 +13,6 @@ with ps; [ final.lua51Packages.luarocks-rock toml-edit - toml fidget-nvim fzy nvim-nio diff --git a/plugin/rocks.lua b/plugin/rocks.lua index a6378505..8d2c4f34 100644 --- a/plugin/rocks.lua +++ b/plugin/rocks.lua @@ -24,11 +24,12 @@ log.trace("loading rocks.adapter") local adapter = require("rocks.adapter") log.trace("loading rocks config") local config = require("rocks.config.internal") -log.debug("Using luarocks config " .. config.luarocks_config) -- Initialize the luarocks loader if config.enable_luarocks_loader then - nio.run(require("rocks.loader").enable) + nio.run(function() + require("rocks.loader").enable() + end) end -- Set up the Rocks user command From 677bc2c80d096c375ae2ac0cc5a9b9258e618987 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Fri, 5 Jul 2024 16:55:23 +0200 Subject: [PATCH 04/10] chore: make luarocks loader/luarocks_config synchronous again --- .github/workflows/integration.yml | 2 +- lua/rocks/config/internal.lua | 68 ++++++++++++++++++------------- lua/rocks/loader.lua | 61 +++++++++++---------------- plugin/rocks.lua | 4 +- spec/loader_spec.lua | 3 +- 5 files changed, 66 insertions(+), 72 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index ab58ac1d..fec5b74a 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -54,7 +54,7 @@ jobs: cat rocks/rocks.log echo "vim.cmd.colorscheme('sweetie')" >> .github/resources/init-integration.lua echo "vim.cmd.e('success')" >> .github/resources/init-integration.lua - nvim -u .github/resources/init-integration.lua -c 'set noswapfile' +wq || true + nvim -u .github/resources/init-integration.lua -c 'set noswapfile' +wq if [ ! -f success ]; then echo "Integration test failed!" exit 1 diff --git a/lua/rocks/config/internal.lua b/lua/rocks/config/internal.lua index f1bf8180..aa0bc213 100644 --- a/lua/rocks/config/internal.lua +++ b/lua/rocks/config/internal.lua @@ -117,7 +117,7 @@ local default_config = { vim.tbl_deep_extend("force", vim.empty_dict(), rocks_toml.rocks or {}, rocks_toml.plugins or {}) return config.apply_rock_spec_modifiers(user_rocks) end, - ---@type async fun():string + ---@type fun():string luarocks_config_path = nil, } @@ -153,6 +153,29 @@ if #config.debug_info.unrecognized_configs > 0 then ) end +---@return string +local function mk_luarocks_config() + local default_luarocks_config = { + lua_version = "5.1", + rocks_trees = { + { + name = "rocks.nvim", + root = config.rocks_path, + }, + }, + } + local luarocks_config = vim.tbl_deep_extend("force", default_luarocks_config, opts.luarocks_config or {}) + + local config_str = vim.iter(luarocks_config):fold("", function(acc, k, v) + return ([[ +%s +%s = %s +]]):format(acc, k, vim.inspect(v)) + end) + require("rocks.log").debug("luarocks config:\n" .. config_str) + return config_str +end + if type(opts.luarocks_config) == "string" then vim.deprecate( "g:rocks_nvim.luarocks_config (string)", @@ -173,41 +196,28 @@ if type(opts.luarocks_config) == "string" then end end if not opts.luarocks_config or type(opts.luarocks_config) == "table" then - local nio = require("nio") local luarocks_config_path ---@diagnostic disable-next-line: inject-field - config.luarocks_config_path = nio.create(function() + config.luarocks_config_path = function() if luarocks_config_path then - return luarocks_config_path + return ("%s"):format(luarocks_config_path) end luarocks_config_path = vim.fs.joinpath(config.rocks_path, "luarocks-config.lua") - local default_luarocks_config = { - lua_version = "5.1", - rocks_trees = { - { - name = "rocks.nvim", - root = config.rocks_path, - }, - }, - } - local luarocks_config = vim.tbl_deep_extend("force", default_luarocks_config, opts.luarocks_config or {}) - - ---@type string - local config_str = vim.iter(luarocks_config):fold("", function(acc, k, v) - return ([[ -%s -%s = %s -]]):format(acc, k, vim.inspect(v)) - end) - - fs.write_file_await(luarocks_config_path, "w+", config_str) - - vim.schedule(function() - require("rocks.log").debug("Using luarocks config " .. config_str) - end) + require("rocks.log").debug("luarocks config path: " .. luarocks_config_path) + -- NOTE: We don't use fs/libuv here, because we need the file to be written + -- before it is used + local fh = io.open(luarocks_config_path, "w+") + if fh then + local config_str = mk_luarocks_config() + fh:write(config_str) + fh:close() + else + require("rocks.log").error(("Could not open %s for writing."):format(luarocks_config_path)) + luarocks_config_path = "" + end ---@diagnostic disable-next-line: inject-field return ("%s"):format(luarocks_config_path) - end) + end end return config diff --git a/lua/rocks/loader.lua b/lua/rocks/loader.lua index 0deff1f2..df8f6b99 100644 --- a/lua/rocks/loader.lua +++ b/lua/rocks/loader.lua @@ -2,59 +2,46 @@ local loader = {} local log = require("rocks.log") local config = require("rocks.config.internal") -local nio = require("nio") ---@return string | nil local function get_luarocks_lua_dir_from_luarocks() - local future = nio.control.future() - local ok = pcall( - vim.system, - { config.luarocks_binary, "--lua-version=5.1", "which", "luarocks.loader" }, - nil, - function(sc) - future.set(sc) - end - ) + ---@type boolean, vim.SystemObj + local ok, so = pcall(vim.system, { config.luarocks_binary, "--lua-version=5.1", "which", "luarocks.loader" }, nil) if not ok then log.error(("Could not invoke luarocks at %s"):format(config.luarocks_binary)) return end ---@type vim.SystemCompleted - local sc = future.wait() + local sc = so:wait() local result = sc.stdout and sc.stdout:match(vim.fs.joinpath("(%S+)", "5.1", "luarocks", "loader.lua")) return result end ----@type async fun():boolean -loader.enable = nio.create(function() +---@return boolean success +function loader.enable() local luarocks_config_path = config.luarocks_config_path() + log.trace("Enabling luarocks loader") local luarocks_lua_dir = config.luarocks_binary == config.default_luarocks_binary and vim.fs.joinpath(config.rocks_path, "share", "lua") or get_luarocks_lua_dir_from_luarocks() - local future = nio.control.future() - vim.schedule(function() - log.trace("Enabling luarocks loader") - if luarocks_lua_dir then - package.path = package.path - .. ";" - .. table.concat({ - vim.fs.joinpath(luarocks_lua_dir, "5.1", "?.lua"), - vim.fs.joinpath(luarocks_lua_dir, "5.1", "init.lua"), - }, ";") - vim.env.LUAROCKS_CONFIG = luarocks_config_path - local ok, err = pcall(require, "luarocks.loader") - if ok then - future.set(true) - return - end - log.error(err or "Unknown error initializing luarocks loader") - vim.notify("Failed to initialize luarocks loader: " .. err, vim.log.levels.WARN, { - title = "rocks.nvim", - }) + if luarocks_lua_dir then + package.path = package.path + .. ";" + .. table.concat({ + vim.fs.joinpath(luarocks_lua_dir, "5.1", "?.lua"), + vim.fs.joinpath(luarocks_lua_dir, "5.1", "init.lua"), + }, ";") + vim.env.LUAROCKS_CONFIG = luarocks_config_path + local ok, err = pcall(require, "luarocks.loader") + if ok then + return true end - future.set(false) - end) - return future.wait() -end) + log.error(err or "Unknown error initializing luarocks loader") + vim.notify("Failed to initialize luarocks loader: " .. err, vim.log.levels.WARN, { + title = "rocks.nvim", + }) + end + return false +end return loader diff --git a/plugin/rocks.lua b/plugin/rocks.lua index 8d2c4f34..c6702eb4 100644 --- a/plugin/rocks.lua +++ b/plugin/rocks.lua @@ -27,9 +27,7 @@ local config = require("rocks.config.internal") -- Initialize the luarocks loader if config.enable_luarocks_loader then - nio.run(function() - require("rocks.loader").enable() - end) + require("rocks.loader").enable() end -- Set up the Rocks user command diff --git a/spec/loader_spec.lua b/spec/loader_spec.lua index dcd1f1ff..3df0b7a7 100644 --- a/spec/loader_spec.lua +++ b/spec/loader_spec.lua @@ -3,10 +3,9 @@ vim.g.rocks_nvim = { luarocks_binary = "luarocks", } local loader = require("rocks.loader") -local nio = require("nio") describe("rocks.loader", function() - nio.tests.it("Can enable luarocks.loader", function() + it("Can enable luarocks.loader", function() assert.True(loader.enable()) end) end) From d0a49aca5918538ef3930f078893e606a4144bff Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Thu, 4 Jul 2024 18:14:34 +0200 Subject: [PATCH 05/10] feat(update): prompt to install breaking changes --- lua/rocks/commands.lua | 8 +++- lua/rocks/operations/helpers.lua | 70 ++++++++++++++++++++++++++++++++ lua/rocks/operations/init.lua | 44 ++++++++++++++------ lua/rocks/state.lua | 5 +-- spec/operations/helpers_spec.lua | 11 +++++ 5 files changed, 120 insertions(+), 18 deletions(-) diff --git a/lua/rocks/commands.lua b/lua/rocks/commands.lua index 78b34a00..0a0a4308 100644 --- a/lua/rocks/commands.lua +++ b/lua/rocks/commands.lua @@ -21,6 +21,8 @@ --- sync Synchronize installed rocks with rocks.toml. --- It may take more than one sync to prune all rocks that can be pruned. --- update Search for updated rocks and install them. +--- Use 'Rocks! update` to skip prompts to install updates +--- with breaking changes. --- edit Edit the rocks.toml file. --- pin {rock} Pin {rock} to the installed version. --- Pinned rocks are ignored by ':Rocks update'. @@ -119,8 +121,10 @@ end ---@type { [string]: RocksCmd } local rocks_command_tbl = { update = { - impl = function(_) - require("rocks.operations").update() + impl = function(_, opts) + require("rocks.operations").update(nil, { + skip_prompt = opts.bang, + }) end, }, sync = { diff --git a/lua/rocks/operations/helpers.lua b/lua/rocks/operations/helpers.lua index 00d88003..daf2ed79 100644 --- a/lua/rocks/operations/helpers.lua +++ b/lua/rocks/operations/helpers.lua @@ -176,4 +176,74 @@ helpers.is_installed = nio.create(function(rock_name) return future.wait() end, 1) +---@class RockUpdate +---@field name rock_name +---@field version vim.Version +---@field target_version vim.Version +---@field pretty string + +---@param outdated_rocks table +---@return table +function helpers.get_breaking_changes(outdated_rocks) + return vim.iter(outdated_rocks):fold( + {}, + ---@param acc table + ---@param key rock_name + ---@param rock OutdatedRock + function(acc, key, rock) + local _, version = pcall(vim.version.parse, rock.version) + local _, target_version = pcall(vim.version.parse, rock.target_version) + if + type(version) == "table" + and type(target_version) == "table" + and target_version.major > version.major + then + acc[key] = setmetatable({ + name = rock.name, + version = version, + target_version = target_version, + }, { + __tostring = function() + return ("%s %s -> %s"):format(rock.name, tostring(version), tostring(target_version)) + end, + }) + end + return acc + end + ) +end + +---@type async fun(breaking_changes: table, outdated_rocks: table): table +helpers.prompt_for_breaking_update = nio.create(function(breaking_changes, outdated_rocks) + local pretty_changes = vim.iter(breaking_changes) + :map(function(_, breaking_change) + return tostring(breaking_change) + end) + :totable() + local prompt = ([[ +There are potential breaking changes! Update them anyway? +To skip this prompt, run 'Rocks! update' + +Breaking changes: +%s +]]):format(table.concat(pretty_changes, "\n")) + nio.scheduler() + local choice = vim.fn.confirm(prompt, "&Yes\n&No", 2, "Question") + if choice == 1 then + return outdated_rocks + end + return vim.iter(outdated_rocks):fold( + {}, + ---@param acc table + ---@param key rock_name + ---@param rock OutdatedRock + function(acc, key, rock) + if not breaking_changes[key] then + acc[key] = rock + end + return acc + end + ) +end, 2) + return helpers diff --git a/lua/rocks/operations/init.lua b/lua/rocks/operations/init.lua index a578b494..4b3d315c 100644 --- a/lua/rocks/operations/init.lua +++ b/lua/rocks/operations/init.lua @@ -322,10 +322,15 @@ operations.sync = function(user_rocks, on_complete) end) end +---@class rocks.UpdateOpts +---@field skip_prompt? boolean Whether to skip "install breaking changes?" prompts + --- Attempts to update every available rock if it is not pinned. --- This function invokes a UI. ---@param on_complete? function -operations.update = function(on_complete) +---@param opts? rocks.UpdateOpts +operations.update = function(on_complete, opts) + opts = opts or {} local progress_handle = progress.handle.create({ title = "Updating", message = "Checking for updates...", @@ -352,22 +357,36 @@ operations.update = function(on_complete) local user_rocks = parse_rocks_toml() - local outdated_rocks = state.outdated_rocks() + local to_update = vim.iter(state.outdated_rocks()):fold( + {}, + -- Filter unpinned rocks + ---@param acc table + ---@param key rock_name + ---@param rock OutdatedRock + function(acc, key, rock) + local _, user_rock = get_rock_and_key(user_rocks, rock.name) + if user_rock and not user_rock.pin then + acc[key] = rock + end + return acc + end + ) + + local breaking_changes = helpers.get_breaking_changes(to_update) + if not opts.skip_prompt and not vim.tbl_isempty(breaking_changes) then + to_update = helpers.prompt_for_breaking_update(breaking_changes, to_update) + end if config.reinstall_dev_rocks_on_update then - outdated_rocks = add_dev_rocks_for_update(outdated_rocks) + to_update = add_dev_rocks_for_update(to_update) end local external_update_handlers = handlers.get_update_handler_callbacks(user_rocks) - local total_update_count = #outdated_rocks + #external_update_handlers + local total_update_count = #to_update + #external_update_handlers nio.scheduler() local ct = 0 - for name, rock in pairs(outdated_rocks) do - local _, user_rock = get_rock_and_key(user_rocks, rock.name) - if not user_rock or user_rock.pin then - goto skip_update - end + for name, rock in pairs(to_update) do nio.scheduler() progress_handle:report({ message = name, @@ -392,7 +411,6 @@ operations.update = function(on_complete) percentage = get_percentage(ct, total_update_count), }) end - ::skip_update:: end for _, handler in pairs(external_update_handlers) do local function report_progress(message) @@ -407,7 +425,7 @@ operations.update = function(on_complete) ct = ct + 1 end - if vim.tbl_isempty(outdated_rocks) and vim.tbl_isempty(external_update_handlers) then + if vim.tbl_isempty(to_update) and vim.tbl_isempty(external_update_handlers) then progress_handle:report({ message = "Nothing to update!", percentage = 100 }) end -- Update the version for all installed rocks in case rocks.toml is out of date [#380] @@ -463,7 +481,7 @@ local function prompt_retry_install_with_dev(arg_list, rock_name, version) local prompt = rocks[rock_name] and rock_name .. " only has a 'dev' version. Install anyway? " or "Could not find " .. rock_name .. ". Search for 'dev' version?" vim.schedule(function() - local choice = vim.fn.confirm(prompt, "y/n", "y", "Question") + local choice = vim.fn.confirm(prompt, "&Yes\n&No", 1, "Question") if choice == 1 then arg_list = vim.iter(arg_list) :filter(function(arg) @@ -581,7 +599,6 @@ Use 'Rocks install {rock_name}' or install rocks-git.nvim. -- This should be fixed ASAP. if not user_rocks.plugins then local plugins = vim.empty_dict() - ---@cast plugins rock_table user_rocks.plugins = plugins end @@ -603,6 +620,7 @@ Use 'Rocks install {rock_name}' or install rocks-git.nvim. elseif install_spec.opt or install_spec.pin then -- toml-edit's metatable can't set a table directly. -- Each field has to be set individually. + ---@diagnostic disable-next-line: missing-fields user_rocks.plugins[rock_name] = {} user_rocks.plugins[rock_name].version = installed_rock.version user_rocks.plugins[rock_name].opt = install_spec.opt diff --git a/lua/rocks/state.lua b/lua/rocks/state.lua index c8a037cb..2a199d2b 100644 --- a/lua/rocks/state.lua +++ b/lua/rocks/state.lua @@ -51,10 +51,9 @@ state.installed_rocks = nio.create(function() return rocks end) ----@type async fun(): {[string]: OutdatedRock} +---@type async fun(): table state.outdated_rocks = nio.create(function() - local rocks = vim.empty_dict() - ---@cast rocks {[string]: Rock} + local rocks = vim.empty_dict() --[[ @as table ]] local future = nio.control.future() diff --git a/spec/operations/helpers_spec.lua b/spec/operations/helpers_spec.lua index 7401dc90..2306be39 100644 --- a/spec/operations/helpers_spec.lua +++ b/spec/operations/helpers_spec.lua @@ -37,4 +37,15 @@ describe("operations.helpers", function() print("GIT2_DIR not set. Skipping install_args test case") end end) + it("Detect breaking changes", function() + local result = helpers.get_breaking_changes({ + foo = { name = "foo", version = "7.0.0", target_version = "8.0.0" }, + bar = { name = "bar", version = "7.0.0", target_version = "7.1.0" }, + baz = { name = "baz", version = "7.0.0", target_version = "7.1.1" }, + }) + assert.is_not_nil(result.foo) + assert.is_nil(result.bar) + assert.is_nil(result.baz) + assert.same("foo 7.0.0 -> 8.0.0", tostring(result.foo)) + end) end) From 85f6450747c1cb28cff0f663f500b279cdaac754 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Thu, 4 Jul 2024 19:25:22 +0200 Subject: [PATCH 06/10] feat(install): skip prompts with `Rocks! install` --- doc/rocks.txt | 3 +++ lua/rocks/commands.lua | 9 ++++++--- lua/rocks/operations/init.lua | 24 +++++++++++++++++------- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/doc/rocks.txt b/doc/rocks.txt index c7e5e352..33435164 100644 --- a/doc/rocks.txt +++ b/doc/rocks.txt @@ -68,11 +68,14 @@ rocks.nvim commands *rocks-commands* - pin={true|false} Rocks that have been installed with 'pim=true' will be ignored by ':Rocks update'. + Use 'Rocks! install ...' to skip prompts prune {rock} Uninstall {rock} and its stale dependencies, and remove it from rocks.toml. sync Synchronize installed rocks with rocks.toml. It may take more than one sync to prune all rocks that can be pruned. update Search for updated rocks and install them. + Use 'Rocks! update` to skip prompts + with breaking changes. edit Edit the rocks.toml file. pin {rock} Pin {rock} to the installed version. Pinned rocks are ignored by ':Rocks update'. diff --git a/lua/rocks/commands.lua b/lua/rocks/commands.lua index 0a0a4308..da43ecb7 100644 --- a/lua/rocks/commands.lua +++ b/lua/rocks/commands.lua @@ -16,12 +16,13 @@ --- - pin={true|false} --- Rocks that have been installed with 'pim=true' --- will be ignored by ':Rocks update'. +--- Use 'Rocks! install ...' to skip prompts --- prune {rock} Uninstall {rock} and its stale dependencies, --- and remove it from rocks.toml. --- sync Synchronize installed rocks with rocks.toml. --- It may take more than one sync to prune all rocks that can be pruned. --- update Search for updated rocks and install them. ---- Use 'Rocks! update` to skip prompts to install updates +--- Use 'Rocks! update` to skip prompts --- with breaking changes. --- edit Edit the rocks.toml file. --- pin {rock} Pin {rock} to the installed version. @@ -133,12 +134,14 @@ local rocks_command_tbl = { end, }, install = { - impl = function(args) + impl = function(args, opts) if #args == 0 then vim.notify("Rocks install: Called without required package argument.", vim.log.levels.ERROR) return end - require("rocks.operations").add(args) + require("rocks.operations").add(args, nil, { + auto_search_dev = opts.bang, + }) end, complete = function(query) local name, version_query = query:match("([^%s]+)%s(.+)$") diff --git a/lua/rocks/operations/init.lua b/lua/rocks/operations/init.lua index 4b3d315c..95de24e4 100644 --- a/lua/rocks/operations/init.lua +++ b/lua/rocks/operations/init.lua @@ -323,7 +323,7 @@ operations.sync = function(user_rocks, on_complete) end ---@class rocks.UpdateOpts ----@field skip_prompt? boolean Whether to skip "install breaking changes?" prompts +---@field skip_prompts? boolean Whether to skip "install breaking changes?" prompts --- Attempts to update every available rock if it is not pinned. --- This function invokes a UI. @@ -373,7 +373,7 @@ operations.update = function(on_complete, opts) ) local breaking_changes = helpers.get_breaking_changes(to_update) - if not opts.skip_prompt and not vim.tbl_isempty(breaking_changes) then + if not opts.skip_prompts and not vim.tbl_isempty(breaking_changes) then to_update = helpers.prompt_for_breaking_update(breaking_changes, to_update) end if config.reinstall_dev_rocks_on_update then @@ -475,13 +475,18 @@ end ---@param arg_list string[] #Argument list, potentially used by external handlers ---@param rock_name rock_name #The rock name ---@param version? string #The version of the rock to use -local function prompt_retry_install_with_dev(arg_list, rock_name, version) +---@param skip_prompt? boolean +local function prompt_retry_install_with_dev(arg_list, rock_name, version, skip_prompt) if version ~= "dev" then local rocks = cache.try_get_rocks() - local prompt = rocks[rock_name] and rock_name .. " only has a 'dev' version. Install anyway? " + local prompt = ( + rocks[rock_name] and rock_name .. " only has a 'dev' version. Install anyway? " or "Could not find " .. rock_name .. ". Search for 'dev' version?" + ) + .. "\n" + .. "To skip this prompt, run 'Rocks! install {rock}'" vim.schedule(function() - local choice = vim.fn.confirm(prompt, "&Yes\n&No", 1, "Question") + local choice = skip_prompt and 1 or vim.fn.confirm(prompt, "&Yes\n&No", 1, "Question") if choice == 1 then arg_list = vim.iter(arg_list) :filter(function(arg) @@ -499,10 +504,15 @@ local function prompt_retry_install_with_dev(arg_list, rock_name, version) end end +---@class rocks.AddOpts +---@field skip_prompts? boolean Whether to skip any "search 'dev' manifest prompts + --- Adds a new rock and updates the `rocks.toml` file ---@param arg_list string[] #Argument list, potentially used by external handlers. The first argument is the package, e.g. the rock name ---@param callback? fun(rock: Rock) -operations.add = function(arg_list, callback) +---@param opts? rocks.AddOpts +operations.add = function(arg_list, callback, opts) + opts = opts or {} local progress_handle = progress.handle.create({ title = "Installing", lsp_client = { name = constants.ROCKS_NVIM }, @@ -580,7 +590,7 @@ Use 'Rocks install {rock_name}' or install rocks-git.nvim. message = message, }) if not_found then - prompt_retry_install_with_dev(arg_list, rock_name, version) + prompt_retry_install_with_dev(arg_list, rock_name, version, opts.skip_prompts) end nio.scheduler() progress_handle:cancel() From aa1b22f960b234111c6397f03db64d855b8abe27 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Thu, 4 Jul 2024 22:38:18 +0200 Subject: [PATCH 07/10] feat: add breaking change check for `Rocks install {rock}` --- lua/rocks/operations/helpers.lua | 56 +++++++++++++++++-------- lua/rocks/operations/init.lua | 7 ++++ lua/rocks/state.lua | 2 +- spec/operations/install_update_spec.lua | 4 +- 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/lua/rocks/operations/helpers.lua b/lua/rocks/operations/helpers.lua index daf2ed79..3f5691fa 100644 --- a/lua/rocks/operations/helpers.lua +++ b/lua/rocks/operations/helpers.lua @@ -176,6 +176,30 @@ helpers.is_installed = nio.create(function(rock_name) return future.wait() end, 1) +---@param rock OutdatedRock +---@return RockUpdate | nil +local function get_breaking_change(rock) + local _, version = pcall(vim.version.parse, rock.version) + local _, target_version = pcall(vim.version.parse, rock.target_version) + if type(version) == "table" and type(target_version) == "table" and target_version.major > version.major then + return setmetatable({ + name = rock.name, + version = version, + target_version = target_version, + }, { + __tostring = function() + return ("%s %s -> %s"):format(rock.name, tostring(version), tostring(target_version)) + end, + }) + end +end + +---@type fun(rock_name: rock_name): RockUpdate | nil +helpers.get_breaking_change = nio.create(function(rock_name) + local rock = state.outdated_rocks()[rock_name] + return rock and get_breaking_change(rock) +end, 2) + ---@class RockUpdate ---@field name rock_name ---@field version vim.Version @@ -187,32 +211,30 @@ end, 1) function helpers.get_breaking_changes(outdated_rocks) return vim.iter(outdated_rocks):fold( {}, - ---@param acc table + ---@param acc table ---@param key rock_name ---@param rock OutdatedRock function(acc, key, rock) - local _, version = pcall(vim.version.parse, rock.version) - local _, target_version = pcall(vim.version.parse, rock.target_version) - if - type(version) == "table" - and type(target_version) == "table" - and target_version.major > version.major - then - acc[key] = setmetatable({ - name = rock.name, - version = version, - target_version = target_version, - }, { - __tostring = function() - return ("%s %s -> %s"):format(rock.name, tostring(version), tostring(target_version)) - end, - }) + local breaking_change = get_breaking_change(rock) + if breaking_change then + acc[key] = breaking_change end return acc end ) end +---@type async fun(rock: RockUpdate): boolean +helpers.prompt_for_breaking_intall = nio.create(function(rock) + local prompt = ([[ +%s may be a breaking change! Update anyway? +To skip this prompt, run 'Rocks! install {rock}' +]]):format(tostring(rock)) + nio.scheduler() + local choice = vim.fn.confirm(prompt, "&Yes\n&No", 2, "Question") + return choice == 1 +end, 1) + ---@type async fun(breaking_changes: table, outdated_rocks: table): table helpers.prompt_for_breaking_update = nio.create(function(breaking_changes, outdated_rocks) local pretty_changes = vim.iter(breaking_changes) diff --git a/lua/rocks/operations/init.lua b/lua/rocks/operations/init.lua index 95de24e4..9ae33681 100644 --- a/lua/rocks/operations/init.lua +++ b/lua/rocks/operations/init.lua @@ -566,6 +566,13 @@ Use 'Rocks install {rock_name}' or install rocks-git.nvim. end local install_spec = parse_result.spec local version = install_spec.version + local breaking_change = not version and helpers.get_breaking_change(rock_name) + if breaking_change and not helpers.prompt_for_breaking_intall(breaking_change) then + progress_handle:report({ + title = "Installation aborted", + }) + progress_handle:cancel() + end nio.scheduler() progress_handle:report({ message = version and ("%s -> %s"):format(rock_name, version) or rock_name, diff --git a/lua/rocks/state.lua b/lua/rocks/state.lua index 2a199d2b..aee28b97 100644 --- a/lua/rocks/state.lua +++ b/lua/rocks/state.lua @@ -23,7 +23,7 @@ local config = require("rocks.config.internal") local log = require("rocks.log") local nio = require("nio") ----@type async fun(): {[string]: Rock} +---@type async fun(): table state.installed_rocks = nio.create(function() local rocks = vim.empty_dict() ---@cast rocks {[string]: Rock} diff --git a/spec/operations/install_update_spec.lua b/spec/operations/install_update_spec.lua index 3c894d4c..d3847a91 100644 --- a/spec/operations/install_update_spec.lua +++ b/spec/operations/install_update_spec.lua @@ -30,7 +30,9 @@ describe("install/update", function() future = nio.control.future() operations.update(function() future.set(true) - end) + end, { + skip_prompts = true, + }) future.wait() installed_rocks = state.installed_rocks() local updated_version = vim.version.parse(installed_rocks.neorg.version) From 6e2cfb0ae7a2f066fad4908ff1caff37d6322708 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Thu, 4 Jul 2024 23:21:38 +0200 Subject: [PATCH 08/10] docs(readme): add note on using `Rocks install` to update rocks --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a72e4885..1f420839 100644 --- a/README.md +++ b/README.md @@ -342,8 +342,11 @@ Examples: ### Updating rocks -Running the `:Rocks update` command will attempt to update every available rock -if it is not pinned. +- Running the `:Rocks update` command will update every available rock + that is not pinned. + +- `:Rocks install {rock}` (without a version) will update `{rock}` + to the latest version. ### Syncing rocks From 9de02848ebbc441b636263046495768577b80e21 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Fri, 5 Jul 2024 00:22:45 +0200 Subject: [PATCH 09/10] chore: typo --- lua/rocks/commands.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/rocks/commands.lua b/lua/rocks/commands.lua index da43ecb7..fd0c76b6 100644 --- a/lua/rocks/commands.lua +++ b/lua/rocks/commands.lua @@ -124,7 +124,7 @@ local rocks_command_tbl = { update = { impl = function(_, opts) require("rocks.operations").update(nil, { - skip_prompt = opts.bang, + skip_prompts = opts.bang, }) end, }, From 9944b384f3a7f02d37f9490363e2d7501432cfd9 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Fri, 5 Jul 2024 00:25:01 +0200 Subject: [PATCH 10/10] chore: complete partial refactor --- lua/rocks/commands.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/rocks/commands.lua b/lua/rocks/commands.lua index fd0c76b6..b79a8086 100644 --- a/lua/rocks/commands.lua +++ b/lua/rocks/commands.lua @@ -140,7 +140,7 @@ local rocks_command_tbl = { return end require("rocks.operations").add(args, nil, { - auto_search_dev = opts.bang, + skip_prompts = opts.bang, }) end, complete = function(query)