create colored summary output for all commands

This commit is contained in:
2025-12-27 15:13:28 +01:00
parent 88aa1de902
commit 519fc38769
2 changed files with 752 additions and 2 deletions

View File

@@ -14,9 +14,47 @@ local state = {
last_scope_kind = nil,
last_scope_exit_code = nil,
last_scope_failures = nil,
last_border_kind = "default",
autocmds_set = false,
}
local summary_ns = vim.api.nvim_create_namespace("TestSamuraiSummary")
local result_ns = vim.api.nvim_create_namespace("TestSamuraiResult")
local function get_hl_fg(name)
local ok, hl = pcall(vim.api.nvim_get_hl, 0, { name = name, link = true })
if ok and type(hl) == "table" and hl.fg then
return hl.fg
end
return nil
end
local function resolve_hl_fg(names, fallback)
for _, name in ipairs(names or {}) do
local fg = get_hl_fg(name)
if fg then
return fg
end
end
return fallback
end
local function setup_summary_highlights()
local normal_fg = get_hl_fg("Normal")
local pass_fg = resolve_hl_fg({ "DiffAdd", "DiagnosticOk" }, normal_fg)
local fail_fg = resolve_hl_fg({ "DiffDelete", "DiagnosticError" }, normal_fg)
local skip_fg = resolve_hl_fg({ "DiagnosticInfo", "DiagnosticHint" }, normal_fg)
pcall(vim.api.nvim_set_hl, 0, "TestSamuraiSummaryBold", { fg = normal_fg, bold = true })
pcall(vim.api.nvim_set_hl, 0, "TestSamuraiSummaryPass", { fg = pass_fg, bold = true })
pcall(vim.api.nvim_set_hl, 0, "TestSamuraiSummaryFail", { fg = fail_fg, bold = true })
pcall(vim.api.nvim_set_hl, 0, "TestSamuraiSummarySkip", { fg = skip_fg, bold = true })
pcall(vim.api.nvim_set_hl, 0, "TestSamuraiResultPass", { fg = pass_fg })
pcall(vim.api.nvim_set_hl, 0, "TestSamuraiResultFail", { fg = fail_fg })
pcall(vim.api.nvim_set_hl, 0, "TestSamuraiResultSkip", { fg = skip_fg })
pcall(vim.api.nvim_set_hl, 0, "TestSamuraiBorderPass", { fg = pass_fg, bold = true })
pcall(vim.api.nvim_set_hl, 0, "TestSamuraiBorderFail", { fg = fail_fg, bold = true })
end
local function load_runners()
state.runners = {}
local opts = config.get()
@@ -51,12 +89,20 @@ local function ensure_output_autocmds()
end,
})
vim.api.nvim_create_autocmd("ColorScheme", {
group = group,
callback = function()
setup_summary_highlights()
end,
})
state.autocmds_set = true
end
function M.setup()
load_runners()
ensure_output_autocmds()
setup_summary_highlights()
state.last_command = nil
state.last_runner = nil
state.last_scope_command = nil
@@ -64,6 +110,7 @@ function M.setup()
state.last_scope_kind = nil
state.last_scope_exit_code = nil
state.last_scope_failures = nil
state.last_border_kind = "default"
end
function M.reload_runners()
@@ -176,6 +223,19 @@ local function float_geometry()
return width, height, row, col
end
local function apply_border_kind(win, kind)
if not (win and vim.api.nvim_win_is_valid(win)) then
return
end
if kind == "pass" then
vim.api.nvim_win_set_option(win, "winhighlight", "FloatBorder:TestSamuraiBorderPass")
elseif kind == "fail" then
vim.api.nvim_win_set_option(win, "winhighlight", "FloatBorder:TestSamuraiBorderFail")
else
vim.api.nvim_win_set_option(win, "winhighlight", "")
end
end
local function create_output_win(initial_lines)
if state.last_win and vim.api.nvim_win_is_valid(state.last_win) then
pcall(vim.api.nvim_win_close, state.last_win, true)
@@ -215,6 +275,7 @@ local function create_output_win(initial_lines)
state.last_win = win
state.last_buf = buf
apply_border_kind(win, state.last_border_kind)
return buf, win
end
@@ -251,6 +312,7 @@ local function reopen_output_win()
end, { buffer = state.last_buf, nowait = true, silent = true })
state.last_win = win
apply_border_kind(win, state.last_border_kind)
return state.last_buf, win
end
@@ -344,6 +406,137 @@ local function format_results(results, scope_kind)
return lines
end
local function make_summary_tracker(enabled)
if not enabled then
return nil
end
return {
passes = {},
failures = {},
skips = {},
}
end
local function add_unique_items(target, items)
if type(items) ~= "table" then
return
end
for _, item in ipairs(items) do
if item and item ~= "" then
target[item] = true
end
end
end
local function update_summary(summary, results)
if not summary or not results then
return
end
add_unique_items(summary.passes, results.passes)
add_unique_items(summary.failures, results.failures)
add_unique_items(summary.skips, results.skips)
end
local function count_summary(summary)
if not summary then
return 0, 0, 0, 0
end
local function count(set)
local total = 0
for _ in pairs(set) do
total = total + 1
end
return total
end
local pass_count = count(summary.passes)
local fail_count = count(summary.failures)
local skip_count = count(summary.skips)
return pass_count, fail_count, skip_count, pass_count + fail_count + skip_count
end
local function format_summary_lines(summary, elapsed_seconds)
local pass_count, fail_count, skip_count, total = count_summary(summary)
local minutes = 0
local seconds = 0
if type(elapsed_seconds) == "number" and elapsed_seconds >= 0 then
local total_seconds = math.floor(elapsed_seconds + 0.0000001)
minutes = math.floor(total_seconds / 60)
seconds = total_seconds % 60
end
local duration = string.format("%02dm %02ds", minutes, seconds)
return {
"",
string.format("TOTAL %d", total),
string.format("DURATION %s", duration),
"",
string.format(" PASS %d - SKIPPED %d - FAILED %d", pass_count, skip_count, fail_count),
}
end
local function highlight_label_number(buf, ns, lnum, line, label, hl_group)
if not (buf and vim.api.nvim_buf_is_valid(buf)) then
return
end
local label_start = line:find(label, 1, true)
if not label_start then
return
end
local num_start, num_end = line:find("%d+", label_start + #label)
if not num_start then
return
end
vim.api.nvim_buf_add_highlight(buf, ns, hl_group, lnum, num_start - 1, num_end)
end
local function highlight_label_word(buf, ns, lnum, line, label, hl_group)
if not (buf and vim.api.nvim_buf_is_valid(buf)) then
return
end
local label_start = line:find(label, 1, true)
if not label_start then
return
end
vim.api.nvim_buf_add_highlight(buf, ns, hl_group, lnum, label_start - 1, label_start - 1 + #label)
end
local function apply_summary_highlights(buf, start_line, lines)
if not (buf and vim.api.nvim_buf_is_valid(buf)) then
return
end
for i, line in ipairs(lines or {}) do
local lnum = start_line + i - 1
if line:match("^%s*PASS%s+%d+") then
highlight_label_word(buf, summary_ns, lnum, line, "PASS", "TestSamuraiSummaryPass")
highlight_label_number(buf, summary_ns, lnum, line, "PASS", "TestSamuraiSummaryPass")
highlight_label_word(buf, summary_ns, lnum, line, "SKIPPED", "TestSamuraiSummarySkip")
highlight_label_number(buf, summary_ns, lnum, line, "SKIPPED", "TestSamuraiSummarySkip")
highlight_label_word(buf, summary_ns, lnum, line, "FAILED", "TestSamuraiSummaryFail")
highlight_label_number(buf, summary_ns, lnum, line, "FAILED", "TestSamuraiSummaryFail")
elseif line:match("^TOTAL%s+%d+") then
highlight_label_word(buf, summary_ns, lnum, line, "TOTAL", "TestSamuraiSummaryBold")
highlight_label_number(buf, summary_ns, lnum, line, "TOTAL", "TestSamuraiSummaryBold")
elseif line:match("^DURATION%s+") then
highlight_label_word(buf, summary_ns, lnum, line, "DURATION", "TestSamuraiSummaryBold")
end
end
end
local function apply_result_highlights(buf, start_line, lines)
if not (buf and vim.api.nvim_buf_is_valid(buf)) then
return
end
for i, line in ipairs(lines or {}) do
local lnum = start_line + i - 1
if line:match("^%[ PASS %]") then
highlight_label_word(buf, result_ns, lnum, line, "PASS", "TestSamuraiResultPass")
elseif line:match("^%[ FAIL %]") then
highlight_label_word(buf, result_ns, lnum, line, "FAIL", "TestSamuraiResultFail")
elseif line:match("^%[ SKIP %]") then
highlight_label_word(buf, result_ns, lnum, line, "SKIP", "TestSamuraiResultSkip")
end
end
end
local function run_command(command, opts)
local options = opts or {}
if command and type(command.cmd) == "table" and #command.cmd > 0 then
@@ -378,6 +571,14 @@ local function run_command(command, opts)
local parser_state = {}
parser_state.scope_kind = options.scope_kind
local had_parsed_output = false
local summary_enabled = options.scope_kind == "file" or options.scope_kind == "all" or options.scope_kind == "nearest"
local summary = make_summary_tracker(summary_enabled)
local result_counts = make_summary_tracker(true)
state.last_border_kind = "default"
local start_time = nil
if vim.loop and vim.loop.hrtime then
start_time = vim.loop.hrtime()
end
local output_lines = {}
@@ -410,6 +611,8 @@ local function run_command(command, opts)
return
end
had_parsed_output = true
update_summary(summary, results)
update_summary(result_counts, results)
if options.track_scope then
if results.failures_all ~= nil then
state.last_scope_failures = results.failures_all
@@ -422,7 +625,9 @@ local function run_command(command, opts)
return
end
ensure_output_started()
local start_line = vim.api.nvim_buf_line_count(buf)
append_lines(buf, lines)
apply_result_highlights(buf, start_line, lines)
end
run_cmd(cmd, cwd, {
@@ -472,6 +677,15 @@ local function run_command(command, opts)
handle_parsed(results)
end
end
local pass_count, fail_count = count_summary(result_counts)
if fail_count > 0 then
state.last_border_kind = "fail"
elseif pass_count > 0 then
state.last_border_kind = "pass"
else
state.last_border_kind = "default"
end
apply_border_kind(state.last_win, state.last_border_kind)
if parser then
if not had_parsed_output and #output_lines > 0 then
ensure_output_started()
@@ -479,6 +693,17 @@ local function run_command(command, opts)
elseif not has_output then
ensure_output_started()
end
if summary_enabled then
local elapsed = nil
if start_time and vim.loop and vim.loop.hrtime then
elapsed = (vim.loop.hrtime() - start_time) / 1000000000
end
ensure_output_started()
local summary_lines = format_summary_lines(summary, elapsed)
local start_line = vim.api.nvim_buf_line_count(buf)
append_lines(buf, summary_lines)
apply_summary_highlights(buf, start_line, summary_lines)
end
append_lines(buf, { "", "[exit code] " .. tostring(code) })
else
if not has_output then
@@ -487,6 +712,17 @@ local function run_command(command, opts)
vim.api.nvim_buf_set_lines(buf, #cur - 1, #cur, false, {})
end
end
if summary_enabled then
local elapsed = nil
if start_time and vim.loop and vim.loop.hrtime then
elapsed = (vim.loop.hrtime() - start_time) / 1000000000
end
ensure_output_started()
local summary_lines = format_summary_lines(summary, elapsed)
local start_line = vim.api.nvim_buf_line_count(buf)
append_lines(buf, summary_lines)
apply_summary_highlights(buf, start_line, summary_lines)
end
append_lines(buf, { "", "[exit code] " .. tostring(code) })
end
if options.track_scope then