feat: :CP test -> :CP run
This commit is contained in:
parent
ef3d39c7f4
commit
dd6bf47684
7 changed files with 64 additions and 90 deletions
25
doc/cp.txt
25
doc/cp.txt
|
|
@ -49,7 +49,7 @@ Setup Commands ~
|
|||
|
||||
Action Commands ~
|
||||
|
||||
:CP test [--debug] Toggle test panel for individual test case
|
||||
:CP run [--debug] Toggle run panel for individual test case
|
||||
debugging. Shows per-test results with redesigned
|
||||
layout for efficient comparison.
|
||||
Use --debug flag to compile with debug flags
|
||||
|
|
@ -112,9 +112,8 @@ Optional configuration with lazy.nvim: >
|
|||
vim.diagnostic.enable(false)
|
||||
end,
|
||||
},
|
||||
test_panel = {
|
||||
run_panel = {
|
||||
diff_mode = "vim", -- "vim" or "git"
|
||||
toggle_key = "t", -- toggle test panel
|
||||
next_test_key = "<c-n>", -- navigate to next test case
|
||||
prev_test_key = "<c-p>", -- navigate to previous test case
|
||||
},
|
||||
|
|
@ -141,7 +140,7 @@ Optional configuration with lazy.nvim: >
|
|||
during operation.
|
||||
• {scrapers} (`table<string,boolean>`) Per-platform scraper control.
|
||||
Default enables all platforms.
|
||||
• {test_panel} (`TestPanelConfig`) Test panel behavior configuration.
|
||||
• {run_panel} (`RunPanelConfig`) Test panel behavior configuration.
|
||||
• {diff} (`DiffConfig`) Diff backend configuration.
|
||||
• {filename}? (`function`) Custom filename generation function.
|
||||
`function(contest, contest_id, problem_id, config, language)`
|
||||
|
|
@ -169,12 +168,11 @@ Optional configuration with lazy.nvim: >
|
|||
• {extension} (`string`) File extension (e.g. "cc", "py").
|
||||
• {executable}? (`string`) Executable name for interpreted languages.
|
||||
|
||||
*cp.TestPanelConfig*
|
||||
*cp.RunPanelConfig*
|
||||
|
||||
Fields: ~
|
||||
• {diff_mode} (`string`, default: `"vim"`) Diff backend: "vim" or "git".
|
||||
Git provides character-level precision, vim uses built-in diff.
|
||||
• {toggle_key} (`string`, default: `"t"`) Key to toggle test panel.
|
||||
• {next_test_key} (`string`, default: `"<c-n>"`) Key to navigate to next test case.
|
||||
• {prev_test_key} (`string`, default: `"<c-p>"`) Key to navigate to previous test case.
|
||||
|
||||
|
|
@ -295,15 +293,15 @@ Example: Quick setup for single Codeforces problem >
|
|||
:CP test " Test immediately
|
||||
<
|
||||
|
||||
TEST PANEL *cp-test*
|
||||
RUN PANEL *cp-run*
|
||||
|
||||
The test panel provides individual test case debugging with a streamlined
|
||||
The run panel provides individual test case debugging with a streamlined
|
||||
layout optimized for modern screens. Shows test status with competitive
|
||||
programming terminology and efficient space usage.
|
||||
|
||||
Activation ~
|
||||
*:CP-test*
|
||||
:CP test [--debug] Toggle test panel on/off. When activated,
|
||||
*:CP-run*
|
||||
:CP run [--debug] Toggle run panel on/off. When activated,
|
||||
replaces current layout with test interface.
|
||||
Automatically compiles and runs all tests.
|
||||
Use --debug flag to compile with debug symbols
|
||||
|
|
@ -312,7 +310,7 @@ Activation ~
|
|||
|
||||
Interface ~
|
||||
|
||||
The test panel uses a redesigned two-pane layout for efficient comparison:
|
||||
The run panel uses a redesigned two-pane layout for efficient comparison:
|
||||
(note that the diff is indeed highlighted, not the weird amalgamation of
|
||||
characters below) >
|
||||
|
||||
|
|
@ -338,10 +336,9 @@ Test cases use competitive programming terminology:
|
|||
|
||||
Keymaps ~
|
||||
*cp-test-keys*
|
||||
<c-n> Navigate to next test case (configurable via test_panel.next_test_key)
|
||||
<c-p> Navigate to previous test case (configurable via test_panel.prev_test_key)
|
||||
<c-n> Navigate to next test case (configurable via run_panel.next_test_key)
|
||||
<c-p> Navigate to previous test case (configurable via run_panel.prev_test_key)
|
||||
q Exit test panel (restore layout)
|
||||
t Toggle test panel (configurable via test_panel.toggle_key)
|
||||
|
||||
Diff Modes ~
|
||||
|
||||
|
|
|
|||
|
|
@ -31,9 +31,8 @@
|
|||
---@field before_debug? fun(ctx: ProblemContext)
|
||||
---@field setup_code? fun(ctx: ProblemContext)
|
||||
|
||||
---@class TestPanelConfig
|
||||
---@class RunPanelConfig
|
||||
---@field diff_mode "vim"|"git" Diff backend to use
|
||||
---@field toggle_key string Key to toggle test panel
|
||||
---@field next_test_key string Key to navigate to next test case
|
||||
---@field prev_test_key string Key to navigate to previous test case
|
||||
|
||||
|
|
@ -51,7 +50,7 @@
|
|||
---@field debug boolean
|
||||
---@field scrapers table<string, boolean>
|
||||
---@field filename? fun(contest: string, contest_id: string, problem_id?: string, config: cp.Config, language?: string): string
|
||||
---@field test_panel TestPanelConfig
|
||||
---@field run_panel RunPanelConfig
|
||||
---@field diff DiffConfig
|
||||
|
||||
---@class cp.UserConfig
|
||||
|
|
@ -61,7 +60,7 @@
|
|||
---@field debug? boolean
|
||||
---@field scrapers? table<string, boolean>
|
||||
---@field filename? fun(contest: string, contest_id: string, problem_id?: string, config: cp.Config, language?: string): string
|
||||
---@field test_panel? TestPanelConfig
|
||||
---@field run_panel? RunPanelConfig
|
||||
---@field diff? DiffConfig
|
||||
|
||||
local M = {}
|
||||
|
|
@ -79,9 +78,8 @@ M.defaults = {
|
|||
debug = false,
|
||||
scrapers = constants.PLATFORMS,
|
||||
filename = nil,
|
||||
test_panel = {
|
||||
run_panel = {
|
||||
diff_mode = 'vim',
|
||||
toggle_key = 't',
|
||||
next_test_key = '<c-n>',
|
||||
prev_test_key = '<c-p>',
|
||||
},
|
||||
|
|
@ -108,7 +106,7 @@ function M.setup(user_config)
|
|||
debug = { user_config.debug, { 'boolean', 'nil' }, true },
|
||||
scrapers = { user_config.scrapers, { 'table', 'nil' }, true },
|
||||
filename = { user_config.filename, { 'function', 'nil' }, true },
|
||||
test_panel = { user_config.test_panel, { 'table', 'nil' }, true },
|
||||
run_panel = { user_config.run_panel, { 'table', 'nil' }, true },
|
||||
diff = { user_config.diff, { 'table', 'nil' }, true },
|
||||
})
|
||||
|
||||
|
|
@ -132,31 +130,24 @@ function M.setup(user_config)
|
|||
})
|
||||
end
|
||||
|
||||
if user_config.test_panel then
|
||||
if user_config.run_panel then
|
||||
vim.validate({
|
||||
diff_mode = {
|
||||
user_config.test_panel.diff_mode,
|
||||
user_config.run_panel.diff_mode,
|
||||
function(value)
|
||||
return vim.tbl_contains({ 'vim', 'git' }, value)
|
||||
end,
|
||||
"diff_mode must be 'vim' or 'git'",
|
||||
},
|
||||
toggle_key = {
|
||||
user_config.test_panel.toggle_key,
|
||||
function(value)
|
||||
return type(value) == 'string' and value ~= ''
|
||||
end,
|
||||
'toggle_key must be a non-empty string',
|
||||
},
|
||||
next_test_key = {
|
||||
user_config.test_panel.next_test_key,
|
||||
user_config.run_panel.next_test_key,
|
||||
function(value)
|
||||
return type(value) == 'string' and value ~= ''
|
||||
end,
|
||||
'next_test_key must be a non-empty string',
|
||||
},
|
||||
prev_test_key = {
|
||||
user_config.test_panel.prev_test_key,
|
||||
user_config.run_panel.prev_test_key,
|
||||
function(value)
|
||||
return type(value) == 'string' and value ~= ''
|
||||
end,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ local state = {
|
|||
saved_session = nil,
|
||||
test_cases = nil,
|
||||
test_states = {},
|
||||
test_panel_active = false,
|
||||
run_panel_active = false,
|
||||
}
|
||||
|
||||
local constants = require('cp.constants')
|
||||
|
|
@ -149,14 +149,14 @@ local function get_current_problem()
|
|||
return filename
|
||||
end
|
||||
|
||||
local function toggle_test_panel(is_debug)
|
||||
if state.test_panel_active then
|
||||
local function toggle_run_panel(is_debug)
|
||||
if state.run_panel_active then
|
||||
if state.saved_session then
|
||||
vim.cmd(('source %s'):format(state.saved_session))
|
||||
vim.fn.delete(state.saved_session)
|
||||
state.saved_session = nil
|
||||
end
|
||||
state.test_panel_active = false
|
||||
state.run_panel_active = false
|
||||
logger.log('test panel closed')
|
||||
return
|
||||
end
|
||||
|
|
@ -249,7 +249,7 @@ local function toggle_test_panel(is_debug)
|
|||
end
|
||||
|
||||
local function update_expected_pane()
|
||||
local test_state = test_module.get_test_panel_state()
|
||||
local test_state = test_module.get_run_panel_state()
|
||||
local current_test = test_state.test_cases[test_state.current_index]
|
||||
|
||||
if not current_test then
|
||||
|
|
@ -262,7 +262,7 @@ local function toggle_test_panel(is_debug)
|
|||
update_buffer_content(test_buffers.expected_buf, expected_lines, {})
|
||||
|
||||
local diff_backend = require('cp.diff')
|
||||
local backend = diff_backend.get_best_backend(config.test_panel.diff_mode)
|
||||
local backend = diff_backend.get_best_backend(config.run_panel.diff_mode)
|
||||
|
||||
if backend.name == 'vim' and current_test.status == 'fail' then
|
||||
vim.api.nvim_set_option_value('diff', true, { win = test_windows.expected_win })
|
||||
|
|
@ -272,7 +272,7 @@ local function toggle_test_panel(is_debug)
|
|||
end
|
||||
|
||||
local function update_actual_pane()
|
||||
local test_state = test_module.get_test_panel_state()
|
||||
local test_state = test_module.get_run_panel_state()
|
||||
local current_test = test_state.test_cases[test_state.current_index]
|
||||
|
||||
if not current_test then
|
||||
|
|
@ -291,7 +291,7 @@ local function toggle_test_panel(is_debug)
|
|||
|
||||
if enable_diff then
|
||||
local diff_backend = require('cp.diff')
|
||||
local backend = diff_backend.get_best_backend(config.test_panel.diff_mode)
|
||||
local backend = diff_backend.get_best_backend(config.run_panel.diff_mode)
|
||||
|
||||
if backend.name == 'git' then
|
||||
local diff_result = backend.render(current_test.expected, current_test.actual)
|
||||
|
|
@ -321,14 +321,14 @@ local function toggle_test_panel(is_debug)
|
|||
end
|
||||
end
|
||||
|
||||
local function refresh_test_panel()
|
||||
local function refresh_run_panel()
|
||||
if not test_buffers.tab_buf or not vim.api.nvim_buf_is_valid(test_buffers.tab_buf) then
|
||||
return
|
||||
end
|
||||
|
||||
local test_render = require('cp.test_render')
|
||||
test_render.setup_highlights()
|
||||
local test_state = test_module.get_test_panel_state()
|
||||
local test_state = test_module.get_run_panel_state()
|
||||
local tab_lines, tab_highlights = test_render.render_test_list(test_state)
|
||||
update_buffer_content(test_buffers.tab_buf, tab_lines, tab_highlights)
|
||||
|
||||
|
|
@ -337,7 +337,7 @@ local function toggle_test_panel(is_debug)
|
|||
end
|
||||
|
||||
local function navigate_test_case(delta)
|
||||
local test_state = test_module.get_test_panel_state()
|
||||
local test_state = test_module.get_run_panel_state()
|
||||
if #test_state.test_cases == 0 then
|
||||
return
|
||||
end
|
||||
|
|
@ -349,22 +349,19 @@ local function toggle_test_panel(is_debug)
|
|||
test_state.current_index = 1
|
||||
end
|
||||
|
||||
refresh_test_panel()
|
||||
refresh_run_panel()
|
||||
end
|
||||
|
||||
vim.keymap.set('n', config.test_panel.next_test_key, function()
|
||||
vim.keymap.set('n', config.run_panel.next_test_key, function()
|
||||
navigate_test_case(1)
|
||||
end, { buffer = test_buffers.tab_buf, silent = true })
|
||||
vim.keymap.set('n', config.test_panel.prev_test_key, function()
|
||||
vim.keymap.set('n', config.run_panel.prev_test_key, function()
|
||||
navigate_test_case(-1)
|
||||
end, { buffer = test_buffers.tab_buf, silent = true })
|
||||
|
||||
for _, buf in pairs(test_buffers) do
|
||||
vim.keymap.set('n', 'q', function()
|
||||
toggle_test_panel()
|
||||
end, { buffer = buf, silent = true })
|
||||
vim.keymap.set('n', config.test_panel.toggle_key, function()
|
||||
toggle_test_panel()
|
||||
toggle_run_panel()
|
||||
end, { buffer = buf, silent = true })
|
||||
end
|
||||
|
||||
|
|
@ -382,14 +379,14 @@ local function toggle_test_panel(is_debug)
|
|||
test_module.run_all_test_cases(ctx, contest_config)
|
||||
end
|
||||
|
||||
refresh_test_panel()
|
||||
refresh_run_panel()
|
||||
|
||||
vim.api.nvim_set_current_win(test_windows.tab_win)
|
||||
|
||||
state.test_panel_active = true
|
||||
state.run_panel_active = true
|
||||
state.test_buffers = test_buffers
|
||||
state.test_windows = test_windows
|
||||
local test_state = test_module.get_test_panel_state()
|
||||
local test_state = test_module.get_run_panel_state()
|
||||
logger.log(string.format('test panel opened (%d test cases)', #test_state.test_cases))
|
||||
end
|
||||
|
||||
|
|
@ -556,8 +553,8 @@ function M.handle_command(opts)
|
|||
end
|
||||
|
||||
if cmd.type == 'action' then
|
||||
if cmd.action == 'test' then
|
||||
toggle_test_panel(cmd.debug)
|
||||
if cmd.action == 'run' then
|
||||
toggle_run_panel(cmd.debug)
|
||||
elseif cmd.action == 'next' then
|
||||
navigate_problem(1, cmd.language)
|
||||
elseif cmd.action == 'prev' then
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
---@field signal string?
|
||||
---@field timed_out boolean?
|
||||
|
||||
---@class TestPanelState
|
||||
---@class RunPanelState
|
||||
---@field test_cases TestCase[]
|
||||
---@field current_index number
|
||||
---@field buffer number?
|
||||
|
|
@ -24,8 +24,8 @@ local M = {}
|
|||
local constants = require('cp.constants')
|
||||
local logger = require('cp.log')
|
||||
|
||||
---@type TestPanelState
|
||||
local test_panel_state = {
|
||||
---@type RunPanelState
|
||||
local run_panel_state = {
|
||||
test_cases = {},
|
||||
current_index = 1,
|
||||
buffer = nil,
|
||||
|
|
@ -227,8 +227,8 @@ function M.load_test_cases(ctx, state)
|
|||
test_cases = parse_test_cases_from_files(ctx.input_file, ctx.expected_file)
|
||||
end
|
||||
|
||||
test_panel_state.test_cases = test_cases
|
||||
test_panel_state.current_index = 1
|
||||
run_panel_state.test_cases = test_cases
|
||||
run_panel_state.current_index = 1
|
||||
|
||||
logger.log(('loaded %d test case(s)'):format(#test_cases))
|
||||
return #test_cases > 0
|
||||
|
|
@ -239,7 +239,7 @@ end
|
|||
---@param index number
|
||||
---@return boolean
|
||||
function M.run_test_case(ctx, contest_config, index)
|
||||
local test_case = test_panel_state.test_cases[index]
|
||||
local test_case = run_panel_state.test_cases[index]
|
||||
if not test_case then
|
||||
return false
|
||||
end
|
||||
|
|
@ -266,16 +266,16 @@ end
|
|||
---@return TestCase[]
|
||||
function M.run_all_test_cases(ctx, contest_config)
|
||||
local results = {}
|
||||
for i, _ in ipairs(test_panel_state.test_cases) do
|
||||
for i, _ in ipairs(run_panel_state.test_cases) do
|
||||
M.run_test_case(ctx, contest_config, i)
|
||||
table.insert(results, test_panel_state.test_cases[i])
|
||||
table.insert(results, run_panel_state.test_cases[i])
|
||||
end
|
||||
return results
|
||||
end
|
||||
|
||||
---@return TestPanelState
|
||||
function M.get_test_panel_state()
|
||||
return test_panel_state
|
||||
---@return RunPanelState
|
||||
function M.get_run_panel_state()
|
||||
return run_panel_state
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ local function data_row(c, idx, tc, is_current)
|
|||
return line, hi
|
||||
end
|
||||
|
||||
---@param test_state TestPanelState
|
||||
---@param test_state RunPanelState
|
||||
---@return string[], table[] lines and highlight positions
|
||||
function M.render_test_list(test_state)
|
||||
local lines, highlights = {}, {}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ describe('cp command parsing', function()
|
|||
|
||||
describe('action commands', function()
|
||||
it('handles test action without error', function()
|
||||
local opts = { fargs = { 'test' } }
|
||||
local opts = { fargs = { 'run' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
|
|
@ -126,7 +126,7 @@ describe('cp command parsing', function()
|
|||
|
||||
describe('language flag parsing', function()
|
||||
it('logs error for --lang flag missing value', function()
|
||||
local opts = { fargs = { 'test', '--lang' } }
|
||||
local opts = { fargs = { 'run', '--lang' } }
|
||||
|
||||
cp.handle_command(opts)
|
||||
|
||||
|
|
@ -169,7 +169,7 @@ describe('cp command parsing', function()
|
|||
|
||||
describe('debug flag parsing', function()
|
||||
it('handles debug flag without error', function()
|
||||
local opts = { fargs = { 'test', '--debug' } }
|
||||
local opts = { fargs = { 'run', '--debug' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
|
|
@ -177,7 +177,7 @@ describe('cp command parsing', function()
|
|||
end)
|
||||
|
||||
it('handles combined language and debug flags', function()
|
||||
local opts = { fargs = { 'test', '--lang=cpp', '--debug' } }
|
||||
local opts = { fargs = { 'run', '--lang=cpp', '--debug' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
|
|
@ -234,7 +234,7 @@ describe('cp command parsing', function()
|
|||
end)
|
||||
|
||||
it('handles flag order variations', function()
|
||||
local opts = { fargs = { '--debug', 'test', '--lang=python' } }
|
||||
local opts = { fargs = { '--debug', 'run', '--lang=python' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
|
|
@ -242,7 +242,7 @@ describe('cp command parsing', function()
|
|||
end)
|
||||
|
||||
it('handles multiple language flags', function()
|
||||
local opts = { fargs = { 'test', '--lang=cpp', '--lang=python' } }
|
||||
local opts = { fargs = { 'run', '--lang=cpp', '--lang=python' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
|
|
|
|||
|
|
@ -74,20 +74,10 @@ describe('cp.config', function()
|
|||
end)
|
||||
end)
|
||||
|
||||
describe('test_panel config validation', function()
|
||||
describe('run_panel config validation', function()
|
||||
it('validates diff_mode values', function()
|
||||
local invalid_config = {
|
||||
test_panel = { diff_mode = 'invalid' },
|
||||
}
|
||||
|
||||
assert.has_error(function()
|
||||
config.setup(invalid_config)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('validates toggle_key is non-empty string', function()
|
||||
local invalid_config = {
|
||||
test_panel = { toggle_key = '' },
|
||||
run_panel = { diff_mode = 'invalid' },
|
||||
}
|
||||
|
||||
assert.has_error(function()
|
||||
|
|
@ -97,7 +87,7 @@ describe('cp.config', function()
|
|||
|
||||
it('validates next_test_key is non-empty string', function()
|
||||
local invalid_config = {
|
||||
test_panel = { next_test_key = nil },
|
||||
run_panel = { next_test_key = nil },
|
||||
}
|
||||
|
||||
assert.has_error(function()
|
||||
|
|
@ -107,7 +97,7 @@ describe('cp.config', function()
|
|||
|
||||
it('validates prev_test_key is non-empty string', function()
|
||||
local invalid_config = {
|
||||
test_panel = { prev_test_key = '' },
|
||||
run_panel = { prev_test_key = '' },
|
||||
}
|
||||
|
||||
assert.has_error(function()
|
||||
|
|
@ -115,11 +105,10 @@ describe('cp.config', function()
|
|||
end)
|
||||
end)
|
||||
|
||||
it('accepts valid test_panel config', function()
|
||||
it('accepts valid run_panel config', function()
|
||||
local valid_config = {
|
||||
test_panel = {
|
||||
run_panel = {
|
||||
diff_mode = 'git',
|
||||
toggle_key = 'x',
|
||||
next_test_key = 'j',
|
||||
prev_test_key = 'k',
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue