Merge pull request #212 from barrettruth/feat/async

make `:CP {run,panel}` asynchronous
This commit is contained in:
Barrett Ruth 2026-01-27 13:25:15 -06:00 committed by GitHub
commit ae7b571b68
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 522 additions and 467 deletions

View file

@ -39,14 +39,14 @@ end
---@param compile_cmd string[]
---@param substitutions SubstitutableCommand
function M.compile(compile_cmd, substitutions)
---@param on_complete fun(r: {code: integer, stdout: string})
function M.compile(compile_cmd, substitutions, on_complete)
local cmd = substitute_template(compile_cmd, substitutions)
local sh = table.concat(cmd, ' ') .. ' 2>&1'
local t0 = vim.uv.hrtime()
local r = vim.system({ 'sh', '-c', sh }, { text = false }):wait()
vim.system({ 'sh', '-c', sh }, { text = false }, function(r)
local dt = (vim.uv.hrtime() - t0) / 1e6
local ansi = require('cp.ui.ansi')
r.stdout = ansi.bytes_to_string(r.stdout or '')
@ -56,7 +56,10 @@ function M.compile(compile_cmd, substitutions)
logger.log(('Compilation failed in %.1fms.'):format(dt))
end
return r
vim.schedule(function()
on_complete(r)
end)
end)
end
local function parse_and_strip_time_v(output)
@ -103,7 +106,8 @@ local function parse_and_strip_time_v(output)
return head, peak_mb
end
function M.run(cmd, stdin, timeout_ms, memory_mb)
---@param on_complete fun(result: ExecuteResult)
function M.run(cmd, stdin, timeout_ms, memory_mb, on_complete)
local time_bin = utils.time_path()
local timeout_bin = utils.timeout_path()
@ -117,12 +121,7 @@ function M.run(cmd, stdin, timeout_ms, memory_mb)
local sh = prefix .. timeout_prefix .. ('%s -v sh -c %q 2>&1'):format(time_bin, prog)
local t0 = vim.uv.hrtime()
local r = vim
.system({ 'sh', '-c', sh }, {
stdin = stdin,
text = true,
})
:wait()
vim.system({ 'sh', '-c', sh }, { stdin = stdin, text = true }, function(r)
local dt = (vim.uv.hrtime() - t0) / 1e6
local code = r.code or 0
@ -143,7 +142,7 @@ function M.run(cmd, stdin, timeout_ms, memory_mb)
or lower:find('enomem', 1, true)
local near_cap = peak_mb >= (0.90 * memory_mb)
local mled = (peak_mb >= memory_mb) or near_cap or (oom_hint and not tled)
local mled = (peak_mb >= memory_mb) or near_cap or (oom_hint ~= nil and not tled)
if tled then
logger.log(('Execution timed out in %.1fms.'):format(dt))
@ -155,7 +154,8 @@ function M.run(cmd, stdin, timeout_ms, memory_mb)
logger.log(('Execution successful in %.1fms.'):format(dt))
end
return {
vim.schedule(function()
on_complete({
stdout = cleaned,
code = code,
time_ms = dt,
@ -163,10 +163,14 @@ function M.run(cmd, stdin, timeout_ms, memory_mb)
mled = mled,
peak_mb = peak_mb,
signal = signal,
}
})
end)
end)
end
function M.compile_problem(debug)
---@param debug boolean?
---@param on_complete fun(result: {success: boolean, output: string?})
function M.compile_problem(debug, on_complete)
local state = require('cp.state')
local config = require('cp.config').get_config()
local platform = state.get_platform()
@ -176,17 +180,20 @@ function M.compile_problem(debug)
local compile_config = (debug and eff.commands.debug) or eff.commands.build
if not compile_config then
return { success = true, output = nil }
on_complete({ success = true, output = nil })
return
end
local binary = debug and state.get_debug_file() or state.get_binary_file()
local substitutions = { source = state.get_source_file(), binary = binary }
local r = M.compile(compile_config, substitutions)
M.compile(compile_config, substitutions, function(r)
if r.code ~= 0 then
return { success = false, output = r.stdout or 'unknown error' }
on_complete({ success = false, output = r.stdout or 'unknown error' })
else
on_complete({ success = true, output = nil })
end
return { success = true, output = nil }
end)
end
return M

View file

@ -101,8 +101,8 @@ end
---@param test_case RanTestCase
---@param debug boolean?
---@return { status: "pass"|"fail"|"tle"|"mle", actual: string, actual_highlights: Highlight[], error: string, stderr: string, time_ms: number, code: integer, ok: boolean, signal: string, tled: boolean, mled: boolean, rss_mb: number }
local function run_single_test_case(test_case, debug)
---@param on_complete fun(result: { status: "pass"|"fail"|"tle"|"mle", actual: string, actual_highlights: Highlight[], error: string, stderr: string, time_ms: number, code: integer, ok: boolean, signal: string?, tled: boolean, mled: boolean, rss_mb: number })
local function run_single_test_case(test_case, debug, on_complete)
local source_file = state.get_source_file()
local binary_file = debug and state.get_debug_file() or state.get_binary_file()
@ -117,8 +117,7 @@ local function run_single_test_case(test_case, debug)
local timeout_ms = (panel_state.constraints and panel_state.constraints.timeout_ms) or 0
local memory_mb = panel_state.constraints and panel_state.constraints.memory_mb or 0
local r = execute.run(cmd, stdin_content, timeout_ms, memory_mb)
execute.run(cmd, stdin_content, timeout_ms, memory_mb, function(r)
local ansi = require('cp.ui.ansi')
local out = r.stdout or ''
local highlights = {}
@ -162,7 +161,7 @@ local function run_single_test_case(test_case, debug)
status = 'fail'
end
return {
on_complete({
status = status,
actual = out,
actual_highlights = highlights,
@ -175,7 +174,8 @@ local function run_single_test_case(test_case, debug)
tled = r.tled or false,
mled = r.mled or false,
rss_mb = r.peak_mb or 0,
}
})
end)
end
---@return boolean
@ -199,8 +199,8 @@ function M.load_test_cases()
end
---@param debug boolean?
---@return RanTestCase?
function M.run_combined_test(debug)
---@param on_complete fun(result: RanTestCase?)
function M.run_combined_test(debug, on_complete)
local combined = cache.get_combined_test(
state.get_platform() or '',
state.get_contest_id() or '',
@ -209,7 +209,8 @@ function M.run_combined_test(debug)
if not combined then
logger.log('No combined test found', vim.log.levels.ERROR)
return nil
on_complete(nil)
return
end
local ran_test = {
@ -228,22 +229,23 @@ function M.run_combined_test(debug)
selected = true,
}
local result = run_single_test_case(ran_test, debug)
return result
run_single_test_case(ran_test, debug, function(result)
on_complete(result)
end)
end
---@param index number
---@param debug boolean?
---@return boolean
function M.run_test_case(index, debug)
---@param on_complete fun(success: boolean)
function M.run_test_case(index, debug, on_complete)
local tc = panel_state.test_cases[index]
if not tc then
return false
on_complete(false)
return
end
tc.status = 'running'
local r = run_single_test_case(tc, debug)
run_single_test_case(tc, debug, function(r)
tc.status = r.status
tc.actual = r.actual
tc.actual_highlights = r.actual_highlights
@ -257,13 +259,15 @@ function M.run_test_case(index, debug)
tc.mled = r.mled
tc.rss_mb = r.rss_mb
return true
on_complete(true)
end)
end
---@param indices? integer[]
---@param debug boolean?
---@return RanTestCase[]
function M.run_all_test_cases(indices, debug)
---@param on_each? fun(index: integer, total: integer)
---@param on_done fun(results: RanTestCase[])
function M.run_all_test_cases(indices, debug, on_each, on_done)
local to_run = indices
if not to_run then
to_run = {}
@ -272,20 +276,26 @@ function M.run_all_test_cases(indices, debug)
end
end
for _, i in ipairs(to_run) do
M.run_test_case(i, debug)
end
local function run_next(pos)
if pos > #to_run then
logger.log(
('Finished %s %s test cases.'):format(
debug and 'debugging' or 'running',
#panel_state.test_cases
),
('Finished %s %d test cases.'):format(debug and 'debugging' or 'running', #to_run),
vim.log.levels.INFO,
true
)
on_done(panel_state.test_cases)
return
end
return panel_state.test_cases
M.run_test_case(to_run[pos], debug, function()
if on_each then
on_each(pos, #to_run)
end
run_next(pos + 1)
end)
end
run_next(1)
end
---@return PanelState

View file

@ -4,6 +4,10 @@
local M = {}
local function strwidth(s)
return vim.api.nvim_strwidth(s)
end
local exit_code_names = {
[128] = 'SIGHUP',
[129] = 'SIGINT',
@ -26,6 +30,12 @@ local exit_code_names = {
---@param ran_test_case RanTestCase
---@return StatusInfo
function M.get_status_info(ran_test_case)
if ran_test_case.status == 'pending' then
return { text = '...', highlight_group = 'CpTestNA' }
elseif ran_test_case.status == 'running' then
return { text = 'RUN', highlight_group = 'CpTestNA' }
end
if ran_test_case.ok then
return { text = 'AC', highlight_group = 'CpTestAC' }
end
@ -34,7 +44,7 @@ function M.get_status_info(ran_test_case)
return { text = 'TLE', highlight_group = 'CpTestTLE' }
elseif ran_test_case.mled then
return { text = 'MLE', highlight_group = 'CpTestMLE' }
elseif ran_test_case.code > 0 and ran_test_case.code >= 128 then
elseif ran_test_case.code and ran_test_case.code >= 128 then
return { text = 'RTE', highlight_group = 'CpTestRTE' }
elseif ran_test_case.code == 0 and not ran_test_case.ok then
return { text = 'WA', highlight_group = 'CpTestWA' }
@ -63,24 +73,24 @@ local function compute_cols(test_state)
for i, tc in ipairs(test_state.test_cases) do
local prefix = (i == test_state.current_index) and '>' or ' '
w.num = math.max(w.num, #(' ' .. prefix .. i .. ' '))
w.status = math.max(w.status, #(' ' .. M.get_status_info(tc).text .. ' '))
w.num = math.max(w.num, strwidth(' ' .. prefix .. i .. ' '))
w.status = math.max(w.status, strwidth(' ' .. M.get_status_info(tc).text .. ' '))
local time_str = tc.time_ms and string.format('%.2f', tc.time_ms) or ''
w.time = math.max(w.time, #(' ' .. time_str .. ' '))
w.timeout = math.max(w.timeout, #(' ' .. timeout_str .. ' '))
w.time = math.max(w.time, strwidth(' ' .. time_str .. ' '))
w.timeout = math.max(w.timeout, strwidth(' ' .. timeout_str .. ' '))
local rss_str = (tc.rss_mb and string.format('%.0f', tc.rss_mb)) or ''
w.rss = math.max(w.rss, #(' ' .. rss_str .. ' '))
w.memory = math.max(w.memory, #(' ' .. memory_str .. ' '))
w.exit = math.max(w.exit, #(' ' .. format_exit_code(tc.code) .. ' '))
w.rss = math.max(w.rss, strwidth(' ' .. rss_str .. ' '))
w.memory = math.max(w.memory, strwidth(' ' .. memory_str .. ' '))
w.exit = math.max(w.exit, strwidth(' ' .. format_exit_code(tc.code) .. ' '))
end
w.num = math.max(w.num, #' # ')
w.status = math.max(w.status, #' Status ')
w.time = math.max(w.time, #' Runtime (ms) ')
w.timeout = math.max(w.timeout, #' Time (ms) ')
w.rss = math.max(w.rss, #' RSS (MB) ')
w.memory = math.max(w.memory, #' Mem (MB) ')
w.exit = math.max(w.exit, #' Exit Code ')
w.num = math.max(w.num, strwidth(' # '))
w.status = math.max(w.status, strwidth(' Status '))
w.time = math.max(w.time, strwidth(' Runtime (ms) '))
w.timeout = math.max(w.timeout, strwidth(' Time (ms) '))
w.rss = math.max(w.rss, strwidth(' RSS (MB) '))
w.memory = math.max(w.memory, strwidth(' Mem (MB) '))
w.exit = math.max(w.exit, strwidth(' Exit Code '))
local sum = w.num + w.status + w.time + w.timeout + w.rss + w.memory + w.exit
local inner = sum + 6
@ -89,7 +99,7 @@ local function compute_cols(test_state)
end
local function center(text, width)
local pad = width - #text
local pad = width - strwidth(text)
if pad <= 0 then
return text
end
@ -101,7 +111,7 @@ local function format_num_column(prefix, idx, width)
local num_str = tostring(idx)
local content = (#num_str == 1) and (' ' .. prefix .. ' ' .. num_str .. ' ')
or (' ' .. prefix .. num_str .. ' ')
local total_pad = width - #content
local total_pad = width - strwidth(content)
if total_pad <= 0 then
return content
end
@ -314,10 +324,10 @@ function M.render_test_list(test_state)
for _, input_line in ipairs(vim.split(tc.input, '\n', { plain = true, trimempty = false })) do
local s = input_line or ''
if #s > c.inner then
if strwidth(s) > c.inner then
s = string.sub(s, 1, c.inner)
end
local pad = c.inner - #s
local pad = c.inner - strwidth(s)
table.insert(lines, '' .. s .. string.rep(' ', pad) .. '')
end

View file

@ -81,15 +81,26 @@ function M.toggle_interactive(interactor_cmd)
local execute = require('cp.runner.execute')
local run = require('cp.runner.run')
local compile_result = execute.compile_problem()
local function restore_session()
if state.saved_interactive_session then
vim.cmd.source(state.saved_interactive_session)
vim.fn.delete(state.saved_interactive_session)
state.saved_interactive_session = nil
end
end
execute.compile_problem(false, function(compile_result)
if not compile_result.success then
run.handle_compilation_failure(compile_result.output)
restore_session()
return
end
local binary = state.get_binary_file()
if not binary or binary == '' then
logger.log('No binary produced.', vim.log.levels.ERROR)
restore_session()
return
end
@ -104,14 +115,11 @@ function M.toggle_interactive(interactor_cmd)
("Interactor '%s' is not executable."):format(interactor_cmd),
vim.log.levels.ERROR
)
if state.saved_interactive_session then
vim.cmd.source(state.saved_interactive_session)
vim.fn.delete(state.saved_interactive_session)
state.saved_interactive_session = nil
end
restore_session()
return
end
local orchestrator = vim.fn.fnamemodify(utils.get_plugin_path() .. '/scripts/interact.py', ':p')
local orchestrator =
vim.fn.fnamemodify(utils.get_plugin_path() .. '/scripts/interact.py', ':p')
cmdline = table.concat({
'uv',
'run',
@ -139,11 +147,7 @@ function M.toggle_interactive(interactor_cmd)
pcall(vim.fn.jobstop, job)
end
end
if state.saved_interactive_session then
vim.cmd.source(state.saved_interactive_session)
vim.fn.delete(state.saved_interactive_session)
state.saved_interactive_session = nil
end
restore_session()
state.interactive_buf = nil
state.interactive_win = nil
state.set_active_panel(nil)
@ -189,6 +193,7 @@ function M.toggle_interactive(interactor_cmd)
state.interactive_buf = term_buf
state.interactive_win = term_win
state.set_active_panel('interactive')
end)
end
---@return integer, integer
@ -464,6 +469,158 @@ function M.ensure_io_view()
end
end
local function render_io_view_results(io_state, test_indices, mode, combined_result, combined_input)
local run = require('cp.runner.run')
local run_render = require('cp.runner.run_render')
local cfg = config_module.get_config()
run_render.setup_highlights()
local input_lines = {}
local output_lines = {}
local verdict_lines = {}
local verdict_highlights = {}
local formatter = cfg.ui.run.format_verdict
local test_state = run.get_panel_state()
if mode == 'combined' and combined_result then
input_lines = vim.split(combined_input, '\n')
if combined_result.actual and combined_result.actual ~= '' then
output_lines = vim.split(combined_result.actual, '\n')
end
local status = run_render.get_status_info(combined_result)
local format_data = {
index = 1,
status = status,
time_ms = combined_result.time_ms or 0,
time_limit_ms = test_state.constraints and test_state.constraints.timeout_ms or 0,
memory_mb = combined_result.rss_mb or 0,
memory_limit_mb = test_state.constraints and test_state.constraints.memory_mb or 0,
exit_code = combined_result.code or 0,
signal = (combined_result.code and combined_result.code >= 128)
and require('cp.constants').signal_codes[combined_result.code]
or nil,
time_actual_width = #string.format('%.2f', combined_result.time_ms or 0),
time_limit_width = #tostring(
test_state.constraints and test_state.constraints.timeout_ms or 0
),
mem_actual_width = #string.format('%.0f', combined_result.rss_mb or 0),
mem_limit_width = #string.format(
'%.0f',
test_state.constraints and test_state.constraints.memory_mb or 0
),
}
local verdict_result = formatter(format_data)
table.insert(verdict_lines, verdict_result.line)
if verdict_result.highlights then
for _, hl in ipairs(verdict_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
else
local max_time_actual, max_time_limit, max_mem_actual, max_mem_limit = 0, 0, 0, 0
for _, idx in ipairs(test_indices) do
local tc = test_state.test_cases[idx]
max_time_actual = math.max(max_time_actual, #string.format('%.2f', tc.time_ms or 0))
max_time_limit = math.max(
max_time_limit,
#tostring(test_state.constraints and test_state.constraints.timeout_ms or 0)
)
max_mem_actual = math.max(max_mem_actual, #string.format('%.0f', tc.rss_mb or 0))
max_mem_limit = math.max(
max_mem_limit,
#string.format('%.0f', test_state.constraints and test_state.constraints.memory_mb or 0)
)
end
local all_outputs = {}
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
if tc.actual then
table.insert(all_outputs, tc.actual)
end
end
local combined_output = table.concat(all_outputs, '')
if combined_output ~= '' then
for _, line in ipairs(vim.split(combined_output, '\n')) do
table.insert(output_lines, line)
end
end
for _, idx in ipairs(test_indices) do
local tc = test_state.test_cases[idx]
local status = run_render.get_status_info(tc)
local format_data = {
index = idx,
status = status,
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,
time_actual_width = max_time_actual,
time_limit_width = max_time_limit,
mem_actual_width = max_mem_actual,
mem_limit_width = max_mem_limit,
}
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
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 _, 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)
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
function M.run_io_view(test_indices_arg, debug, mode)
logger.log(('%s tests...'):format(debug and 'Debugging' or 'Running'), vim.log.levels.INFO, true)
@ -544,20 +701,25 @@ function M.run_io_view(test_indices_arg, debug, mode)
return
end
local config = config_module.get_config()
local cfg = config_module.get_config()
if config.ui.ansi then
if cfg.ui.ansi then
require('cp.ui.ansi').setup_highlight_groups()
end
local execute = require('cp.runner.execute')
local compile_result = execute.compile_problem(debug)
execute.compile_problem(debug, function(compile_result)
if not vim.api.nvim_buf_is_valid(io_state.output_buf) then
return
end
if not compile_result.success then
local ansi = require('cp.ui.ansi')
local output = compile_result.output or ''
local lines, highlights
if config.ui.ansi then
if cfg.ui.ansi then
local parsed = ansi.parse_ansi_text(output)
lines = parsed.lines
highlights = parsed.highlights
@ -571,19 +733,8 @@ function M.run_io_view(test_indices_arg, debug, mode)
return
end
local run_render = require('cp.runner.run_render')
run_render.setup_highlights()
local input_lines = {}
local output_lines = {}
local verdict_lines = {}
local verdict_highlights = {}
local formatter = config.ui.run.format_verdict
if mode == 'combined' then
local combined = cache.get_combined_test(platform, contest_id, problem_id)
if not combined then
logger.log('No combined test found', vim.log.levels.ERROR)
return
@ -591,161 +742,24 @@ function M.run_io_view(test_indices_arg, debug, mode)
run.load_test_cases()
local result = run.run_combined_test(debug)
run.run_combined_test(debug, function(result)
if not result then
logger.log('Failed to run combined test', vim.log.levels.ERROR)
return
end
input_lines = vim.split(combined.input, '\n')
if result.actual and result.actual ~= '' then
output_lines = vim.split(result.actual, '\n')
end
local status = run_render.get_status_info(result)
local test_state = run.get_panel_state()
---@type VerdictFormatData
local format_data = {
index = 1,
status = status,
time_ms = result.time_ms or 0,
time_limit_ms = test_state.constraints and test_state.constraints.timeout_ms or 0,
memory_mb = result.rss_mb or 0,
memory_limit_mb = test_state.constraints and test_state.constraints.memory_mb or 0,
exit_code = result.code or 0,
signal = (result.code and result.code >= 128)
and require('cp.constants').signal_codes[result.code]
or nil,
time_actual_width = #string.format('%.2f', result.time_ms or 0),
time_limit_width = #tostring(
test_state.constraints and test_state.constraints.timeout_ms or 0
),
mem_actual_width = #string.format('%.0f', result.rss_mb or 0),
mem_limit_width = #string.format(
'%.0f',
test_state.constraints and test_state.constraints.memory_mb or 0
),
}
local verdict_result = formatter(format_data)
table.insert(verdict_lines, verdict_result.line)
if verdict_result.highlights then
for _, hl in ipairs(verdict_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
if vim.api.nvim_buf_is_valid(io_state.output_buf) then
render_io_view_results(io_state, test_indices, mode, result, combined.input)
end
end)
else
run.run_all_test_cases(test_indices, debug)
local test_state = run.get_panel_state()
local max_time_actual = 0
local max_time_limit = 0
local max_mem_actual = 0
local max_mem_limit = 0
for _, idx in ipairs(test_indices) do
local tc = test_state.test_cases[idx]
max_time_actual = math.max(max_time_actual, #string.format('%.2f', tc.time_ms or 0))
max_time_limit = math.max(
max_time_limit,
#tostring(test_state.constraints and test_state.constraints.timeout_ms or 0)
)
max_mem_actual = math.max(max_mem_actual, #string.format('%.0f', tc.rss_mb or 0))
max_mem_limit = math.max(
max_mem_limit,
#string.format('%.0f', test_state.constraints and test_state.constraints.memory_mb or 0)
)
run.run_all_test_cases(test_indices, debug, nil, function()
if vim.api.nvim_buf_is_valid(io_state.output_buf) then
render_io_view_results(io_state, test_indices, mode, nil, nil)
end
local all_outputs = {}
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
if tc.actual then
table.insert(all_outputs, tc.actual)
end
end
local combined_output = table.concat(all_outputs, '')
if combined_output ~= '' then
for _, line in ipairs(vim.split(combined_output, '\n')) do
table.insert(output_lines, line)
end
end
for _, idx in ipairs(test_indices) do
local tc = test_state.test_cases[idx]
local status = run_render.get_status_info(tc)
---@type VerdictFormatData
local format_data = {
index = idx,
status = status,
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,
time_actual_width = max_time_actual,
time_limit_width = max_time_limit,
mem_actual_width = max_mem_actual,
mem_limit_width = max_mem_limit,
}
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
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 _, 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)
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)
end
---@param panel_opts? PanelOpts
@ -918,16 +932,15 @@ function M.toggle_panel(panel_opts)
end)
end
local execute = require('cp.runner.execute')
local compile_result = execute.compile_problem(panel_opts and panel_opts.debug)
if compile_result.success then
run.run_all_test_cases(nil, panel_opts and panel_opts.debug)
else
run.handle_compilation_failure(compile_result.output)
end
vim.api.nvim_set_current_win(test_windows.tab_win)
state.test_buffers = test_buffers
state.test_windows = test_windows
state.set_active_panel('run')
logger.log('test panel opened')
refresh_panel()
local function finalize_panel()
vim.schedule(function()
if config.ui.ansi then
require('cp.ui.ansi').setup_highlight_groups()
@ -936,12 +949,27 @@ function M.toggle_panel(panel_opts)
update_diff_panes()
end
end)
end
vim.api.nvim_set_current_win(test_windows.tab_win)
state.test_buffers = test_buffers
state.test_windows = test_windows
state.set_active_panel('run')
logger.log('test panel opened')
local execute = require('cp.runner.execute')
execute.compile_problem(panel_opts and panel_opts.debug, function(compile_result)
if not test_buffers.tab_buf or not vim.api.nvim_buf_is_valid(test_buffers.tab_buf) then
return
end
if compile_result.success then
run.run_all_test_cases(nil, panel_opts and panel_opts.debug, function()
refresh_panel()
end, function()
refresh_panel()
finalize_panel()
end)
else
run.handle_compilation_failure(compile_result.output)
refresh_panel()
finalize_panel()
end
end)
end
return M