commit
8cf32d5877
5 changed files with 123 additions and 110 deletions
|
|
@ -11,7 +11,7 @@ https://github.com/user-attachments/assets/cb142535-fba0-4280-8f11-66ad1ca50ca9
|
|||
## Features
|
||||
|
||||
- Support for multiple online judges ([AtCoder](https://atcoder.jp/), [Codeforces](https://codeforces.com/), [CSES](https://cses.fi))
|
||||
- Multi-language support (C++, Python)
|
||||
- Language-agnostic features
|
||||
- Automatic problem scraping and test case management
|
||||
- Integrated running and debugging
|
||||
- Enhanced test viewer
|
||||
|
|
|
|||
155
doc/cp.txt
155
doc/cp.txt
|
|
@ -72,61 +72,29 @@ Here's an example configuration with lazy.nvim: >
|
|||
'barrett-ruth/cp.nvim',
|
||||
cmd = 'CP',
|
||||
opts = {
|
||||
debug = false,
|
||||
scrapers = {
|
||||
atcoder = true,
|
||||
codeforces = false,
|
||||
cses = true,
|
||||
},
|
||||
contests = {
|
||||
codeforces = {
|
||||
cpp = {
|
||||
compile = {
|
||||
'g++', '-std=c++{version}', '-O2', '-Wall', '-Wextra',
|
||||
'-DLOCAL', '{source}', '-o', '{binary}',
|
||||
},
|
||||
test = { '{binary}' },
|
||||
debug = {
|
||||
'g++', '-std=c++{version}', '-g3',
|
||||
'-fsanitize=address,undefined', '-DLOCAL',
|
||||
'{source}', '-o', '{binary}',
|
||||
},
|
||||
version = 23,
|
||||
extension = "cc",
|
||||
},
|
||||
python = {
|
||||
test = { 'python3', '{source}' },
|
||||
debug = { 'python3', '{source}' },
|
||||
extension = "py",
|
||||
},
|
||||
default_language = "cpp",
|
||||
},
|
||||
},
|
||||
contests = {},
|
||||
snippets = {},
|
||||
hooks = {
|
||||
before_run = function(ctx) vim.cmd.w() end,
|
||||
before_debug = function(ctx) ... end,
|
||||
setup_code = function(ctx)
|
||||
vim.wo.foldmethod = "marker"
|
||||
vim.wo.foldmarker = "{{{,}}}"
|
||||
vim.diagnostic.enable(false)
|
||||
end,
|
||||
before_run = nil,
|
||||
before_debug = nil,
|
||||
setup_code = nil,
|
||||
},
|
||||
debug = false,
|
||||
scrapers = { atcoder = true, codeforces = true, cses = true },
|
||||
filename = nil,
|
||||
run_panel = {
|
||||
diff_mode = "vim",
|
||||
next_test_key = "<c-n>",
|
||||
prev_test_key = "<c-p>",
|
||||
toggle_diff_key = "t",
|
||||
diff_mode = 'vim',
|
||||
next_test_key = '<c-n>',
|
||||
prev_test_key = '<c-p>',
|
||||
toggle_diff_key = '<c-t>',
|
||||
max_output_lines = 50,
|
||||
},
|
||||
diff = {
|
||||
git = {
|
||||
command = "git",
|
||||
args = {"diff", "--no-index", "--word-diff=plain",
|
||||
"--word-diff-regex=.", "--no-prefix"},
|
||||
command = 'git',
|
||||
args = { 'diff', '--no-index', '--word-diff=plain', '--word-diff-regex=.', '--no-prefix' },
|
||||
},
|
||||
},
|
||||
snippets = { ... }, -- LuaSnip snippets
|
||||
filename = function(contest, contest_id, problem_id, config, language) ... end,
|
||||
}
|
||||
}
|
||||
<
|
||||
|
|
@ -134,16 +102,16 @@ Here's an example configuration with lazy.nvim: >
|
|||
*cp.Config*
|
||||
|
||||
Fields: ~
|
||||
• {contests} (`table<string,ContestConfig>`) Contest configurations.
|
||||
• {hooks} (`cp.Hooks`) Hook functions called at various stages.
|
||||
• {snippets} (`table[]`) LuaSnip snippet definitions.
|
||||
• {debug} (`boolean`, default: `false`) Show info messages
|
||||
- {contests} (`table<string,ContestConfig>`) Contest configurations.
|
||||
- {hooks} (`cp.Hooks`) Hook functions called at various stages.
|
||||
- {snippets} (`table[]`) LuaSnip snippet definitions.
|
||||
- {debug} (`boolean`, default: `false`) Show info messages
|
||||
during operation.
|
||||
• {scrapers} (`table<string,boolean>`) Per-platform scraper control.
|
||||
- {scrapers} (`table<string,boolean>`) Per-platform scraper control.
|
||||
Default enables all platforms.
|
||||
• {run_panel} (`RunPanelConfig`) Test panel behavior configuration.
|
||||
• {diff} (`DiffConfig`) Diff backend configuration.
|
||||
• {filename}? (`function`) Custom filename generation function.
|
||||
- {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)`
|
||||
Should return full filename with extension.
|
||||
(default: concats contest_id and problem id)
|
||||
|
|
@ -151,60 +119,60 @@ Here's an example configuration with lazy.nvim: >
|
|||
*cp.ContestConfig*
|
||||
|
||||
Fields: ~
|
||||
• {cpp} (`LanguageConfig`) C++ language configuration.
|
||||
• {python} (`LanguageConfig`) Python language configuration.
|
||||
• {default_language} (`string`, default: `"cpp"`) Default language when
|
||||
- {cpp} (`LanguageConfig`) C++ language configuration.
|
||||
- {python} (`LanguageConfig`) Python language configuration.
|
||||
- {default_language} (`string`, default: `"cpp"`) Default language when
|
||||
`--lang` not specified.
|
||||
|
||||
*cp.LanguageConfig*
|
||||
|
||||
Fields: ~
|
||||
• {compile}? (`string[]`) Compile command template with
|
||||
- {compile}? (`string[]`) Compile command template with
|
||||
`{version}`, `{source}`, `{binary}` placeholders.
|
||||
• {test} (`string[]`) Test execution command template.
|
||||
• {debug}? (`string[]`) Debug compile command template.
|
||||
• {version}? (`number`) Language version (e.g. 20, 23 for C++).
|
||||
• {extension} (`string`) File extension (e.g. "cc", "py").
|
||||
• {executable}? (`string`) Executable name for interpreted languages.
|
||||
- {test} (`string[]`) Test execution command template.
|
||||
- {debug}? (`string[]`) Debug compile command template.
|
||||
- {version}? (`number`) Language version (e.g. 20, 23 for C++).
|
||||
- {extension} (`string`) File extension (e.g. "cc", "py").
|
||||
- {executable}? (`string`) Executable name for interpreted languages.
|
||||
|
||||
*cp.RunPanelConfig*
|
||||
|
||||
Fields: ~
|
||||
• {diff_mode} (`string`, default: `"vim"`) Diff backend: "vim" or "git".
|
||||
- {diff_mode} (`string`, default: `"vim"`) Diff backend: "vim" or "git".
|
||||
Git provides character-level precision, vim uses built-in diff.
|
||||
• {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.
|
||||
• {toggle_diff_key} (`string`, default: `"t"`) Key to toggle diff mode between vim and git.
|
||||
• {max_output_lines} (`number`, default: `50`) Maximum lines of test output to display.
|
||||
- {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.
|
||||
- {toggle_diff_key} (`string`, default: `"<c-t>"`) Key to toggle diff mode between vim and git.
|
||||
- {max_output_lines} (`number`, default: `50`) Maximum lines of test output to display.
|
||||
|
||||
*cp.DiffConfig*
|
||||
|
||||
Fields: ~
|
||||
• {git} (`DiffGitConfig`) Git diff backend configuration.
|
||||
- {git} (`DiffGitConfig`) Git diff backend configuration.
|
||||
|
||||
*cp.Hooks*
|
||||
|
||||
Fields: ~
|
||||
• {before_run}? (`function`) Called before test panel opens.
|
||||
- {before_run}? (`function`) Called before test panel opens.
|
||||
`function(ctx: ProblemContext)`
|
||||
• {before_debug}? (`function`) Called before debug compilation.
|
||||
- {before_debug}? (`function`) Called before debug compilation.
|
||||
`function(ctx: ProblemContext)`
|
||||
• {setup_code}? (`function`) Called after source file is opened.
|
||||
- {setup_code}? (`function`) Called after source file is opened.
|
||||
Good for configuring buffer settings.
|
||||
`function(ctx: ProblemContext)`
|
||||
|
||||
*ProblemContext*
|
||||
|
||||
Fields: ~
|
||||
• {contest} (`string`) Platform name (e.g. "atcoder", "codeforces")
|
||||
• {contest_id} (`string`) Contest ID (e.g. "abc123", "1933")
|
||||
• {problem_id}? (`string`) Problem ID (e.g. "a", "b") - nil for CSES
|
||||
• {source_file} (`string`) Source filename (e.g. "abc123a.cpp")
|
||||
• {binary_file} (`string`) Binary output path (e.g. "build/abc123a.run")
|
||||
• {input_file} (`string`) Test input path (e.g. "io/abc123a.cpin")
|
||||
• {output_file} (`string`) Program output path (e.g. "io/abc123a.cpout")
|
||||
• {expected_file} (`string`) Expected output path (e.g. "io/abc123a.expected")
|
||||
• {problem_name} (`string`) Display name (e.g. "abc123a")
|
||||
- {contest} (`string`) Platform name (e.g. "atcoder", "codeforces")
|
||||
- {contest_id} (`string`) Contest ID (e.g. "abc123", "1933")
|
||||
- {problem_id}? (`string`) Problem ID (e.g. "a", "b") - nil for CSES
|
||||
- {source_file} (`string`) Source filename (e.g. "abc123a.cpp")
|
||||
- {binary_file} (`string`) Binary output path (e.g. "build/abc123a.run")
|
||||
- {input_file} (`string`) Test input path (e.g. "io/abc123a.cpin")
|
||||
- {output_file} (`string`) Program output path (e.g. "io/abc123a.cpout")
|
||||
- {expected_file} (`string`) Expected output path (e.g. "io/abc123a.expected")
|
||||
- {problem_name} (`string`) Display name (e.g. "abc123a")
|
||||
|
||||
WORKFLOW *cp-workflow*
|
||||
|
||||
|
|
@ -337,13 +305,28 @@ characters below) >
|
|||
|
||||
Status Indicators ~
|
||||
|
||||
Test cases use competitive programming terminology:
|
||||
Test cases use competitive programming terminology with color highlighting:
|
||||
|
||||
AC Accepted (passed)
|
||||
WA Wrong Answer (output mismatch)
|
||||
TLE Time Limit Exceeded (timeout)
|
||||
RTE Runtime Error (non-zero exit)
|
||||
AC Accepted (passed) - Green
|
||||
WA Wrong Answer (output mismatch) - Red
|
||||
TLE Time Limit Exceeded (timeout) - Orange
|
||||
RTE Runtime Error (non-zero exit) - Purple
|
||||
|
||||
Highlight Groups ~
|
||||
*cp-highlights*
|
||||
cp.nvim defines the following highlight groups for status indicators:
|
||||
|
||||
CpTestAC Green foreground for AC status
|
||||
CpTestWA Red foreground for WA status
|
||||
CpTestTLE Orange foreground for TLE status
|
||||
CpTestRTE Purple foreground for RTE status
|
||||
CpTestPending Gray foreground for pending tests
|
||||
|
||||
You can customize these colors by linking to other highlight groups in your
|
||||
colorscheme or by redefining them: >
|
||||
vim.api.nvim_set_hl(0, 'CpTestAC', { link = 'DiffAdd' })
|
||||
vim.api.nvim_set_hl(0, 'CpTestWA', { fg = '#ff0000' })
|
||||
<
|
||||
Keymaps ~
|
||||
*cp-test-keys*
|
||||
<c-n> Navigate to next test case (configurable via run_panel.next_test_key)
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ local function toggle_run_panel(is_debug)
|
|||
vim.api.nvim_buf_set_extmark(bufnr, test_list_namespace, hl.line, hl.col_start, {
|
||||
end_col = hl.col_end,
|
||||
hl_group = hl.highlight_group,
|
||||
priority = 100,
|
||||
priority = 200,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -30,14 +30,14 @@ function M.get_status_info(test_case)
|
|||
return { text = 'AC', highlight_group = 'CpTestAC' }
|
||||
elseif test_case.status == 'fail' then
|
||||
if test_case.timed_out then
|
||||
return { text = 'TLE', highlight_group = 'CpTestError' }
|
||||
return { text = 'TLE', highlight_group = 'CpTestTLE' }
|
||||
elseif test_case.code and test_case.code >= 128 then
|
||||
return { text = 'RTE', highlight_group = 'CpTestError' }
|
||||
return { text = 'RTE', highlight_group = 'CpTestRTE' }
|
||||
else
|
||||
return { text = 'WA', highlight_group = 'CpTestError' }
|
||||
return { text = 'WA', highlight_group = 'CpTestWA' }
|
||||
end
|
||||
elseif test_case.status == 'timeout' then
|
||||
return { text = 'TLE', highlight_group = 'CpTestError' }
|
||||
return { text = 'TLE', highlight_group = 'CpTestTLE' }
|
||||
elseif test_case.status == 'running' then
|
||||
return { text = '...', highlight_group = 'CpTestPending' }
|
||||
else
|
||||
|
|
@ -264,15 +264,14 @@ local function data_row(c, idx, tc, is_current, test_state)
|
|||
|
||||
local hi
|
||||
if status.text ~= '' then
|
||||
local content = ' ' .. status.text .. ' '
|
||||
local pad = w.status - #content
|
||||
local status_start_col = 1 + w.num + 1 + pad + 1
|
||||
local status_end_col = status_start_col + #status.text
|
||||
hi = {
|
||||
col_start = status_start_col,
|
||||
col_end = status_end_col,
|
||||
highlight_group = status.highlight_group,
|
||||
}
|
||||
local status_pos = line:find(status.text)
|
||||
if status_pos then
|
||||
hi = {
|
||||
col_start = status_pos - 1,
|
||||
col_end = status_pos - 1 + #status.text,
|
||||
highlight_group = status.highlight_group,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return line, hi
|
||||
|
|
@ -352,8 +351,10 @@ end
|
|||
---@return table<string, table>
|
||||
function M.get_highlight_groups()
|
||||
return {
|
||||
CpTestAC = { fg = '#10b981', bold = true },
|
||||
CpTestError = { fg = '#ef4444', bold = true },
|
||||
CpTestAC = { fg = '#10b981' },
|
||||
CpTestWA = { fg = '#ef4444' },
|
||||
CpTestTLE = { fg = '#f59e0b' },
|
||||
CpTestRTE = { fg = '#8b5cf6' },
|
||||
CpTestPending = { fg = '#6b7280' },
|
||||
CpDiffRemoved = { fg = '#ef4444', bg = '#1f1f1f' },
|
||||
CpDiffAdded = { fg = '#10b981', bg = '#1f1f1f' },
|
||||
|
|
|
|||
|
|
@ -13,28 +13,28 @@ describe('cp.test_render', function()
|
|||
local test_case = { status = 'fail', code = 1 }
|
||||
local result = test_render.get_status_info(test_case)
|
||||
assert.equals('WA', result.text)
|
||||
assert.equals('CpTestError', result.highlight_group)
|
||||
assert.equals('CpTestWA', result.highlight_group)
|
||||
end)
|
||||
|
||||
it('returns TLE for timeout status', function()
|
||||
local test_case = { status = 'timeout' }
|
||||
local result = test_render.get_status_info(test_case)
|
||||
assert.equals('TLE', result.text)
|
||||
assert.equals('CpTestError', result.highlight_group)
|
||||
assert.equals('CpTestTLE', result.highlight_group)
|
||||
end)
|
||||
|
||||
it('returns TLE for timed out fail status', function()
|
||||
local test_case = { status = 'fail', timed_out = true }
|
||||
local result = test_render.get_status_info(test_case)
|
||||
assert.equals('TLE', result.text)
|
||||
assert.equals('CpTestError', result.highlight_group)
|
||||
assert.equals('CpTestTLE', result.highlight_group)
|
||||
end)
|
||||
|
||||
it('returns RTE for fail with signal codes (>= 128)', function()
|
||||
local test_case = { status = 'fail', code = 139 }
|
||||
local result = test_render.get_status_info(test_case)
|
||||
assert.equals('RTE', result.text)
|
||||
assert.equals('CpTestError', result.highlight_group)
|
||||
assert.equals('CpTestRTE', result.highlight_group)
|
||||
end)
|
||||
|
||||
it('returns empty for pending status', function()
|
||||
|
|
@ -159,11 +159,40 @@ describe('cp.test_render', function()
|
|||
local mock_set_hl = spy.on(vim.api, 'nvim_set_hl')
|
||||
test_render.setup_highlights()
|
||||
|
||||
assert.spy(mock_set_hl).was_called(5)
|
||||
assert.spy(mock_set_hl).was_called_with(0, 'CpTestAC', { fg = '#10b981', bold = true })
|
||||
assert.spy(mock_set_hl).was_called_with(0, 'CpTestError', { fg = '#ef4444', bold = true })
|
||||
assert.spy(mock_set_hl).was_called(7)
|
||||
assert.spy(mock_set_hl).was_called_with(0, 'CpTestAC', { fg = '#10b981' })
|
||||
assert.spy(mock_set_hl).was_called_with(0, 'CpTestWA', { fg = '#ef4444' })
|
||||
assert.spy(mock_set_hl).was_called_with(0, 'CpTestTLE', { fg = '#f59e0b' })
|
||||
assert.spy(mock_set_hl).was_called_with(0, 'CpTestRTE', { fg = '#8b5cf6' })
|
||||
|
||||
mock_set_hl:revert()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('highlight positioning', function()
|
||||
it('generates correct highlight positions for status text', function()
|
||||
local test_state = {
|
||||
test_cases = {
|
||||
{ status = 'pass', input = '' },
|
||||
{ status = 'fail', code = 1, input = '' },
|
||||
},
|
||||
current_index = 1,
|
||||
}
|
||||
local lines, highlights = test_render.render_test_list(test_state)
|
||||
|
||||
assert.equals(2, #highlights)
|
||||
|
||||
for _, hl in ipairs(highlights) do
|
||||
assert.is_not_nil(hl.line)
|
||||
assert.is_not_nil(hl.col_start)
|
||||
assert.is_not_nil(hl.col_end)
|
||||
assert.is_not_nil(hl.highlight_group)
|
||||
assert.is_true(hl.col_end > hl.col_start)
|
||||
|
||||
local line_content = lines[hl.line + 1]
|
||||
local highlighted_text = line_content:sub(hl.col_start + 1, hl.col_end)
|
||||
assert.is_true(highlighted_text == 'AC' or highlighted_text == 'WA')
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue