Merge pull request #212 from barrettruth/feat/async
make `:CP {run,panel}` asynchronous
This commit is contained in:
commit
ae7b571b68
4 changed files with 522 additions and 467 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue