feat: bindings and --debug flag
This commit is contained in:
parent
038fcd36f8
commit
6a6cf2c594
8 changed files with 142 additions and 46 deletions
|
|
@ -53,23 +53,30 @@ local function parse_command(args)
|
|||
else
|
||||
return { type = 'action', action = 'interact' }
|
||||
end
|
||||
elseif first == 'run' then
|
||||
local test_arg = args[2]
|
||||
if test_arg then
|
||||
local test_index = tonumber(test_arg)
|
||||
if not test_index then
|
||||
return {
|
||||
type = 'error',
|
||||
message = ("Test index '%s' is not a number"):format(test_index),
|
||||
}
|
||||
elseif first == 'run' or first == 'panel' then
|
||||
local debug = false
|
||||
local test_index = nil
|
||||
|
||||
for i = 2, #args do
|
||||
local arg = args[i]
|
||||
if arg == '--debug' then
|
||||
debug = true
|
||||
else
|
||||
local idx = tonumber(arg)
|
||||
if not idx then
|
||||
return {
|
||||
type = 'error',
|
||||
message = ("Invalid argument '%s': expected test number or --debug"):format(arg),
|
||||
}
|
||||
end
|
||||
if idx < 1 or idx ~= math.floor(idx) then
|
||||
return { type = 'error', message = ("'%s' is not a valid test index"):format(idx) }
|
||||
end
|
||||
test_index = idx
|
||||
end
|
||||
if test_index < 1 or test_index ~= math.floor(test_index) then
|
||||
return { type = 'error', message = ("'%s' is not a valid test index"):format(test_index) }
|
||||
end
|
||||
return { type = 'action', action = 'run', test_index = test_index }
|
||||
else
|
||||
return { type = 'action', action = 'run' }
|
||||
end
|
||||
|
||||
return { type = 'action', action = first, test_index = test_index, debug = debug }
|
||||
else
|
||||
return { type = 'action', action = first }
|
||||
end
|
||||
|
|
@ -127,11 +134,9 @@ function M.handle_command(opts)
|
|||
if cmd.action == 'interact' then
|
||||
ui.toggle_interactive(cmd.interactor_cmd)
|
||||
elseif cmd.action == 'run' then
|
||||
ui.run_io_view(cmd.test_index)
|
||||
ui.run_io_view(cmd.test_index, cmd.debug)
|
||||
elseif cmd.action == 'panel' then
|
||||
ui.toggle_panel()
|
||||
elseif cmd.action == 'debug' then
|
||||
ui.toggle_panel({ debug = true })
|
||||
ui.toggle_panel({ debug = cmd.debug, test_index = cmd.test_index })
|
||||
elseif cmd.action == 'next' then
|
||||
setup.navigate_problem(1)
|
||||
elseif cmd.action == 'prev' then
|
||||
|
|
|
|||
|
|
@ -34,8 +34,12 @@
|
|||
---@field setup_io_input? fun(bufnr: integer, state: cp.State)
|
||||
---@field setup_io_output? fun(bufnr: integer, state: cp.State)
|
||||
|
||||
---@class RunConfig
|
||||
---@field width number
|
||||
|
||||
---@class CpUI
|
||||
---@field ansi boolean
|
||||
---@field run RunConfig
|
||||
---@field panel PanelConfig
|
||||
---@field diff DiffConfig
|
||||
---@field picker string|nil
|
||||
|
|
@ -116,6 +120,7 @@ M.defaults = {
|
|||
filename = nil,
|
||||
ui = {
|
||||
ansi = true,
|
||||
run = { width = 0.3 },
|
||||
panel = { diff_mode = 'none', max_output_lines = 50 },
|
||||
diff = {
|
||||
git = {
|
||||
|
|
@ -157,6 +162,11 @@ local function validate_language(id, lang)
|
|||
extension = { lang.extension, 'string' },
|
||||
commands = { lang.commands, { 'table' } },
|
||||
})
|
||||
|
||||
if not lang.commands.run then
|
||||
error(('[cp.nvim] languages.%s.commands.run is required'):format(id))
|
||||
end
|
||||
|
||||
if lang.commands.build ~= nil then
|
||||
vim.validate({ build = { lang.commands.build, { 'table' } } })
|
||||
if not has_tokens(lang.commands.build, { '{source}', '{binary}' }) then
|
||||
|
|
@ -232,6 +242,14 @@ function M.setup(user_config)
|
|||
vim.validate({ user_config = { user_config, { 'table', 'nil' }, true } })
|
||||
local cfg = vim.tbl_deep_extend('force', vim.deepcopy(M.defaults), user_config or {})
|
||||
|
||||
if not next(cfg.languages) then
|
||||
error('[cp.nvim] At least one language must be configured')
|
||||
end
|
||||
|
||||
if not next(cfg.platforms) then
|
||||
error('[cp.nvim] At least one platform must be configured')
|
||||
end
|
||||
|
||||
vim.validate({
|
||||
hooks = { cfg.hooks, { 'table' } },
|
||||
ui = { cfg.ui, { 'table' } },
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
local M = {}
|
||||
|
||||
M.PLATFORMS = { 'atcoder', 'codeforces', 'cses' }
|
||||
M.ACTIONS = { 'run', 'panel', 'debug', 'next', 'prev', 'pick', 'cache', 'interact' }
|
||||
M.ACTIONS = { 'run', 'panel', 'next', 'prev', 'pick', 'cache', 'interact' }
|
||||
|
||||
M.PLATFORM_DISPLAY_NAMES = {
|
||||
atcoder = 'AtCoder',
|
||||
|
|
|
|||
|
|
@ -160,19 +160,21 @@ function M.run(cmd, stdin, timeout_ms, memory_mb)
|
|||
}
|
||||
end
|
||||
|
||||
function M.compile_problem()
|
||||
function M.compile_problem(debug)
|
||||
local state = require('cp.state')
|
||||
local config = require('cp.config').get_config()
|
||||
local platform = state.get_platform() or ''
|
||||
local platform = state.get_platform()
|
||||
local language = config.platforms[platform].default_language
|
||||
local eff = config.runtime.effective[platform][language]
|
||||
local compile_config = eff and eff.commands and eff.commands.build
|
||||
|
||||
local compile_config = (debug and eff.commands.debug) or eff.commands.build
|
||||
|
||||
if not compile_config then
|
||||
return { success = true, output = nil }
|
||||
end
|
||||
|
||||
local substitutions = { source = state.get_source_file(), binary = state.get_binary_file() }
|
||||
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)
|
||||
|
||||
if r.code ~= 0 then
|
||||
|
|
|
|||
|
|
@ -100,11 +100,12 @@ local function build_command(cmd, substitutions)
|
|||
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)
|
||||
local function run_single_test_case(test_case, debug)
|
||||
local source_file = state.get_source_file()
|
||||
|
||||
local binary_file = state.get_binary_file()
|
||||
local binary_file = debug and state.get_debug_file() or state.get_binary_file()
|
||||
local substitutions = { source = source_file, binary = binary_file }
|
||||
|
||||
local platform_config = config.platforms[state.get_platform() or '']
|
||||
|
|
@ -198,15 +199,16 @@ function M.load_test_cases()
|
|||
end
|
||||
|
||||
---@param index number
|
||||
---@param debug boolean?
|
||||
---@return boolean
|
||||
function M.run_test_case(index)
|
||||
function M.run_test_case(index, debug)
|
||||
local tc = panel_state.test_cases[index]
|
||||
if not tc then
|
||||
return false
|
||||
end
|
||||
|
||||
tc.status = 'running'
|
||||
local r = run_single_test_case(tc)
|
||||
local r = run_single_test_case(tc, debug)
|
||||
|
||||
tc.status = r.status
|
||||
tc.actual = r.actual
|
||||
|
|
@ -225,8 +227,9 @@ function M.run_test_case(index)
|
|||
end
|
||||
|
||||
---@param indices? integer[]
|
||||
---@param debug boolean?
|
||||
---@return RanTestCase[]
|
||||
function M.run_all_test_cases(indices)
|
||||
function M.run_all_test_cases(indices, debug)
|
||||
local to_run = indices
|
||||
if not to_run then
|
||||
to_run = {}
|
||||
|
|
@ -236,7 +239,7 @@ function M.run_all_test_cases(indices)
|
|||
end
|
||||
|
||||
for _, i in ipairs(to_run) do
|
||||
M.run_test_case(i)
|
||||
M.run_test_case(i, debug)
|
||||
end
|
||||
|
||||
return panel_state.test_cases
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
---@field input_buf integer
|
||||
---@field output_win integer
|
||||
---@field input_win integer
|
||||
---@field current_test_index integer?
|
||||
|
||||
---@class cp.State
|
||||
---@field get_platform fun(): string?
|
||||
|
|
@ -127,6 +128,12 @@ function M.get_binary_file()
|
|||
return base_name and ('build/%s.run'):format(base_name) or nil
|
||||
end
|
||||
|
||||
---@return string?
|
||||
function M.get_debug_file()
|
||||
local base_name = M.get_base_name()
|
||||
return base_name and ('build/%s.dbg'):format(base_name) or nil
|
||||
end
|
||||
|
||||
---@return string?
|
||||
function M.get_input_file()
|
||||
local base_name = M.get_base_name()
|
||||
|
|
|
|||
|
|
@ -228,7 +228,8 @@ function M.ensure_io_view()
|
|||
|
||||
vim.cmd.vsplit()
|
||||
output_win = vim.api.nvim_get_current_win()
|
||||
local width = math.floor(vim.o.columns * 0.3)
|
||||
local config = config_module.get_config()
|
||||
local width = math.floor(vim.o.columns * (config.ui.run.width or 0.3))
|
||||
vim.api.nvim_win_set_width(output_win, width)
|
||||
output_buf = utils.create_buffer_with_options()
|
||||
vim.api.nvim_win_set_buf(output_win, output_buf)
|
||||
|
|
@ -243,6 +244,7 @@ function M.ensure_io_view()
|
|||
input_buf = input_buf,
|
||||
output_win = output_win,
|
||||
input_win = input_win,
|
||||
current_test_index = 1,
|
||||
})
|
||||
|
||||
local config = config_module.get_config()
|
||||
|
|
@ -253,6 +255,36 @@ function M.ensure_io_view()
|
|||
if config.hooks and config.hooks.setup_io_input then
|
||||
pcall(config.hooks.setup_io_input, input_buf, state)
|
||||
end
|
||||
|
||||
local function navigate_test(delta)
|
||||
local io_state = state.get_io_view_state()
|
||||
if not io_state then
|
||||
return
|
||||
end
|
||||
local test_cases = cache.get_test_cases(platform, contest_id, problem_id)
|
||||
if not test_cases or #test_cases == 0 then
|
||||
return
|
||||
end
|
||||
local new_index = (io_state.current_test_index or 1) + delta
|
||||
if new_index < 1 or new_index > #test_cases then
|
||||
return
|
||||
end
|
||||
io_state.current_test_index = new_index
|
||||
M.run_io_view(new_index)
|
||||
end
|
||||
|
||||
vim.keymap.set('n', '<c-n>', function()
|
||||
navigate_test(1)
|
||||
end, { buffer = output_buf, silent = true, desc = 'Next test' })
|
||||
vim.keymap.set('n', '<c-p>', function()
|
||||
navigate_test(-1)
|
||||
end, { buffer = output_buf, silent = true, desc = 'Previous test' })
|
||||
vim.keymap.set('n', '<c-n>', function()
|
||||
navigate_test(1)
|
||||
end, { buffer = input_buf, silent = true, desc = 'Next test' })
|
||||
vim.keymap.set('n', '<c-p>', function()
|
||||
navigate_test(-1)
|
||||
end, { buffer = input_buf, silent = true, desc = 'Previous test' })
|
||||
end
|
||||
|
||||
utils.update_buffer_content(input_buf, {})
|
||||
|
|
@ -272,7 +304,7 @@ function M.ensure_io_view()
|
|||
vim.api.nvim_set_current_win(solution_win)
|
||||
end
|
||||
|
||||
function M.run_io_view(test_index)
|
||||
function M.run_io_view(test_index, debug)
|
||||
local platform, contest_id, problem_id =
|
||||
state.get_platform(), state.get_contest_id(), state.get_problem_id()
|
||||
if not platform or not contest_id or not problem_id then
|
||||
|
|
@ -332,7 +364,7 @@ function M.run_io_view(test_index)
|
|||
end
|
||||
|
||||
local execute = require('cp.runner.execute')
|
||||
local compile_result = execute.compile_problem()
|
||||
local compile_result = execute.compile_problem(debug)
|
||||
if not compile_result.success then
|
||||
local ansi = require('cp.ui.ansi')
|
||||
local output = compile_result.output or ''
|
||||
|
|
@ -352,7 +384,7 @@ function M.run_io_view(test_index)
|
|||
return
|
||||
end
|
||||
|
||||
run.run_all_test_cases(test_indices)
|
||||
run.run_all_test_cases(test_indices, debug)
|
||||
|
||||
local run_render = require('cp.runner.run_render')
|
||||
run_render.setup_highlights()
|
||||
|
|
@ -629,9 +661,9 @@ function M.toggle_panel(panel_opts)
|
|||
end
|
||||
|
||||
local execute = require('cp.runner.execute')
|
||||
local compile_result = execute.compile_problem()
|
||||
local compile_result = execute.compile_problem(panel_opts and panel_opts.debug)
|
||||
if compile_result.success then
|
||||
run.run_all_test_cases()
|
||||
run.run_all_test_cases(nil, panel_opts and panel_opts.debug)
|
||||
else
|
||||
run.handle_compilation_failure(compile_result.output)
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue