From b5f34cb9edace704d020f32f7992337fd6448b2c Mon Sep 17 00:00:00 2001 From: dario Date: Thu, 1 May 2025 22:22:03 +0200 Subject: [PATCH] feat: add preview of tasks to task plugin --- lua/custom/task/init.lua | 159 +++++++++++++++++++++++++++++++++++---- 1 file changed, 143 insertions(+), 16 deletions(-) diff --git a/lua/custom/task/init.lua b/lua/custom/task/init.lua index 8389d879..c4d9d912 100644 --- a/lua/custom/task/init.lua +++ b/lua/custom/task/init.lua @@ -1,4 +1,5 @@ local M = {} +local preview_ns = vim.api.nvim_create_namespace 'TaskPreview' M._win = nil M._buf = nil @@ -15,7 +16,7 @@ local defaults = { auto = true, }, keymaps = { - rerun = 'rt' + rerun = 'rt', }, } @@ -32,9 +33,9 @@ local function setup_global_keymaps() end function M.setup(opts) - vim.validate({ - opts = { opts, 'table', true } - }) + vim.validate { + opts = { opts, 'table', true }, + } M._options = vim.tbl_deep_extend('force', {}, defaults, opts or {}) if M._options.keymaps ~= false then @@ -42,33 +43,49 @@ function M.setup(opts) 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 function calculate_dimensions(percent_width, percent_height) + local width = math.floor(vim.o.columns * percent_width) + local height = math.floor(vim.o.lines * percent_height) local row = math.floor((vim.o.lines - height) / 2) local col = math.floor((vim.o.columns - width) / 2) + return width, height, row, col +end + +local function float_size() + local cfg = M._options.float + local width, height, row, col = calculate_dimensions(cfg.width or 0.8, cfg.height or 0.8) return width, height, row, col, cfg.border or 'rounded' end local function scroll_to_bottom() vim.schedule(function() - pcall(vim.cmd, 'normal! G') + vim.cmd 'normal! G' end) end +local function open_floating_win(buf, opts, enter) + return vim.api.nvim_open_win( + buf, + enter or false, + vim.tbl_extend('force', { + relative = 'editor', + style = 'minimal', + border = 'single', + }, opts or {}) + ) +end + 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', + local win = open_floating_win(buf, { row = row, col = col, width = width, height = height, - style = 'minimal', border = border, - }) + }, true) + vim.api.nvim_set_current_buf(buf) return buf, win end @@ -82,6 +99,23 @@ local function cleanup_terminal() end end +local function clean_dry_output(lines) + local cleaned = {} + for _, line in ipairs(lines) do + local cleaned_line = line:gsub('^task:%s+%[.-%]%s*', '') + table.insert(cleaned, cleaned_line) + end + return cleaned +end + +local function highlight_line(buf, ns, line) + local content = vim.api.nvim_buf_get_lines(buf, line, line + 1, false)[1] or '' + vim.api.nvim_buf_set_extmark(buf, ns, line, 0, { + end_col = #content, + hl_group = 'Visual', + }) +end + local function run_task_in_terminal(buf, task) local opts = {} if M._options.scroll.auto then @@ -111,7 +145,7 @@ function M.execute_task(task) end function M.get_tasks() - if vim.fn.executable('task') ~= 1 then + if vim.fn.executable 'task' ~= 1 then vim.notify("'task' executable not found in PATH", vim.log.levels.ERROR) return {} end @@ -130,7 +164,7 @@ end function M.on_choice(item) if not item or not item.name then - vim.notify('Invalid task selection', vim.log.levels.WARN) + vim.notify("Invalid task selection: no 'name' field", vim.log.levels.WARN) return end M.execute_task(item.name) @@ -150,6 +184,99 @@ function M.open_window() }, M.on_choice) end +function M.select_task_with_preview() + local tasks = M.get_tasks() + if #tasks == 0 then + vim.notify('No tasks available', vim.log.levels.WARN) + return + end + + local total_width, total_height, row, col = calculate_dimensions(0.8, 0.6) + local list_width = math.floor(total_width * 0.4) + local preview_width = total_width - list_width - 2 + local list_buf = vim.api.nvim_create_buf(false, true) + local preview_buf = vim.api.nvim_create_buf(false, true) + + local lines = {} + for _, task in ipairs(tasks) do + table.insert(lines, string.format('%-20s %s', task.name, task.desc or '')) + end + vim.api.nvim_buf_set_lines(list_buf, 0, -1, false, lines) + + local list_win = open_floating_win(list_buf, { + row = row, + col = col, + width = list_width, + height = total_height, + title = 'Tasks', + title_pos = 'center', + }, true) + + local preview_win = open_floating_win(preview_buf, { + row = row, + col = col + list_width + 2, + width = preview_width, + height = total_height, + title = 'Preview', + title_pos = 'center', + }, false) + + local current_line = 1 + local line = current_line - 1 + highlight_line(list_buf, preview_ns, line) + + local function update_preview(index) + local task = tasks[index] + if not task then + return + end + local output = vim.fn.system { 'task', task.name, '--dry' } + local cleaned_output = clean_dry_output(vim.split(output, '\n')) + vim.api.nvim_buf_set_lines(preview_buf, 0, -1, false, cleaned_output) + end + + update_preview(current_line) + + vim.keymap.set('n', '', function() + local task = tasks[current_line] + if task then + vim.api.nvim_win_close(list_win, true) + vim.api.nvim_win_close(preview_win, true) + M.execute_task(task.name) + end + end, { buffer = list_buf }) + + vim.keymap.set('n', 'q', function() + vim.api.nvim_win_close(list_win, true) + vim.api.nvim_win_close(preview_win, true) + end, { buffer = list_buf }) + + vim.keymap.set('n', '', function() + vim.api.nvim_win_close(list_win, true) + vim.api.nvim_win_close(preview_win, true) + end, { buffer = list_buf }) + + vim.keymap.set('n', 'j', function() + if current_line < #tasks then + vim.api.nvim_buf_clear_namespace(list_buf, preview_ns, 0, -1) + current_line = current_line + 1 + highlight_line(list_buf, preview_ns, current_line - 1) + update_preview(current_line) + vim.api.nvim_win_set_cursor(list_win, { current_line, 0 }) + end + end, { buffer = list_buf }) + + vim.keymap.set('n', 'k', function() + if current_line > 1 then + vim.api.nvim_buf_clear_namespace(list_buf, preview_ns, 0, -1) + current_line = current_line - 1 + highlight_line(list_buf, preview_ns, current_line - 1) + update_preview(current_line) + vim.api.nvim_win_set_cursor(list_win, { current_line, 0 }) + end + end, { buffer = list_buf }) +end + local function complete(ArgLead, _, _) local matches = {} for _, task in ipairs(M.get_tasks()) do @@ -165,7 +292,7 @@ vim.api.nvim_create_user_command('Task', function(input) if input.args ~= '' then M.execute_task(input.args) else - M.open_window() + M.select_task_with_preview() end end, { bang = true, desc = 'Run tasks defined in a Taskfile', nargs = '?', complete = complete })