Merge pull request #163 from barrett-ruth/feat/ui/alignment

ui alignment
This commit is contained in:
Barrett Ruth 2025-10-23 23:21:02 -04:00 committed by GitHub
commit 743c29e634
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 160 additions and 70 deletions

View file

@ -378,9 +378,9 @@ Example: Setting up and solving AtCoder contest ABC324
==============================================================================
I/O VIEW *cp-io-view*
The I/O view provides the main view aggregate view into test input and
program output. Used time/memory per test case are appended to the output.
The |cp-panel| offers more fine-grained analysis into each test case.
The I/O view provides lightweight test feedback in persistent side splits.
All test outputs are concatenated with verdict summaries at the bottom.
The |cp-panel| offers more fine-grained analysis with diff modes.
Access the I/O view with :CP run [n]
@ -388,24 +388,33 @@ Layout ~
The I/O view appears as 30% width splits on the right side: >
┌──────────────────────────┬──────────────────────────┐
│ │ Output │
│ │ Test 1: AC (42ms, 8MB) │
│ │ Test 2: AC (38ms, 8MB) │
│ Solution Code │ Test 3: WA (45ms, 8MB) │
│ │ Test 4: AC (51ms, 9MB) │
│ ├──────────────────────────┤
│ │ Input │
│ │ 5 3 │
│ │ 1 2 3 4 5 │
│ │ 2 1 │
│ │ 10 20 │
└──────────────────────────┴──────────────────────────┘
┌──────────────────────────┬─────────────────────────────────────────────┐
│ │ Output (Top Split) │
│ │ 5 510 │
│ │ │
│ │ 7 714 │
│ Solution Code │ │
│ │ Test 1: WA | 212.07/2000 ms | 1/512 MB |...│
│ │ Test 2: WA | 81.94/2000 ms | 1/512 MB |...│
│ ├─────────────────────────────────────────────┤
│ │ Input (Bottom Split) │
│ │ 1 2 3 │
│ │ │
│ │ 4 5 6 │
└──────────────────────────┴─────────────────────────────────────────────┘
<
The output split shows:
1. Concatenated test outputs (separated by blank lines)
2. Space-aligned verdict summary with:
- Test number and status (AC/WA/TLE/MLE/RTE with color highlighting)
- Runtime: actual/limit in milliseconds
- Memory: actual/limit in megabytes
- Exit code (with signal name for crashes)
Usage ~
:CP run Run all tests
:CP run 3 Run test 3
:CP run 3 Run test 3 only
Buffer Customization ~

View file

@ -10,4 +10,29 @@ function M.clearcol(bufnr)
end
end
function M.pad_right(text, width)
local pad = width - #text
if pad <= 0 then
return text
end
return text .. string.rep(' ', pad)
end
function M.pad_left(text, width)
local pad = width - #text
if pad <= 0 then
return text
end
return string.rep(' ', pad) .. text
end
function M.center(text, width)
local pad = width - #text
if pad <= 0 then
return text
end
local left = math.ceil(pad / 2)
return string.rep(' ', left) .. text .. string.rep(' ', pad - left)
end
return M

View file

@ -68,9 +68,9 @@ function M.toggle_interactive(interactor_cmd)
if
not contest_data
or not contest_data.index_map
or contest_data.problems[contest_data.index_map[problem_id]].interactive
or not contest_data.problems[contest_data.index_map[problem_id]].interactive
then
logger.log('This problem is interactive. Use :CP {run,panel}.', vim.log.levels.ERROR)
logger.log('This problem is interactive. Use :CP interact.', vim.log.levels.ERROR)
return
end
@ -210,7 +210,7 @@ function M.ensure_io_view()
and contest_data.index_map
and contest_data.problems[contest_data.index_map[problem_id]].interactive
then
logger.log('No platform configured.', vim.log.levels.ERROR)
logger.log('This problem is not interactive. Use :CP {run,panel}.', vim.log.levels.ERROR)
return
end
@ -285,12 +285,8 @@ function M.run_io_view(test_index)
cache.load()
local contest_data = cache.get_contest_data(platform, contest_id)
if
not contest_data
or not contest_data.index_map
or contest_data.problems[contest_data.index_map[problem_id]].interactive
then
logger.log('This problem is interactive. Use :CP {run,panel}.', vim.log.levels.ERROR)
if not contest_data or not contest_data.index_map then
logger.log('No test cases available.', vim.log.levels.ERROR)
return
end
@ -358,62 +354,122 @@ function M.run_io_view(test_index)
run.run_all_test_cases(test_indices)
local input_lines = {}
for _, idx in ipairs(test_indices) do
local tc = test_state.test_cases[idx]
for _, line in ipairs(vim.split(tc.input, '\n')) do
table.insert(input_lines, line)
end
end
utils.update_buffer_content(io_state.input_buf, input_lines, nil, nil)
local run_render = require('cp.runner.run_render')
run_render.setup_highlights()
if #test_indices == 1 then
local idx = test_indices[1]
local tc = test_state.test_cases[idx]
local status = run_render.get_status_info(tc)
local input_lines = {}
local output_lines = {}
local verdict_data = {}
local widths = { test_num = 0, status = 0, time = 0, memory = 0, exit = 0 }
for _, idx in ipairs(test_indices) do
local tc = test_state.test_cases[idx]
local output_lines = {}
if tc.actual then
for _, line in ipairs(vim.split(tc.actual, '\n', { plain = true, trimempty = false })) do
table.insert(output_lines, line)
end
end
table.insert(output_lines, '')
local time = tc.time_ms and string.format('%.2fms', tc.time_ms) or ''
local code = tc.code and tostring(tc.code) or ''
table.insert(output_lines, string.format('--- %s: %s | Exit: %s ---', status.text, time, code))
local highlights = tc.actual_highlights or {}
local ns = vim.api.nvim_create_namespace('cp_io_view_output')
utils.update_buffer_content(io_state.output_buf, output_lines, highlights, ns)
else
local verdict_lines = {}
local verdict_highlights = {}
for _, idx in ipairs(test_indices) do
local tc = test_state.test_cases[idx]
local status = run_render.get_status_info(tc)
local time = tc.time_ms and string.format('%.2f', tc.time_ms) or ''
local mem = tc.rss_mb and string.format('%.0f', tc.rss_mb) or ''
local line = string.format('Test %d: %s (%sms, %sMB)', idx, status.text, time, mem)
table.insert(verdict_lines, line)
local status_pos = line:find(status.text, 1, true)
if status_pos then
table.insert(verdict_highlights, {
line = #verdict_lines - 1,
col_start = status_pos - 1,
col_end = status_pos - 1 + #status.text,
highlight_group = status.highlight_group,
})
end
if idx < #test_indices then
table.insert(output_lines, '')
end
local verdict_ns = vim.api.nvim_create_namespace('cp_io_view_verdict')
utils.update_buffer_content(io_state.output_buf, verdict_lines, verdict_highlights, verdict_ns)
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,
status = status,
time_data = time_data,
mem_data = mem_data,
exit_str = exit_str,
})
for _, line in ipairs(vim.split(tc.input, '\n')) do
table.insert(input_lines, line)
end
if idx < #test_indices then
table.insert(input_lines, '')
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
local verdict_start = #output_lines
for _, line in ipairs(verdict_lines) do
table.insert(output_lines, line)
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
end
utils.update_buffer_content(io_state.input_buf, input_lines, nil, nil)
local output_ns = vim.api.nvim_create_namespace('cp_io_view_output')
utils.update_buffer_content(io_state.output_buf, output_lines, final_highlights, output_ns)
end
---@param panel_opts? PanelOpts