feat: customization
This commit is contained in:
parent
6f9452c7e1
commit
249e84eb5a
5 changed files with 192 additions and 73 deletions
|
|
@ -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: '<c-p>') 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*
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = '<c-n>', prev_test_key = '<c-p>' },
|
||||
run = {
|
||||
width = 0.3,
|
||||
next_test_key = '<c-n>',
|
||||
prev_test_key = '<c-p>',
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,17 +459,14 @@ 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
|
||||
for _, vh in ipairs(verdict_highlights) do
|
||||
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,
|
||||
line = verdict_start + vh.line_offset,
|
||||
col_start = vh.col_start,
|
||||
col_end = vh.col_end,
|
||||
highlight_group = vh.group,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
utils.update_buffer_content(io_state.input_buf, input_lines, nil, nil)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue