diff --git a/lua/custom/plugins/init.lua b/lua/custom/plugins/init.lua index b2b289d4..ee6ed513 100644 --- a/lua/custom/plugins/init.lua +++ b/lua/custom/plugins/init.lua @@ -7,6 +7,12 @@ return { name = 'custom-task', lazy = false, config = function() - require('custom.task').setup() + require('custom.task').setup({ + float = { + width = 0.6, + height = 0.5, + border = 'single', + }, + }) end, -} \ No newline at end of file +} diff --git a/lua/custom/task/init.lua b/lua/custom/task/init.lua index d6176f10..8389d879 100644 --- a/lua/custom/task/init.lua +++ b/lua/custom/task/init.lua @@ -1,36 +1,64 @@ -local Module = {} +local M = {} -Module.setup = function() end +M._win = nil +M._buf = nil +M._last_task = nil +M._options = {} -local function centered_float_size() - local width = math.floor(vim.o.columns * 0.8) - local height = math.floor(vim.o.lines * 0.8) +local defaults = { + float = { + width = 0.8, + height = 0.8, + border = 'rounded', + }, + scroll = { + auto = true, + }, + keymaps = { + rerun = 'rt' + }, +} + +local function setup_global_keymaps() + if M._options.keymaps and M._options.keymaps.rerun then + vim.keymap.set('n', M._options.keymaps.rerun, function() + if M._last_task then + M.execute_task(M._last_task) + else + vim.notify('No task has been run yet.', vim.log.levels.WARN) + end + end, { desc = '[R]erun last [T]ask' }) + end +end + +function M.setup(opts) + vim.validate({ + opts = { opts, 'table', true } + }) + M._options = vim.tbl_deep_extend('force', {}, defaults, opts or {}) + + if M._options.keymaps ~= false then + setup_global_keymaps() + end +end + +local function float_size() + local cfg = M._options.float + local width = math.floor(vim.o.columns * (cfg.width or 0.8)) + local height = math.floor(vim.o.lines * (cfg.height or 0.8)) local row = math.floor((vim.o.lines - height) / 2) local col = math.floor((vim.o.columns - width) / 2) - return width, height, row, col + return width, height, row, col, cfg.border or 'rounded' end -Module.get_tasks = function() - local response = vim.fn.system 'task --list --json' - local exit_code = vim.v.shell_error - - if exit_code ~= 0 then - vim.notify('Task command failed (missing Taskfile?)', vim.log.levels.ERROR) - return {} - end - - local ok, data = pcall(vim.fn.json_decode, response) - if not ok or not data or not data.tasks then - vim.notify('Failed to parse task output.', vim.log.levels.ERROR) - return {} - end - - return data.tasks +local function scroll_to_bottom() + vim.schedule(function() + pcall(vim.cmd, 'normal! G') + end) end -Module.execute_task = function(task) - local width, height, row, col = centered_float_size() - +local function create_terminal_window() + local width, height, row, col, border = float_size() local buf = vim.api.nvim_create_buf(false, true) local win = vim.api.nvim_open_win(buf, true, { relative = 'editor', @@ -39,48 +67,92 @@ Module.execute_task = function(task) width = width, height = height, style = 'minimal', - border = 'rounded', + border = border, }) - vim.api.nvim_set_current_buf(buf) - vim.fn.termopen('task ' .. task, { - on_stdout = function(_, _, _) - vim.api.nvim_command 'normal! G' - end, - on_stderr = function(_, _, _) - vim.api.nvim_command 'normal! G' - end, - on_exit = function() - vim.api.nvim_command 'normal! G' - end, - }) + return buf, win +end +local function cleanup_terminal() + if M._win and vim.api.nvim_win_is_valid(M._win) then + vim.api.nvim_win_close(M._win, true) + end + if M._buf and vim.api.nvim_buf_is_valid(M._buf) then + vim.api.nvim_buf_delete(M._buf, { force = true }) + end +end + +local function run_task_in_terminal(buf, task) + local opts = {} + if M._options.scroll.auto then + opts.on_stdout = scroll_to_bottom + opts.on_stderr = scroll_to_bottom + opts.on_exit = scroll_to_bottom + end + + local term_opts = next(opts) and opts or vim.empty_dict() + vim.fn.termopen('task ' .. task, term_opts) +end + +local function set_quit_key(buf, win) vim.keymap.set('n', 'q', function() - vim.api.nvim_win_close(win, true) + if vim.api.nvim_win_is_valid(win) then + vim.api.nvim_win_close(win, true) + end end, { buffer = buf, nowait = true, silent = true }) end -Module.on_choice = function(item) - Module.execute_task(item.name) +function M.execute_task(task) + cleanup_terminal() + local buf, win = create_terminal_window() + M._buf, M._win, M._last_task = buf, win, task + run_task_in_terminal(buf, task) + set_quit_key(buf, win) end -Module.open_window = function() - local tasks = Module.get_tasks() +function M.get_tasks() + if vim.fn.executable('task') ~= 1 then + vim.notify("'task' executable not found in PATH", vim.log.levels.ERROR) + return {} + end + local response = vim.fn.system 'task --list --json' + if vim.v.shell_error ~= 0 then + vim.notify('Task command failed (missing Taskfile?)', vim.log.levels.ERROR) + return {} + end + local ok, data = pcall(vim.fn.json_decode, response) + if not ok or not data or not data.tasks then + vim.notify('Failed to parse task output.', vim.log.levels.ERROR) + return {} + end + return data.tasks +end + +function M.on_choice(item) + if not item or not item.name then + vim.notify('Invalid task selection', vim.log.levels.WARN) + return + end + M.execute_task(item.name) +end + +function M.open_window() + local tasks = M.get_tasks() if #tasks == 0 then vim.notify('No tasks available', vim.log.levels.WARN) return end - - local formatter = function(task) - return string.format('%-20s %s', task.name, task.desc or '') - end - - vim.ui.select(tasks, { prompt = 'Task:', format_item = formatter }, Module.on_choice) + vim.ui.select(tasks, { + prompt = 'Task:', + format_item = function(task) + return string.format('%-20s %s', task.name, task.desc or '') + end, + }, M.on_choice) end -local complete = function(ArgLead, _, _) +local function complete(ArgLead, _, _) local matches = {} - for _, task in ipairs(Module.get_tasks()) do + for _, task in ipairs(M.get_tasks()) do if task.name:lower():match('^' .. ArgLead:lower()) then table.insert(matches, task.name) end @@ -91,10 +163,18 @@ end vim.api.nvim_create_user_command('Task', function(input) if input.args ~= '' then - Module.execute_task(input.args) + M.execute_task(input.args) else - Module.open_window() + M.open_window() end end, { bang = true, desc = 'Run tasks defined in a Taskfile', nargs = '?', complete = complete }) -return Module +vim.api.nvim_create_user_command('TaskRerun', function() + if not M._last_task then + vim.notify('No task has been run yet.', vim.log.levels.WARN) + return + end + M.execute_task(M._last_task) +end, { desc = 'Rerun last Task' }) + +return M