diff options
| author | William Boman <william@redwill.se> | 2022-07-08 18:34:38 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-07-08 18:34:38 +0200 |
| commit | 976aa4fbee8a070f362cab6f6ec84e9251a90cf9 (patch) | |
| tree | 5e8d9c9c59444a25c7801b8f39763c4ba6e1f76d /lua/mason-core/installer/handle.lua | |
| parent | feat: add gotests, gomodifytags, impl (#28) (diff) | |
| download | mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.gz mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.bz2 mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.lz mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.xz mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.tar.zst mason-976aa4fbee8a070f362cab6f6ec84e9251a90cf9.zip | |
refactor: add mason-schemas and mason-core modules (#29)
* refactor: add mason-schemas and move generated filetype map to mason-lspconfig
* refactor: add mason-core module
Diffstat (limited to 'lua/mason-core/installer/handle.lua')
| -rw-r--r-- | lua/mason-core/installer/handle.lua | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/lua/mason-core/installer/handle.lua b/lua/mason-core/installer/handle.lua new file mode 100644 index 00000000..459e3704 --- /dev/null +++ b/lua/mason-core/installer/handle.lua @@ -0,0 +1,214 @@ +local a = require "mason-core.async" +local spawn = require "mason-core.spawn" +local _ = require "mason-core.functional" +local process = require "mason-core.process" +local EventEmitter = require "mason-core.EventEmitter" +local log = require "mason-core.log" +local Optional = require "mason-core.optional" +local platform = require "mason-core.platform" + +local uv = vim.loop + +---@alias InstallHandleState +--- | '"IDLE"' +--- | '"QUEUED"' +--- | '"ACTIVE"' +--- | '"CLOSED"' + +---@class InstallHandleSpawnInfo +---@field handle luv_handle +---@field pid integer +---@field cmd string +---@field args string[] +local InstallHandleSpawnInfo = {} +InstallHandleSpawnInfo.__index = InstallHandleSpawnInfo + +---@param fields InstallHandleSpawnInfo +function InstallHandleSpawnInfo.new(fields) + return setmetatable(fields, InstallHandleSpawnInfo) +end + +function InstallHandleSpawnInfo:__tostring() + return ("%s %s"):format(self.cmd, table.concat(self.args, " ")) +end + +---@class InstallHandle : EventEmitter +---@field package Package +---@field state InstallHandleState +---@field stdio { buffers: { stdout: string[], stderr: string[] }, sink: StdioSink } +---@field is_terminated boolean +---@field private spawninfo_stack InstallHandleSpawnInfo[] +local InstallHandle = setmetatable({}, { __index = EventEmitter }) +local InstallHandleMt = { __index = InstallHandle } + +---@param handle InstallHandle +local function new_sink(handle) + local stdout, stderr = {}, {} + return { + buffers = { stdout = stdout, stderr = stderr }, + sink = { + stdout = function(chunk) + stdout[#stdout + 1] = chunk + handle:emit("stdout", chunk) + end, + stderr = function(chunk) + stderr[#stderr + 1] = chunk + handle:emit("stderr", chunk) + end, + }, + } +end + +---@param pkg Package +function InstallHandle.new(pkg) + local self = EventEmitter.init(setmetatable({}, InstallHandleMt)) + self.state = "IDLE" + self.package = pkg + self.spawninfo_stack = {} + self.stdio = new_sink(self) + self.is_terminated = false + return self +end + +---@param luv_handle luv_handle +---@param pid integer +---@param cmd string +---@param args string[] +function InstallHandle:push_spawninfo(luv_handle, pid, cmd, args) + local spawninfo = InstallHandleSpawnInfo.new { + handle = luv_handle, + pid = pid, + cmd = cmd, + args = args, + } + log.fmt_trace("Pushing spawninfo stack for %s: %s (pid: %s)", self, spawninfo, pid) + self.spawninfo_stack[#self.spawninfo_stack + 1] = spawninfo + self:emit "spawninfo:change" +end + +---@param luv_handle luv_handle +function InstallHandle:pop_spawninfo(luv_handle) + for i = #self.spawninfo_stack, 1, -1 do + if self.spawninfo_stack[i].handle == luv_handle then + log.fmt_trace("Popping spawninfo stack for %s: %s", self, self.spawninfo_stack[i]) + table.remove(self.spawninfo_stack, i) + self:emit "spawninfo:change" + return true + end + end + return false +end + +---@return Optional @Optional<InstallHandleSpawnInfo> +function InstallHandle:peek_spawninfo_stack() + return Optional.of_nilable(self.spawninfo_stack[#self.spawninfo_stack]) +end + +function InstallHandle:is_idle() + return self.state == "IDLE" +end + +function InstallHandle:is_queued() + return self.state == "QUEUED" +end + +function InstallHandle:is_active() + return self.state == "ACTIVE" +end + +function InstallHandle:is_closed() + return self.state == "CLOSED" +end + +---@param new_state InstallHandleState +function InstallHandle:set_state(new_state) + local old_state = self.state + self.state = new_state + log.fmt_trace("Changing %s state from %s to %s", self, old_state, new_state) + self:emit("state:change", new_state, old_state) +end + +---@param signal integer +function InstallHandle:kill(signal) + assert(not self:is_closed(), "Cannot kill closed handle.") + log.fmt_trace("Sending signal %s to luv handles in %s", signal, self) + for _, spawninfo in pairs(self.spawninfo_stack) do + process.kill(spawninfo.handle, signal) + end + self:emit("kill", signal) +end + +---@param pid integer +local win_taskkill = a.scope(function(pid) + spawn.taskkill { + "/f", + "/t", + "/pid", + pid, + } +end) + +function InstallHandle:terminate() + assert(not self:is_closed(), "Cannot terminate closed handle.") + if self.is_terminated then + log.fmt_trace("Handle is already terminated %s", self) + return + end + log.fmt_trace("Terminating %s", self) + -- https://github.com/libuv/libuv/issues/1133 + if platform.is.win then + for _, spawninfo in ipairs(self.spawninfo_stack) do + win_taskkill(spawninfo.pid) + end + else + self:kill(15) -- SIGTERM + end + self.is_terminated = true + self:emit "terminate" + local check = uv.new_check() + check:start(function() + for _, spawninfo in ipairs(self.spawninfo_stack) do + local luv_handle = spawninfo.handle + local ok, is_closing = pcall(luv_handle.is_closing, luv_handle) + if ok and not is_closing then + return + end + end + check:stop() + if not self:is_closed() then + self:close() + end + end) +end + +function InstallHandle:queued() + assert(self:is_idle(), "Can only queue idle handles.") + self:set_state "QUEUED" +end + +function InstallHandle:active() + assert(self:is_idle() or self:is_queued(), "Can only activate idle or queued handles.") + self:set_state "ACTIVE" +end + +function InstallHandle:close() + log.fmt_trace("Closing %s", self) + assert(not self:is_closed(), "Handle is already closed.") + for _, spawninfo in ipairs(self.spawninfo_stack) do + local luv_handle = spawninfo.handle + local ok, is_closing = pcall(luv_handle.is_closing, luv_handle) + if ok then + assert(is_closing, "There are open libuv handles.") + end + end + self.spawninfo_stack = {} + self:set_state "CLOSED" + self:emit "closed" + self:clear_event_handlers() +end + +function InstallHandleMt:__tostring() + return ("InstallHandle(package=%s, state=%s)"):format(self.package, self.state) +end + +return InstallHandle |
