Skip to content

Commit

Permalink
feat: :Rocks prune command to uninstall rocks and dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
mrcjkb committed Dec 1, 2023
1 parent 32b33b6 commit 4734489
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 7 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,13 @@ The `:Rocks sync` command synchronizes the installed rocks with the `rocks.toml`
### Uninstalling rocks

To uninstall a rock, edit the `rocks.toml` and run `:Rocks sync`.
To uninstall a rock and any of its dependencies,
that are no longer needed, run the `:Rocks prune [rock]` command.

> [!NOTE]
>
> - The command provides completions for rocks that can safely
> be pruned without breaking dependencies.
## :book: License

Expand Down
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
editorconfig-checker
]
++ (with pkgs; [
lua5_1
luarocks
]);
};
Expand Down
3 changes: 1 addition & 2 deletions lua/nio/control.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ nio.control = {}
--- The event can be set from a non-async context.
---@class nio.control.Event
---@field set fun(max_woken?: integer): nil Set the event and signal to all (or limited number of) listeners that the event has occurred. If max_woken is provided and there are more listeners then the event is cleared immediately
---@field wait async fun(): nil Wait for the event to occur, returning immediately if
--- already set
---@field wait async fun(): nil Wait for the event to occur, returning immediately if already set
---@field clear fun(): nil Clear the event
---@field is_set fun(): boolean Returns true if the event is set

Expand Down
14 changes: 14 additions & 0 deletions lua/rocks/commands.lua
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ local rocks_command_tbl = {
local package, version = args[1], args[2]
require("rocks.operations").add(package, version)
end,
prune = function(args)
if #args == 0 then
vim.notify("Rocks prune: Called without required package argument.", vim.log.levels.ERROR)
return
end
local package = args[1]
require("rocks.operations").prune(package)
end,
}

local function rocks(opts)
Expand Down Expand Up @@ -78,6 +86,12 @@ function commands.create_commands()
if #rocks_list > 0 then
return rocks_list
end
local state = require("rocks.state")
name_query = cmdline:match("^Rocks prune%s(.*)$")
rocks_list = state.complete_removable_rocks(name_query)
if #rocks_list > 0 then
return rocks_list
end
if cmdline:match("^Rocks%s+%w*$") then
return vim.iter(rocks_commands)
:filter(function(command)
Expand Down
3 changes: 3 additions & 0 deletions lua/rocks/internal-types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
---@field public name string
---@field public version string

---@class OutdatedRock: Rock
---@field public target_version string

---@class (exact) RockDependency
---@field public name string
---@field public version? string
Expand Down
38 changes: 38 additions & 0 deletions lua/rocks/operations.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ local operations = {}
---@param version? string
---@return Future
operations.install = function(name, version)
state.invalidate_cache()
-- TODO(vhyrro): Input checking on name and version
local future = nio.control.future()
local install_cmd = {
Expand Down Expand Up @@ -67,9 +68,11 @@ operations.install = function(name, version)
}
end

---Removes a rock
---@param name string
---@return Future
operations.remove = function(name)
state.invalidate_cache()
local future = nio.control.future()
local systemObj = luarocks.cli({
"remove",
Expand All @@ -86,6 +89,21 @@ operations.remove = function(name)
}
end

---Removes a rock, and recursively removes its dependencies
---if they are no longer needed.
---@type fun(name: string)
operations.remove_recursive = nio.create(function(name)
---@cast name string
local dependencies = state.rock_dependencies(name)
operations.remove(name).wait()
local removable_rocks = state.query_removable_rocks()
for _, dep in pairs(dependencies) do
if vim.list_contains(removable_rocks, dep.name) then
operations.remove_recursive(dep.name)
end
end
end)

--- Synchronizes the user rocks with the physical state on the current machine.
--- - Installs missing rocks
--- - Ensures that the correct versions are installed
Expand Down Expand Up @@ -332,4 +350,24 @@ operations.add = function(rock_name, version)
end)
end

---Uninstall a rock, pruning it from rocks.toml.
---@param rock_name string
operations.prune = function(rock_name)
vim.notify("Uninstalling '" .. rock_name .. "'...")
nio.run(function()
-- TODO: Error handling
operations.remove_recursive(rock_name)
vim.schedule(function()
local config_file = fs.read_or_create(config.config_path, constants.DEFAULT_CONFIG)
local user_rocks = require("toml_edit").parse(config_file)
if not user_rocks.plugins then
return
end
user_rocks.plugins[rock_name] = nil
fs.write_file(config.config_path, "w", tostring(user_rocks))
vim.notify("Uninstalled: " .. rock_name)
end)
end)
end

return operations
70 changes: 66 additions & 4 deletions lua/rocks/state.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@

local state = {}

---Used for completions only
---@type string[] | nil
local _removable_rock_cache = nil

local luarocks = require("rocks.luarocks")
local nio = require("nio")

---@type fun(): {[string]: Rock}
---@async
---@type fun(): {[string]: Rock}
state.installed_rocks = nio.create(function()
---@type {[string]: Rock}
local rocks = {}
Expand All @@ -48,8 +52,8 @@ state.installed_rocks = nio.create(function()
return rocks
end)

---@type fun(): {[string]: Rock}
---@async
---@type fun(): {[string]: OutdatedRock}
state.outdated_rocks = nio.create(function()
---@type {[string]: Rock}
local rocks = {}
Expand Down Expand Up @@ -78,19 +82,24 @@ state.outdated_rocks = nio.create(function()
end)

---List the dependencies of an installed Rock
---@type fun(rock:Rock): {[string]: RockDependency}
---@async
---@type fun(rock:Rock|string): {[string]: RockDependency}
state.rock_dependencies = nio.create(function(rock)
---@cast rock Rock|string

---@type {[string]: RockDependency}
local dependencies = {}

local future = nio.control.future()

---@type string
local rock_name = rock.name or rock

luarocks.cli({
"show",
"--deps",
"--porcelain",
rock.name,
rock_name,
}, function(obj)
if obj.code ~= 0 then
future.set_error(obj.stderr)
Expand All @@ -114,4 +123,57 @@ state.rock_dependencies = nio.create(function(rock)
return dependencies
end)

---List installed rocks that are not dependencies of any other rocks
---and can be removed.
---@async
---@type fun(): string[]
state.query_removable_rocks = nio.create(function()
local installed_rocks = state.installed_rocks()
--- Unfortunately, luarocks can't list dependencies via its CLI.
---@type string[]
local dependent_rocks = {}
for _, rock in pairs(installed_rocks) do
for _, dep in pairs(state.rock_dependencies(rock)) do
dependent_rocks[#dependent_rocks + 1] = dep.name
end
end
---@diagnostic disable-next-line: invisible
return vim.iter(nio.fn.keys(installed_rocks))
:filter(function(rock_name)
return rock_name ~= "rocks.nvim" and not vim.list_contains(dependent_rocks, rock_name)
end)
:totable()
end)

---@async
local populate_removable_rock_cache = nio.create(function()
if _removable_rock_cache then
return
end
_removable_rock_cache = state.query_removable_rocks()
end)

---Completion for installed rocks that are not dependencies of other rocks
---and can be removed.
---@param query string | nil
---@return string[]
state.complete_removable_rocks = function(query)
if not _removable_rock_cache then
nio.run(populate_removable_rock_cache)
return {}
end
if not query then
return {}
end
return vim.iter(_removable_rock_cache)
:filter(function(rock_name)
return vim.startswith(rock_name, query)
end)
:totable()
end

state.invalidate_cache = function()
_removable_rock_cache = nil
end

return state

0 comments on commit 4734489

Please sign in to comment.