From 30d87089a89d1b5874b65b6d1119561fc09482e9 Mon Sep 17 00:00:00 2001 From: Vhyrro Date: Mon, 21 Aug 2023 12:24:40 +0200 Subject: [PATCH] feat!: move code to nio asynchronous logic, implement `sync` command --- lua/rocks/constants.lua | 1 - lua/rocks/operations.lua | 146 +++++++++++++++++++++------------------ lua/rocks/setup.lua | 16 +++-- lua/rocks/state.lua | 39 +++++------ 4 files changed, 106 insertions(+), 96 deletions(-) diff --git a/lua/rocks/constants.lua b/lua/rocks/constants.lua index 64f8489f..77266341 100644 --- a/lua/rocks/constants.lua +++ b/lua/rocks/constants.lua @@ -48,7 +48,6 @@ constants.DEFAULT_CONFIG = [[ [rocks] toml = "0.3.0-0" # rocks.nvim can manage its own runtime dependencies too, goated! -# magick = "1.6.0-1" # I am using magick for testing stuff sometimes, so lets keep it here [plugins] # If the plugin name contains a dot then you must add quotes to the key name! diff --git a/lua/rocks/operations.lua b/lua/rocks/operations.lua index 9448ed2f..ab0c6dfd 100644 --- a/lua/rocks/operations.lua +++ b/lua/rocks/operations.lua @@ -14,7 +14,11 @@ --- Commentary: -- -- This module handles all the operations that has something to do with --- luarocks. Installing, uninstalling, updating, etc +-- luarocks. Installing, uninstalling, updating, etc. +-- +-- NOTE: The code is not purely idiomatic lua, as a lot of async is involved. +-- A future rewrite would include pulling in plenary.nvim for utility functions +-- related to async. -- ------------------------------------------------------------------------------- -- @@ -24,89 +28,97 @@ local constants = require("rocks.constants") local fs = require("rocks.fs") local config = require("rocks.config") local state = require("rocks.state") +local nio = require("nio") local operations = {} -function operations.install(name, version, callback) +---@alias Rock {name: string, version: string} +--- +operations.install = function(name, version) -- TODO(vhyrro): Input checking on name and version - vim.system({ "luarocks", "--lua-version=" .. constants.LUA_VERSION, "--tree=" .. config.rocks_path, "install", name, version }, function(obj) - callback(obj.code, obj.stderr) + local future = nio.control.future() + vim.system({ "luarocks", "--lua-version=" .. constants.LUA_VERSION, "--tree=" .. config.rocks_path, "install", name, version }, {}, function(...) + -- TODO: Raise an error with set_error on the future if something goes wrong + future.set(...) end) + return future end -function operations.sync(location) - -- Read or create a new config file and decode it - local user_config = require("toml").decode(fs.read_or_create(location or config.config_path, constants.DEFAULT_CONFIG)) +operations.remove = function(name) + local future = nio.control.future() + vim.system({ "luarocks", "--lua-version=" .. constants.LUA_VERSION, "--tree=" .. config.rocks_path, "remove", name }, {}, function(...) + -- TODO: Raise an error with set_error on the future if something goes wrong + future.set(...) + end) + return future +end - -- Merge `rocks` and `plugins` fields as they are just an eye-candy separator for clarity purposes - local user_rocks = vim.tbl_deep_extend("force", user_config.rocks, user_config.plugins) +--- Synchronizes the state inside of rocks.toml with the physical state on the current +--- machine. +---@param user_rocks? { [string]: Rock|string } +---@type fun(user_rocks: { [string]: Rock|string }) +operations.sync = function(user_rocks) + nio.run(function() + if user_rocks == nil then + -- Read or create a new config file and decode it + local user_config = require("toml").decode(fs.read_or_create(config.config_path, constants.DEFAULT_CONFIG)) + + -- Merge `rocks` and `plugins` fields as they are just an eye-candy separator for clarity purposes + user_rocks = vim.tbl_deep_extend("force", user_config.rocks, user_config.plugins) + end - -- TODO: change this to look for plugins that are not installed yet, also - -- invoke the update command at the end - state.installed_rocks(function(rocks) - local counter = #vim.tbl_keys(rocks) + for name, data in pairs(user_rocks) do + -- TODO(vhyrro): Good error checking + if type(data) == "string" then - if counter == 0 then - vim.print("Nothing new to install!") - return + user_rocks[name] = { + name = name, + version = data, + } + end end - for name, data in pairs(rocks) do - operations.install(name, data.version, function(code, err) - counter = counter - 1 - - if code == 0 then - vim.print("Successfully updated '" .. name .. "'!") - else - vim.print("Failed to update '" .. name .. "'!") - vim.print("Error trace below:") - vim.print(err) - vim.print("Run :messages for full stacktrace.") - return - end - - if counter == 0 then - vim.print("Everything is now in-sync!") - end - end) + local rocks = state.installed_rocks() + + ---@type string[] + local key_list = nio.fn.uniq(vim.list_extend(vim.tbl_keys(rocks), vim.tbl_keys(user_rocks))) + + local actions = {} + + for _, key in ipairs(key_list) do + if user_rocks[key] and not rocks[key] then + table.insert(actions, function() + return operations.install(user_rocks[key].name, user_rocks[key].version).wait() + -- TODO: After the operation is complete update a UI + end) + elseif not user_rocks[key] and rocks[key] then + table.insert(actions, function() + -- NOTE: This will fail if it breaks dependencies. + -- That is generall good, although we definitely want a handler + -- that ignores this. + -- To my knowledge there is no way to query all rocks that are *not* + -- dependencies. + return operations.remove(rocks[key].name).wait() + end) + elseif user_rocks[key].version ~= rocks[key].version then + table.insert(actions, function() + -- TODO: Clean this up? + -- `nio.first` seems to cause luarocks to throw some error, look into that. + local removed = operations.remove(rocks[key].name).wait() + local installed = operations.install(user_rocks[key].name, user_rocks[key].version).wait() + return { removed, installed } + end) + end end - end) - operations.update() -end - -function operations.update() - vim.api.nvim_echo({{"Checking for updates..."}}, false, {}) + if not vim.tbl_isempty(actions) then + -- TODO: Error handling + local tasks = nio.gather(actions) - state.outdated_rocks(function(rocks) - local counter = #vim.tbl_keys(rocks) - - if counter == 0 then - vim.print("Nothing to update!") - return + vim.schedule(function() vim.print(tasks) end) end - for name, data in pairs(rocks) do - vim.print("New version for '" .. name .. "': " .. data.version .. " -> " .. data.target_version) - - operations.install(name, data.target_version, function(code, err) - counter = counter - 1 - - if code == 0 then - vim.print("Successfully updated '" .. name .. "'!") - else - vim.print("Failed to update '" .. name .. "'!") - vim.print("Error trace below:") - vim.print(err) - vim.print("Run :messages for full stacktrace.") - return - end - - if counter == 0 then - vim.print("Everything is now up-to-date!") - end - end) - end + vim.print("Complete!") end) end diff --git a/lua/rocks/setup.lua b/lua/rocks/setup.lua index 87033ada..bbd83e20 100644 --- a/lua/rocks/setup.lua +++ b/lua/rocks/setup.lua @@ -22,10 +22,13 @@ local setup = {} --- local constants = require("rocks.constants") --- local operations = require("rocks.operations") +local constants = require("rocks.constants") local config = require("rocks.config") +local function bootstrap_install(name, version) + vim.system({ "luarocks", "--lua-version=" .. constants.LUA_VERSION, "--tree=" .. config.rocks_path, "install", name, version }):wait() +end + --- Initialize rocks.nvim --- Add luarocks Neovim tree paths to LUA_PATH and LUA_CPATH and download required rocks to work function setup.init() @@ -46,10 +49,11 @@ function setup.init() local is_toml_installed, _ = pcall(require, "toml") if not is_toml_installed then - vim.notify("Installing 'toml' dependency by using luarocks. This requires compiling C++ code so it may take a while, please wait ...") - - -- operations.install("toml", "0.3.0-0") - -- operations.install("nui.nvim", "0.1.0-0") + vim.ui.select({ "Ok" }, { prompt = "Installing 'toml' dependency by using luarocks. This requires compiling C++ code so it may take a while, please wait ..." }, function() + vim.schedule(function() + bootstrap_install("toml", "0.3.0-0") + end) + end) end end diff --git a/lua/rocks/state.lua b/lua/rocks/state.lua index 27705ec6..e168ff7f 100644 --- a/lua/rocks/state.lua +++ b/lua/rocks/state.lua @@ -23,34 +23,29 @@ local state = {} local constants = require("rocks.constants") local config = require("rocks.config") +local nio = require("nio") ----@alias Rock { name: string, version: string, target_version?: string } +---@type fun(): {[string]: Rock} +---@async +state.installed_rocks = nio.create(function() + ---@type {[string]: Rock} + local rocks = {} ---- ----@param callback fun(installed: {[string]: Rock}) -function state.installed_rocks(callback) - local _callback = function(obj) - ---@type {[string]: Rock} - local rocks = {} + local future = nio.control.future() - for name, version in obj.stdout:gmatch("([^%s]+)%s+(%d+%.%d+%.%d+%-%d+)%s+installed%s+[^%s]+") do - rocks[name] = { name = name, version = version } - end + vim.system({"luarocks", "--lua-version=" .. constants.LUA_VERSION, "--tree=" .. config.rocks_path, "list", "--porcelain"}, {text = true}, function(obj) + -- TODO: Error handling + future.set(obj.stdout) + end) - if callback then - callback(rocks) - else - return rocks - end - end + local installed_rock_list = future.wait() - if callback then - vim.system({"luarocks", "--lua-version=" .. constants.LUA_VERSION, "--tree=" .. config.rocks_path, "list", "--porcelain"}, _callback) - else - local rocklist = vim.system({"luarocks", "--lua-version=" .. constants.LUA_VERSION, "--tree=" .. config.rocks_path, "list", "--porcelain"}):wait() - return _callback(rocklist) + for name, version in installed_rock_list:gmatch("([^%s]+)%s+(%d+%.%d+%.%d+%-%d+)%s+installed%s+[^%s]+") do + rocks[name] = { name = name, version = version } end -end + + return rocks +end) ---@param callback fun(installed: {[string]: Rock}) function state.outdated_rocks(callback)