diff --git a/doc/cp.nvim.txt b/doc/cp.nvim.txt index 53c0ed7..0dfd9dd 100644 --- a/doc/cp.nvim.txt +++ b/doc/cp.nvim.txt @@ -296,6 +296,8 @@ run CSES problems with Rust using the single schema: to next test in I/O view. Set to nil to disable. {prev_test_key} (string|nil, default: '') Keymap to navigate to previous test in I/O view. Set to nil to disable. + {format_verdict} (|VerdictFormatter|, default: nil) Custom verdict line + formatter. See |cp-verdict-format|. *cp.PanelConfig* Fields: ~ @@ -472,6 +474,74 @@ Use the setup_io_input and setup_io_output hooks (see |cp.Hooks|) to customize buffer appearance. By default, line numbers and columns are removed via helpers.clearcol (see |cp-helpers|). +============================================================================== +VERDICT FORMATTING *cp-verdict-format* + +Customize how verdict summaries appear in the I/O view using format_verdict. + +Configuration ~ + +Set ui.run.format_verdict to a function that formats verdict data: >lua + format_verdict = function(data) + return { line = "...", highlights = {...} } + end +< +Format Function ~ + *VerdictFormatter* +Input: |VerdictFormatData| table with test results +Output: |VerdictFormatResult| table with formatted line and optional highlights + + *VerdictFormatData* + {index} (integer) Test case number + {status} (table) { text: string, highlight_group: string } + {time_ms} (number) Execution time in milliseconds + {time_limit_ms} (number) Time limit in milliseconds + {memory_mb} (number) Peak memory usage in megabytes + {memory_limit_mb} (number) Memory limit in megabytes + {exit_code} (integer) Process exit code + {signal} (string|nil) Signal name for crashes (e.g. "SIGSEGV") + + *VerdictFormatResult* + {line} (string) The formatted verdict line + {highlights} (table[], optional) Highlight regions: + {col_start} (integer) Start column (0-indexed) + {col_end} (integer) End column (exclusive) + {group} (string) Highlight group name + +Examples ~ + +Minimal format: >lua + format_verdict = function(data) + return { + line = string.format("#%d %s", data.index, data.status.text) + } + end +< +With custom alignment using helpers: >lua + local helpers = require('cp').helpers + format_verdict = function(data) + local status = helpers.pad_right(data.status.text, 3) + local time = string.format("%.1fms", data.time_ms) + return { line = string.format("Test %d: %s %s", data.index, status, time) } + end +< +With highlighting: >lua + format_verdict = function(data) + local line = string.format("%d: %s", data.index, data.status.text) + return { + line = line, + highlights = { + { + col_start = #tostring(data.index) + 2, + col_end = #line, + group = data.status.highlight_group + } + } + } + end +< +See |cp-helpers| for alignment functions: pad_right, pad_left, center. + ============================================================================== PICKER INTEGRATION *cp-picker* diff --git a/lua/cp/config.lua b/lua/cp/config.lua index 069cf75..8ba1517 100644 --- a/lua/cp/config.lua +++ b/lua/cp/config.lua @@ -34,10 +34,32 @@ ---@field setup_io_input? fun(bufnr: integer, state: cp.State) ---@field setup_io_output? fun(bufnr: integer, state: cp.State) +---@class VerdictFormatData +---@field index integer +---@field status { text: string, highlight_group: string } +---@field time_ms number +---@field time_limit_ms number +---@field memory_mb number +---@field memory_limit_mb number +---@field exit_code integer +---@field signal string|nil + +---@class VerdictHighlight +---@field col_start integer +---@field col_end integer +---@field group string + +---@class VerdictFormatResult +---@field line string +---@field highlights? VerdictHighlight[] + +---@alias VerdictFormatter fun(data: VerdictFormatData): VerdictFormatResult + ---@class RunConfig ---@field width number ---@field next_test_key string|nil ---@field prev_test_key string|nil +---@field format_verdict VerdictFormatter|nil ---@class CpUI ---@field ansi boolean @@ -122,7 +144,12 @@ M.defaults = { filename = nil, ui = { ansi = true, - run = { width = 0.3, next_test_key = '', prev_test_key = '' }, + run = { + width = 0.3, + next_test_key = '', + prev_test_key = '', + format_verdict = helpers.default_verdict_formatter, + }, panel = { diff_mode = 'none', max_output_lines = 50 }, diff = { git = { @@ -294,6 +321,10 @@ function M.setup(user_config) end, 'nil or non-empty string', }, + format_verdict = { + cfg.ui.run.format_verdict, + 'function', + }, }) for id, lang in pairs(cfg.languages) do diff --git a/lua/cp/helpers.lua b/lua/cp/helpers.lua index 408ad0a..e31b6f9 100644 --- a/lua/cp/helpers.lua +++ b/lua/cp/helpers.lua @@ -10,6 +10,10 @@ function M.clearcol(bufnr) end end +---Pad text on the right (left-align text within width) +---@param text string +---@param width integer +---@return string function M.pad_right(text, width) local pad = width - #text if pad <= 0 then @@ -18,6 +22,10 @@ function M.pad_right(text, width) return text .. string.rep(' ', pad) end +---Pad text on the left (right-align text within width) +---@param text string +---@param width integer +---@return string function M.pad_left(text, width) local pad = width - #text if pad <= 0 then @@ -26,6 +34,10 @@ function M.pad_left(text, width) return string.rep(' ', pad) .. text end +---Center text within width +---@param text string +---@param width integer +---@return string function M.center(text, width) local pad = width - #text if pad <= 0 then @@ -35,4 +47,44 @@ function M.center(text, width) return string.rep(' ', left) .. text .. string.rep(' ', pad - left) end +---Default verdict formatter for I/O view +---@param data VerdictFormatData +---@return VerdictFormatResult +function M.default_verdict_formatter(data) + local time_data = string.format('%.2f', data.time_ms) .. '/' .. data.time_limit_ms + local mem_data = string.format('%.0f', data.memory_mb) + .. '/' + .. string.format('%.0f', data.memory_limit_mb) + local exit_str = data.signal and string.format('%d (%s)', data.exit_code, data.signal) + or tostring(data.exit_code) + + local test_num_part = 'Test ' .. data.index .. ':' + local status_part = M.pad_right(data.status.text, 3) + local time_part = time_data .. ' ms' + local mem_part = mem_data .. ' MB' + local exit_part = 'exit: ' .. exit_str + + local line = test_num_part + .. ' ' + .. status_part + .. ' | ' + .. time_part + .. ' | ' + .. mem_part + .. ' | ' + .. exit_part + + local highlights = {} + local status_pos = line:find(data.status.text, 1, true) + if status_pos then + table.insert(highlights, { + col_start = status_pos - 1, + col_end = status_pos - 1 + #data.status.text, + group = data.status.highlight_group, + }) + end + + return { line = line, highlights = highlights } +end + return M diff --git a/lua/cp/setup.lua b/lua/cp/setup.lua index bbad9a4..50d603d 100644 --- a/lua/cp/setup.lua +++ b/lua/cp/setup.lua @@ -230,7 +230,7 @@ function M.setup_problem(problem_id, language) state.get_problem_id() or '', lang ) - require('cp.ui.panel').ensure_io_view() + require('cp.ui.views').ensure_io_view() end) end diff --git a/lua/cp/ui/views.lua b/lua/cp/ui/views.lua index 044884a..130da3b 100644 --- a/lua/cp/ui/views.lua +++ b/lua/cp/ui/views.lua @@ -231,12 +231,12 @@ function M.ensure_io_view() local cfg = config_module.get_config() local width = math.floor(vim.o.columns * (cfg.ui.run.width or 0.3)) vim.api.nvim_win_set_width(output_win, width) - output_buf = utils.create_buffer_with_options() + output_buf = utils.create_buffer_with_options('cpout') vim.api.nvim_win_set_buf(output_win, output_buf) vim.cmd.split() input_win = vim.api.nvim_get_current_win() - input_buf = utils.create_buffer_with_options() + input_buf = utils.create_buffer_with_options('cpin') vim.api.nvim_win_set_buf(input_win, input_buf) state.set_io_view_state({ @@ -395,9 +395,10 @@ function M.run_io_view(test_index, debug) local input_lines = {} local output_lines = {} - local verdict_data = {} + local verdict_lines = {} + local verdict_highlights = {} - local widths = { test_num = 0, status = 0, time = 0, memory = 0, exit = 0 } + local formatter = config.ui.run.format_verdict for _, idx in ipairs(test_indices) do local tc = test_state.test_cases[idx] @@ -413,35 +414,32 @@ function M.run_io_view(test_index, debug) local status = run_render.get_status_info(tc) - local time_actual = tc.time_ms and string.format('%.2f', tc.time_ms) or '—' - local time_limit = test_state.constraints and tostring(test_state.constraints.timeout_ms) - or '—' - local time_data = time_actual .. '/' .. time_limit - - local mem_actual = tc.rss_mb and string.format('%.0f', tc.rss_mb) or '—' - local mem_limit = test_state.constraints - and string.format('%.0f', test_state.constraints.memory_mb) - or '—' - local mem_data = mem_actual .. '/' .. mem_limit - - local exit_code = tc.code or 0 - local signal_name = exit_code >= 128 and require('cp.constants').signal_codes[exit_code] or nil - local exit_str = signal_name and string.format('%d (%s)', exit_code, signal_name) - or tostring(exit_code) - - widths.test_num = math.max(widths.test_num, #('Test ' .. idx .. ':')) - widths.status = math.max(widths.status, #status.text) - widths.time = math.max(widths.time, #(time_data .. ' ms')) - widths.memory = math.max(widths.memory, #(mem_data .. ' MB')) - widths.exit = math.max(widths.exit, #('exit: ' .. exit_str)) - - table.insert(verdict_data, { - idx = idx, + ---@type VerdictFormatData + local format_data = { + index = idx, status = status, - time_data = time_data, - mem_data = mem_data, - exit_str = exit_str, - }) + time_ms = tc.time_ms or 0, + time_limit_ms = test_state.constraints and test_state.constraints.timeout_ms or 0, + memory_mb = tc.rss_mb or 0, + memory_limit_mb = test_state.constraints and test_state.constraints.memory_mb or 0, + exit_code = tc.code or 0, + signal = (tc.code and tc.code >= 128) and require('cp.constants').signal_codes[tc.code] + or nil, + } + + local result = formatter(format_data) + table.insert(verdict_lines, result.line) + + if result.highlights then + for _, hl in ipairs(result.highlights) do + table.insert(verdict_highlights, { + line_offset = #verdict_lines - 1, + col_start = hl.col_start, + col_end = hl.col_end, + group = hl.group, + }) + end + end for _, line in ipairs(vim.split(tc.input, '\n')) do table.insert(input_lines, line) @@ -451,35 +449,6 @@ function M.run_io_view(test_index, debug) end end - local verdict_lines = {} - local verdict_highlights = {} - for _, vd in ipairs(verdict_data) do - local test_num_part = helpers.pad_right('Test ' .. vd.idx .. ':', widths.test_num) - local status_part = helpers.pad_right(vd.status.text, widths.status) - local time_part = helpers.pad_right(vd.time_data, widths.time - 3) .. ' ms' - local mem_part = helpers.pad_right(vd.mem_data, widths.memory - 3) .. ' MB' - local exit_part = helpers.pad_right('exit: ' .. vd.exit_str, widths.exit) - - local verdict_line = test_num_part - .. ' ' - .. status_part - .. ' | ' - .. time_part - .. ' | ' - .. mem_part - .. ' | ' - .. exit_part - table.insert(verdict_lines, verdict_line) - - local status_pos = verdict_line:find(vd.status.text, 1, true) - if status_pos then - table.insert(verdict_highlights, { - status = vd.status, - verdict_line = verdict_line, - }) - end - end - if #output_lines > 0 and #verdict_lines > 0 then table.insert(output_lines, '') end @@ -490,16 +459,13 @@ function M.run_io_view(test_index, debug) end local final_highlights = {} - for i, vh in ipairs(verdict_highlights) do - local status_pos = vh.verdict_line:find(vh.status.text, 1, true) - if status_pos then - table.insert(final_highlights, { - line = verdict_start + i - 1, - col_start = status_pos - 1, - col_end = status_pos - 1 + #vh.status.text, - highlight_group = vh.status.highlight_group, - }) - end + for _, vh in ipairs(verdict_highlights) do + table.insert(final_highlights, { + line = verdict_start + vh.line_offset, + col_start = vh.col_start, + col_end = vh.col_end, + highlight_group = vh.group, + }) end utils.update_buffer_content(io_state.input_buf, input_lines, nil, nil)