diff --git a/lua/projector/outputs/dap.lua b/lua/projector/outputs/dap.lua index 12f1bd6..6f8967a 100644 --- a/lua/projector/outputs/dap.lua +++ b/lua/projector/outputs/dap.lua @@ -39,6 +39,7 @@ function DapOutput:init(configuration, callback) end self.session.on_close["projector"] = function() + self.state = "inactive" callback(true) end end @@ -66,7 +67,6 @@ function DapOutput:kill() if has_dapui then dapui.close() end - self.state = "inactive" end ---@return task_action[] diff --git a/lua/projector/outputs/doc.lua b/lua/projector/outputs/doc.lua index 673df60..fc99e79 100644 --- a/lua/projector/outputs/doc.lua +++ b/lua/projector/outputs/doc.lua @@ -24,6 +24,7 @@ ---@field hide fun(self: Output) function to hide the output off the screen ---@field actions? fun(self: Output):task_action[] function to list any available actions of the output ---@field preview? fun(self: Output, max_lines: integer):string[]? function to return a preview of output (to show in the dashboard) - max_lines indicates how many lines to show +---@field close? fun(self: Output) optional cleanup function that gets called when the task changes the current output for a different one (for example on restart) ---Output Builder interface. ---@class OutputBuilder diff --git a/lua/projector/outputs/task.lua b/lua/projector/outputs/task.lua index bc9cb79..bae810e 100644 --- a/lua/projector/outputs/task.lua +++ b/lua/projector/outputs/task.lua @@ -1,9 +1,10 @@ ---@class TaskOutput: Output ----@field private name string ----@field private bufnr integer ----@field private winid integer ----@field private state output_status ----@field private job_id integer +---@field private name? string +---@field private bufnr? integer +---@field private winid? integer +---@field private job_id? integer +---@field private died? boolean did task already die? +---@field private killed_manually? boolean was task killed by using the :kill method? local TaskOutput = {} ---@return TaskOutput @@ -16,7 +17,22 @@ end ---@return output_status function TaskOutput:status() - return self.state or "inactive" + if self.died then + return "inactive" + end + + local alive = (self.job_id ~= nil and vim.fn.jobwait({ self.job_id }, 0)[1] == -1) + + if not alive then + self.died = true + return "inactive" + elseif self.winid and vim.api.nvim_win_is_valid(self.winid) then + return "visible" + elseif self.bufnr and vim.api.nvim_buf_is_valid(self.bufnr) then + return "hidden" + end + + return "inactive" end ---@param configuration TaskConfiguration @@ -40,6 +56,11 @@ function TaskOutput:init(configuration, callback) env = configuration.env, cwd = configuration.cwd, on_exit = function(_, code) + if self.killed_manually then + -- close the window and delete the buffer (only if killed usink key combination) + self:close() + end + callback(code == 0) end, } @@ -69,30 +90,19 @@ function TaskOutput:init(configuration, callback) -- close the dummy window vim.api.nvim_win_close(winid, true) - self.state = "hidden" - -- Autocommands - -- Deactivate the output if we delete the buffer - vim.api.nvim_create_autocmd({ "BufDelete", "BufUnload" }, { - buffer = self.bufnr, - callback = function() - self.bufnr = nil - self.state = "inactive" - end, - }) -- If we close the window, the output is hidden vim.api.nvim_create_autocmd({ "WinClosed" }, { buffer = self.bufnr, callback = function() self.winid = nil - self.state = "hidden" end, }) -- switch back to our buffer when trying to open a different buffer in this window vim.api.nvim_create_autocmd({ "BufWinEnter", "BufWinLeave", "BufReadPost", "BufNewFile" }, { callback = function(arg) -- delete autocmd if output is dead - if not self.bufnr or self.state == "inactive" then + if (not self.bufnr or not vim.api.nvim_buf_is_valid(self.bufnr)) or self:status() == "inactive" then return true end @@ -106,8 +116,7 @@ end function TaskOutput:show() -- Open a new window and open the buffer in it - if not self.bufnr then - self.state = "inactive" + if not self.bufnr or not vim.api.nvim_buf_is_valid(self.bufnr) then return end @@ -117,37 +126,55 @@ function TaskOutput:show() -- set winbar vim.api.nvim_win_set_option(self.winid, "winbar", self.name) - - self.state = "visible" end function TaskOutput:hide() pcall(vim.api.nvim_win_close, self.winid, true) - self.winid = nil +end + +---@param winid integer +local function kill_terminal(winid) + if not winid or not vim.api.nvim_win_is_valid(winid) then + return + end + + -- switch to provided window and kill it + vim.api.nvim_set_current_win(winid) - self.state = "hidden" + -- enter insert mode and send ctrl+c and escape (insert-mode, kill-process, normal-mode) + local escaped = vim.api.nvim_replace_termcodes("i", true, false, true) + vim.api.nvim_feedkeys(escaped, "m", false) end function TaskOutput:kill() - -- kill the task - if self.job_id then - vim.fn.jobstop(self.job_id) + local status = self:status() + if status == "inactive" then + return + end + + -- show window on screen if it's not there + if status == "hidden" then + self:show() end - -- close the window and delete the buffer + self.killed_manually = true + -- kill the process + kill_terminal(self.winid) +end + +function TaskOutput:close() pcall(vim.api.nvim_win_close, self.winid, true) pcall(vim.api.nvim_buf_delete, self.bufnr, { force = true }) - - self.state = "inactive" end ---@param max_lines integer ---@return string[]? function TaskOutput:preview(max_lines) - if self.state ~= "visible" and self.state ~= "hidden" then + local status = self:status() + if status ~= "visible" and status ~= "hidden" then return end - if not self.bufnr then + if not self.bufnr or not vim.api.nvim_buf_is_valid(self.bufnr) then return end diff --git a/lua/projector/task.lua b/lua/projector/task.lua index 05a19ff..84d5394 100644 --- a/lua/projector/task.lua +++ b/lua/projector/task.lua @@ -179,10 +179,11 @@ function Task:run(opts) callback(ok) end - -- build the output and run the task - if mode ~= self.last_mode or not self.output then - self.output = self.output_builders[mode]:build() + -- close the old output, build a new one and run the task + if self.output and type(self.output.close) == "function" then + self.output:close() end + self.output = self.output_builders[mode]:build() self.output:init(self.expand_config_variables(self.configuration), cb) -- show the output