Merge pull request #146 from barrett-ruth/feat/cli-enhancements

fixups
This commit is contained in:
Barrett Ruth 2025-10-05 19:01:35 +02:00 committed by GitHub
commit 5e9c00014d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 185 additions and 438 deletions

View file

@ -10,15 +10,13 @@ https://github.com/user-attachments/assets/50b19481-8e6d-47b4-bebc-15e16c61a9c9
- **Multi-platform support**: AtCoder, Codeforces, CSES with consistent interface
- **Automatic problem setup**: Scrape test cases and metadata in seconds
- **Rich test output**: ANSI color support for compiler errors and program output
- **Language agnostic**: Works with any compiled language
- **Template integration**: Contest-specific snippets via LuaSnip
- **Diff viewer**: Compare expected vs actual output with precision
- **Rich test output**: 256 color ANSI support for compiler errors and program output
- **Language agnostic**: Works with any language
- **Diff viewer**: Compare expected vs actual output with 3 diff modes
## Optional Dependencies
- [uv](https://docs.astral.sh/uv/) for problem scraping
- [LuaSnip](https://github.com/L3MON4D3/LuaSnip) for templates
- GNU [time](https://www.gnu.org/software/time/) and [timeout](https://www.gnu.org/software/coreutils/manual/html_node/timeout-invocation.html)
## Quick Start

View file

@ -4,3 +4,4 @@ vim.opt_local.statuscolumn = ''
vim.opt_local.signcolumn = 'no'
vim.opt_local.wrap = true
vim.opt_local.linebreak = true
vim.opt_local.foldcolumn = '0'

View file

@ -1,6 +0,0 @@
vim.opt_local.number = false
vim.opt_local.relativenumber = false
vim.opt_local.statuscolumn = ''
vim.opt_local.signcolumn = 'no'
vim.opt_local.wrap = true
vim.opt_local.linebreak = true

View file

@ -1,7 +0,0 @@
vim.opt_local.number = false
vim.opt_local.relativenumber = false
vim.opt_local.statuscolumn = ''
vim.opt_local.signcolumn = 'no'
vim.opt_local.wrap = true
vim.opt_local.linebreak = true
vim.opt_local.modifiable = true

View file

@ -1,7 +0,0 @@
vim.opt_local.number = false
vim.opt_local.relativenumber = false
vim.opt_local.statuscolumn = ''
vim.opt_local.signcolumn = 'no'
vim.opt_local.wrap = true
vim.opt_local.linebreak = true
vim.opt_local.foldcolumn = '0'

View file

@ -1,17 +0,0 @@
if exists("b:current_syntax")
finish
endif
syntax match cpOutputCode /^\[code\]:/
syntax match cpOutputTime /^\[time\]:/
syntax match cpOutputDebug /^\[debug\]:/
syntax match cpOutputOkTrue /^\[ok\]:\ze true$/
syntax match cpOutputOkFalse /^\[ok\]:\ze false$/
highlight default link cpOutputCode DiagnosticInfo
highlight default link cpOutputTime Comment
highlight default link cpOutputDebug Comment
highlight default link cpOutputOkTrue DiffAdd
highlight default link cpOutputOkFalse DiffDelete
let b:current_syntax = "cp"

View file

@ -16,10 +16,7 @@ REQUIREMENTS *cp-requirements*
- Neovim 0.10.0+
- Unix-like operating system
Optional:
- uv package manager (https://docs.astral.sh/uv/)
- LuaSnip for template expansion (https://github.com/L3MON4D3/LuaSnip)
==============================================================================
COMMANDS *cp-commands*
@ -51,11 +48,13 @@ COMMANDS *cp-commands*
:CP codeforces 1951
<
Action Commands ~
:CP run [--debug] Toggle run panel for individual test case
debugging. Shows per-test results with redesigned
:CP run Toggle run panel for individual test cases.
Shows per-test results with redesigned
layout for efficient comparison.
Use --debug flag to compile with debug flags.
Requires contest setup first.
:CP debug
Same as above but with the debug mode configured
settings.
:CP pick Launch configured picker for interactive
platform/contest selection.
@ -65,10 +64,12 @@ COMMANDS *cp-commands*
Stops at last problem (no wrapping).
Navigation Commands ~
:CP prev Navigate to previous problem in current contest.
Stops at first problem (no wrapping).
:CP {problem_id} Jump to problem {problem_id} in a contest.
Requires that a contest has already been set up.
Cache Commands ~
:CP cache clear [contest]
Clear the cache data (contest list, problem
@ -79,20 +80,6 @@ COMMANDS *cp-commands*
View the cache in a pretty-printed lua buffer.
Exit with q.
Command Flags ~
*cp-flags*
Flags can be used with setup and action commands:
--debug Use the debug command template.
For compiled languages, this selects
`commands.debug` (a debug *build*) instead of
`commands.build`. For interpreted languages,
this selects `commands.debug` in place of
`commands.run`.
Example: >
:CP run --debug
<
Template Variables ~
*cp-template-vars*
Command templates support variable substitution using `{variable}` syntax:
@ -136,7 +123,6 @@ Here's an example configuration with lazy.nvim: >lua
},
},
},
platforms = {
cses = {
enabled_languages = { 'cpp', 'python' },
@ -154,10 +140,7 @@ Here's an example configuration with lazy.nvim: >lua
default_language = 'cpp',
},
},
snippets = {},
debug = false,
ui = {
run_panel = {
ansi = true,
@ -223,7 +206,6 @@ run CSES problems with Rust using the single schema:
{platforms} (table<string,|CpPlatform|>) Per-platform enablement,
default language, and optional overrides.
{hooks} (|cp.Hooks|) Hook functions called at various stages.
{snippets} (table[]) LuaSnip snippet definitions.
{debug} (boolean, default: false) Show info messages.
{scrapers} (string[]) Supported platform ids.
{filename} (function, optional)
@ -359,22 +341,18 @@ Example: Setting up and solving AtCoder contest ABC324
< Navigate with j/k, run specific tests with <enter>
Exit test panel with q or :CP run when done
5. If needed, debug with sanitizers: >
:CP run --debug
<
6. Move to next problem: >
5. Move to next problem: >
:CP next
< This automatically sets up problem B
7. Continue solving problems with :CP next/:CP prev navigation
6. Continue solving problems with :CP next/:CP prev navigation
8. Switch to another file (e.g. previous contest): >
7. Switch to another file (e.g. previous contest): >
:e ~/contests/abc323/a.cpp
:CP
< Automatically restores abc323 contest context
9. Submit solutions on AtCoder website
8. Submit solutions on AtCoder website
==============================================================================
PICKER INTEGRATION *cp-picker*
@ -396,18 +374,9 @@ PICKER KEYMAPS *cp-picker-keys*
==============================================================================
RUN PANEL *cp-run*
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-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
and sanitizers. Toggle again to restore original
layout.
The run panel provides individual test case debugging. Problem time/memory
limit constraints are in columns Time/Mem respectively. Used time/memory are
in columns Runtime/RSS respectively.
Interface ~
@ -441,7 +410,7 @@ Test cases use competitive programming terminology with color highlighting:
TLE Time Limit Exceeded (timeout)
MLE Memory Limit Exceeded Error (heuristic)
RTE Runtime Error (other non-zero exit code)
NA Any other state (undecipherable, error, running)
NA Any other state
<
==============================================================================
@ -450,13 +419,28 @@ ANSI COLORS AND HIGHLIGHTING *cp-ansi*
cp.nvim provides comprehensive ANSI color support and highlighting for
compiler output, program stderr, and diff visualization.
If you cannot see color highlighting in your config, it is likely due to an
erroneous config. Most tools (GCC, Python, Clang, Rustc) color stdout based on
whether stdout is connected to a terminal. One can usually get aorund this by
leveraging flags to force colored output. For example, to force colors with GCC,
alter your config as follows:
{
commands = {
build = {
'g++',
'-fdiagnostics-color=always',
...
}
}
}
==============================================================================
HIGHLIGHT GROUPS *cp-highlights*
Test Status Groups ~
Test cases use competitive programming terminology with color highlighting:
CpTestAC Green foreground for AC status
CpTestWA Red foreground for WA status
CpTestTLE Orange foreground for TLE status
@ -470,56 +454,21 @@ cp.nvim preserves ANSI colors from compiler output and program stderr using
a sophisticated parsing system. Colors are automatically mapped to your
terminal colorscheme via vim.g.terminal_color_* variables.
Basic formatting groups:
CpAnsiBold Bold text formatting
CpAnsiItalic Italic text formatting
CpAnsiBoldItalic Combined bold and italic formatting
Standard terminal colors (each supports Bold, Italic, BoldItalic variants):
CpAnsiRed Standard red (terminal_color_1)
CpAnsiGreen Standard green (terminal_color_2)
CpAnsiYellow Standard yellow (terminal_color_3)
CpAnsiBlue Standard blue (terminal_color_4)
CpAnsiMagenta Standard magenta (terminal_color_5)
CpAnsiCyan Standard cyan (terminal_color_6)
CpAnsiWhite Standard white (terminal_color_7)
CpAnsiBlack Standard black (terminal_color_0)
Bright color variants:
CpAnsiBrightRed Bright red (terminal_color_9)
CpAnsiBrightGreen Bright green (terminal_color_10)
CpAnsiBrightYellow Bright yellow (terminal_color_11)
CpAnsiBrightBlue Bright blue (terminal_color_12)
CpAnsiBrightMagenta Bright magenta (terminal_color_13)
CpAnsiBrightCyan Bright cyan (terminal_color_14)
CpAnsiBrightWhite Bright white (terminal_color_15)
CpAnsiBrightBlack Bright black (terminal_color_8)
Example combinations:
CpAnsiBoldRed Bold red combination
CpAnsiItalicGreen Italic green combination
CpAnsiBoldItalicYellow Bold italic yellow combination
Diff Highlighting ~
Diff visualization uses Neovim's built-in highlight groups that automatically
The git diff backend uses Neovim's built-in highlight groups that automatically
adapt to your colorscheme:
DiffAdd Highlights added text in git diffs
DiffDelete Highlights removed text in git diffs
These groups are automatically used by the git diff backend for character-level
difference visualization with optimal colorscheme integration.
==============================================================================
TERMINAL COLOR INTEGRATION *cp-terminal-colors*
ANSI colors automatically use your terminal's color palette through Neovim's
vim.g.terminal_color_* variables. This ensures compiler colors match your
colorscheme without manual configuration.
ANSI colors automatically use the terminal's color palette through Neovim's
vim.g.terminal_color_* variables.
If your colorscheme doesn't set terminal colors, cp.nvim will warn you and
ANSI colors won't display properly - set them like so: >vim
If your colorscheme doesn't set terminal colors, set them like so: >vim
let g:terminal_color_1 = '#ff6b6b'
...
@ -550,6 +499,7 @@ prevent them from being overridden: >lua
==============================================================================
RUN PANEL KEYMAPS *cp-test-keys*
<c-n> Navigate to next test case
<c-p> Navigate to previous test case
t Cycle through diff modes: none → git → vim
@ -558,27 +508,27 @@ q Exit run panel and restore layout
Diff Modes ~
Two diff backends are available:
Three diff backends are available:
none Nothing
vim Built-in vim diff (default, always available)
git Character-level git word-diff (requires git, more precise)
The git backend shows character-level changes with [-removed-] and {+added+}
markers for precise difference analysis.
markers.
Execution Details ~
Test cases are executed individually using the same compilation and
execution pipeline, but with isolated input/output for
precise failure analysis. All tests are automatically run when the
panel opens.
precise failure analysis.
==============================================================================
FILE STRUCTURE *cp-files*
cp.nvim creates the following file structure upon problem setup: >
{problem_id}.{ext} " Source file (e.g. a.cc, b.py)
{problem_id}.{ext} " Source file
build/
{problem_id}.run " Compiled binary
io/
@ -586,27 +536,6 @@ cp.nvim creates the following file structure upon problem setup: >
{problem_id}.n.cpout " nth program output
{problem_id}.expected " Expected output
<
==============================================================================
SNIPPETS *cp-snippets*
cp.nvim integrates with LuaSnip for automatic template expansion. Built-in
snippets include basic C++ and Python templates for each contest type.
Snippet trigger names must match the following format exactly: >
cp.nvim/{platform}.{language}
<
Where {platform} is the contest platform (atcoder, codeforces, cses) and
{language} is the programming language (cpp, python).
Examples: >
cp.nvim/atcoder.cpp
cp.nvim/codeforces.python
cp.nvim/cses.cpp
<
Custom snippets can be added via the `snippets` configuration field.
==============================================================================
HEALTH CHECK *cp-health*

View file

@ -1,6 +0,0 @@
vim.filetype.add({
extension = {
cpin = 'cpin',
cpout = 'cpout',
},
})

View file

@ -10,11 +10,11 @@ local actions = constants.ACTIONS
---@class ParsedCommand
---@field type string
---@field error string?
---@field debug? boolean
---@field action? string
---@field message? string
---@field contest? string
---@field platform? string
---@field problem_id? string
--- Turn raw args into normalized structure to later dispatch
---@param args string[] The raw command-line mode args
@ -26,22 +26,16 @@ local function parse_command(args)
}
end
local debug = vim.tbl_contains(args, '--debug')
local filtered_args = vim.tbl_filter(function(arg)
return arg ~= '--debug'
end, args)
local first = filtered_args[1]
local first = args[1]
if vim.tbl_contains(actions, first) then
if first == 'cache' then
local subcommand = filtered_args[2]
local subcommand = args[2]
if not subcommand then
return { type = 'error', message = 'cache command requires subcommand: clear' }
end
if vim.tbl_contains({ 'clear', 'read' }, subcommand) then
local platform = filtered_args[3]
local platform = args[3]
return {
type = 'cache',
subcommand = subcommand,
@ -51,42 +45,40 @@ local function parse_command(args)
return { type = 'error', message = 'unknown cache subcommand: ' .. subcommand }
end
else
return { type = 'action', action = first, debug = debug }
return { type = 'action', action = first }
end
end
if vim.tbl_contains(platforms, first) then
if #filtered_args == 1 then
if #args == 1 then
return {
type = 'error',
message = 'Too few arguments - specify a contest.',
}
elseif #filtered_args == 2 then
elseif #args == 2 then
return {
type = 'contest_setup',
platform = first,
contest = filtered_args[2],
contest = args[2],
}
elseif #filtered_args == 3 then
elseif #args == 3 then
return {
type = 'error',
message = 'Setup contests with :CP <platform> <contest_id>',
message = 'Setup contests with :CP <platform> <contest_id>.',
}
else
return { type = 'error', message = 'Too many arguments' }
end
end
if state.get_platform() and state.get_contest_id() then
local cache = require('cp.cache')
cache.load()
if #args == 1 then
return {
type = 'error',
message = ("invalid subcommand '%s'"):format(first),
type = 'problem_jump',
problem_id = first,
}
end
return { type = 'error', message = 'Unknown command or no contest context' }
return { type = 'error', message = 'Unknown command or no contest context.' }
end
--- Core logic for handling `:CP ...` commands
@ -109,7 +101,9 @@ function M.handle_command(opts)
if cmd.action == 'interact' then
ui.toggle_interactive()
elseif cmd.action == 'run' then
ui.toggle_run_panel(cmd.debug)
ui.toggle_run_panel()
elseif cmd.action == 'debug' then
ui.toggle_run_panel({ debug = true })
elseif cmd.action == 'next' then
setup.navigate_problem(1)
elseif cmd.action == 'prev' then
@ -118,6 +112,30 @@ function M.handle_command(opts)
local picker = require('cp.commands.picker')
picker.handle_pick_action()
end
elseif cmd.type == 'problem_jump' then
local platform = state.get_platform()
local contest_id = state.get_contest_id()
local problem_id = cmd.problem_id
if not (platform and contest_id) then
logger.log('No contest is currently active.', vim.log.levels.ERROR)
return
end
local cache = require('cp.cache')
cache.load()
local contest_data = cache.get_contest_data(platform, contest_id)
if not (contest_data and contest_data.index_map and contest_data.index_map[problem_id]) then
logger.log(
("%s contest '%s' has no problem '%s'."):format(platform, contest_id, problem_id),
vim.log.levels.ERROR
)
return
end
local setup = require('cp.setup')
setup.setup_contest(platform, contest_id, problem_id)
elseif cmd.type == 'cache' then
local cache_commands = require('cp.commands.cache')
cache_commands.handle_cache_command(cmd)

View file

@ -42,7 +42,6 @@
---@field languages table<string, CpLanguage>
---@field platforms table<string, CpPlatform>
---@field hooks Hooks
---@field snippets any[]
---@field debug boolean
---@field scrapers string[]
---@field filename? fun(contest: string, contest_id: string, problem_id?: string, config: cp.Config, language?: string): string
@ -100,7 +99,6 @@ M.defaults = {
default_language = 'cpp',
},
},
snippets = {},
hooks = { before_run = nil, before_debug = nil, setup_code = nil },
debug = false,
scrapers = constants.PLATFORMS,

View file

@ -2,7 +2,7 @@ local M = {}
local utils = require('cp.utils')
local function check_required()
local function check()
vim.health.start('cp.nvim [required] ~')
if vim.fn.has('nvim-0.10.0') == 1 then
@ -49,24 +49,12 @@ local function check_required()
end
end
local function check_optional()
vim.health.start('cp.nvim [optional] ~')
local has_luasnip = pcall(require, 'luasnip')
if has_luasnip then
vim.health.ok('LuaSnip integration available')
else
vim.health.info('LuaSnip not available (templates optional)')
end
end
function M.check()
local version = require('cp.version')
vim.health.start('cp.nvim health check ~')
vim.health.info('Version: ' .. version.version)
check_required()
check_optional()
check()
end
return M

View file

@ -2,7 +2,6 @@ local M = {}
local config_module = require('cp.config')
local logger = require('cp.log')
local snippets = require('cp.snippets')
if vim.fn.has('nvim-0.10.0') == 0 then
logger.log('Requires nvim-0.10.0+', vim.log.levels.ERROR)
@ -11,7 +10,6 @@ end
local user_config = {}
local config = nil
local snippets_initialized = false
local initialized = false
--- Root handler for all `:CP ...` commands
@ -27,10 +25,6 @@ function M.setup(opts)
config = config_module.setup(user_config)
config_module.set_current_config(config)
if not snippets_initialized then
snippets.setup(config)
snippets_initialized = true
end
initialized = true
end

View file

@ -107,26 +107,6 @@ function M.setup_problem(problem_id, language)
local lang = language or config.platforms[platform].default_language
local source_file = state.get_source_file(lang)
vim.cmd.e(source_file)
local source_buf = vim.api.nvim_get_current_buf()
if vim.api.nvim_buf_get_lines(source_buf, 0, -1, true)[1] == '' then
local ok, luasnip = pcall(require, 'luasnip')
if ok then
local trigger = ('cp.nvim/%s.%s'):format(platform, lang)
vim.api.nvim_buf_set_lines(0, 0, -1, false, { trigger })
vim.api.nvim_win_set_cursor(0, { 1, #trigger })
vim.cmd.startinsert({ bang = true })
vim.schedule(function()
if luasnip.expandable() then
luasnip.expand()
else
vim.api.nvim_buf_set_lines(0, 0, 1, false, { '' })
vim.api.nvim_win_set_cursor(0, { 1, 0 })
end
vim.cmd.stopinsert()
end)
end
end
if config.hooks and config.hooks.setup_code then
config.hooks.setup_code(state)

View file

@ -1,134 +0,0 @@
local M = {}
local logger = require('cp.log')
function M.setup(config)
local ok, ls = pcall(require, 'luasnip')
if not ok then
logger.log('LuaSnip not available - snippets are disabled.', vim.log.levels.INFO, true)
return
end
local s, i, fmt = ls.snippet, ls.insert_node, require('luasnip.extras.fmt').fmt
local constants = require('cp.constants')
local filetype_to_language = constants.filetype_to_language
local language_to_filetype = {}
for ext, lang in pairs(filetype_to_language) do
if not language_to_filetype[lang] then
language_to_filetype[lang] = ext
end
end
local template_definitions = {
cpp = {
codeforces = [[#include <bits/stdc++.h>
using namespace std;
void solve() {{
{}
}}
int main() {{
std::cin.tie(nullptr)->sync_with_stdio(false);
int tc = 1;
std::cin >> tc;
for (int t = 0; t < tc; ++t) {{
solve();
}}
return 0;
}}]],
atcoder = [[#include <bits/stdc++.h>
using namespace std;
void solve() {{
{}
}}
int main() {{
std::cin.tie(nullptr)->sync_with_stdio(false);
#ifdef LOCAL
int tc;
std::cin >> tc;
for (int t = 0; t < tc; ++t) {{
solve();
}}
#else
solve();
#endif
return 0;
}}]],
cses = [[#include <bits/stdc++.h>
using namespace std;
int main() {{
std::cin.tie(nullptr)->sync_with_stdio(false);
{}
return 0;
}}]],
},
python = {
codeforces = [[def solve():
{}
if __name__ == "__main__":
tc = int(input())
for _ in range(tc):
solve()]],
atcoder = [[def solve():
{}
if __name__ == "__main__":
solve()]],
cses = [[def solve():
{}
if __name__ == "__main__":
solve()]],
},
}
local user_overrides = {}
for _, snippet in ipairs(config.snippets or {}) do
user_overrides[snippet.trigger:lower()] = snippet
end
for language, template_set in pairs(template_definitions) do
local snippets = {}
local filetype = constants.canonical_filetypes[language]
for contest, template in pairs(template_set) do
local prefixed_trigger = ('cp.nvim/%s.%s'):format(contest:lower(), language)
if not user_overrides[prefixed_trigger:lower()] then
table.insert(snippets, s(prefixed_trigger, fmt(template, { i(1) })))
end
end
for trigger, snippet in pairs(user_overrides) do
local prefix_match = trigger:lower():match('^cp%.nvim/[^.]+%.(.+)$')
if prefix_match == language then
table.insert(snippets, snippet)
end
end
ls.add_snippets(filetype, snippets)
end
end
return M

View file

@ -65,6 +65,10 @@ function M.get_base_name()
end
end
function M.get_language()
return
end
function M.get_source_file(language)
local base_name = M.get_base_name()
if not base_name or not M.get_platform() then

View file

@ -10,10 +10,46 @@
local M = {}
local logger = require('cp.log')
local dyn_hl_cache = {}
local ANSI_TERMINAL_COLOR_CODE_FALLBACK = {
[0] = '#000000',
[1] = '#800000',
[2] = '#008000',
[3] = '#808000',
[4] = '#000080',
[5] = '#800080',
[6] = '#008080',
[7] = '#c0c0c0',
[8] = '#808080',
[9] = '#ff0000',
[10] = '#00ff00',
[11] = '#ffff00',
[12] = '#0000ff',
[13] = '#ff00ff',
[14] = '#00ffff',
[15] = '#ffffff',
}
local function xterm_to_hex(n)
if n >= 0 and n <= 15 then
local key = 'terminal_color_' .. n
return vim.g[key] or ANSI_TERMINAL_COLOR_CODE_FALLBACK[n]
end
if n >= 16 and n <= 231 then
local c = n - 16
local r = math.floor(c / 36) % 6
local g = math.floor(c / 6) % 6
local b = c % 6
local function level(x)
return x == 0 and 0 or 55 + 40 * x
end
return ('#%02x%02x%02x'):format(level(r), level(g), level(b))
end
local l = 8 + 10 * (n - 232)
return ('#%02x%02x%02x'):format(l, l, l)
end
---@param s string|table
---@return string
function M.bytes_to_string(s)
@ -40,24 +76,7 @@ local function ensure_hl_for(fg, bold, italic)
suffix = fg.name
elseif fg and fg.kind == 'xterm' then
suffix = ('X%03d'):format(fg.idx)
local function xterm_to_hex(n)
if n >= 0 and n <= 15 then
local key = 'terminal_color_' .. n
return vim.g[key]
end
if n >= 16 and n <= 231 then
local c = n - 16
local r = math.floor(c / 36) % 6
local g = math.floor(c / 6) % 6
local b = c % 6
local function level(x)
return x == 0 and 0 or 55 + 40 * x
end
return ('#%02x%02x%02x'):format(level(r), level(g), level(b))
end
local l = 8 + 10 * (n - 232)
return ('#%02x%02x%02x'):format(l, l, l)
end
opts.fg = xterm_to_hex(fg.idx) or 'NONE'
elseif fg and fg.kind == 'rgb' then
suffix = ('Rgb%02x%02x%02x'):format(fg.r, fg.g, fg.b)
@ -256,31 +275,24 @@ end
---@return nil
function M.setup_highlight_groups()
local color_map = {
Black = vim.g.terminal_color_0,
Red = vim.g.terminal_color_1,
Green = vim.g.terminal_color_2,
Yellow = vim.g.terminal_color_3,
Blue = vim.g.terminal_color_4,
Magenta = vim.g.terminal_color_5,
Cyan = vim.g.terminal_color_6,
White = vim.g.terminal_color_7,
BrightBlack = vim.g.terminal_color_8,
BrightRed = vim.g.terminal_color_9,
BrightGreen = vim.g.terminal_color_10,
BrightYellow = vim.g.terminal_color_11,
BrightBlue = vim.g.terminal_color_12,
BrightMagenta = vim.g.terminal_color_13,
BrightCyan = vim.g.terminal_color_14,
BrightWhite = vim.g.terminal_color_15,
Black = vim.g.terminal_color_0 or ANSI_TERMINAL_COLOR_CODE_FALLBACK[0],
Red = vim.g.terminal_color_1 or ANSI_TERMINAL_COLOR_CODE_FALLBACK[1],
Green = vim.g.terminal_color_2 or ANSI_TERMINAL_COLOR_CODE_FALLBACK[2],
Yellow = vim.g.terminal_color_3 or ANSI_TERMINAL_COLOR_CODE_FALLBACK[3],
Blue = vim.g.terminal_color_4 or ANSI_TERMINAL_COLOR_CODE_FALLBACK[4],
Magenta = vim.g.terminal_color_5 or ANSI_TERMINAL_COLOR_CODE_FALLBACK[5],
Cyan = vim.g.terminal_color_6 or ANSI_TERMINAL_COLOR_CODE_FALLBACK[6],
White = vim.g.terminal_color_7 or ANSI_TERMINAL_COLOR_CODE_FALLBACK[7],
BrightBlack = vim.g.terminal_color_8 or ANSI_TERMINAL_COLOR_CODE_FALLBACK[8],
BrightRed = vim.g.terminal_color_9 or ANSI_TERMINAL_COLOR_CODE_FALLBACK[9],
BrightGreen = vim.g.terminal_color_10 or ANSI_TERMINAL_COLOR_CODE_FALLBACK[10],
BrightYellow = vim.g.terminal_color_11 or ANSI_TERMINAL_COLOR_CODE_FALLBACK[11],
BrightBlue = vim.g.terminal_color_12 or ANSI_TERMINAL_COLOR_CODE_FALLBACK[12],
BrightMagenta = vim.g.terminal_color_13 or ANSI_TERMINAL_COLOR_CODE_FALLBACK[13],
BrightCyan = vim.g.terminal_color_14 or ANSI_TERMINAL_COLOR_CODE_FALLBACK[14],
BrightWhite = vim.g.terminal_color_15 or ANSI_TERMINAL_COLOR_CODE_FALLBACK[15],
}
if vim.tbl_count(color_map) < 16 then
logger.log(
'ansi terminal colors (vim.g.terminal_color_*) not configured. ANSI colors will not display properly.',
vim.log.levels.WARN
)
end
local combinations = {
{ bold = false, italic = false },
{ bold = true, italic = false },

View file

@ -16,8 +16,8 @@ local function create_none_diff_layout(parent_win, expected_content, actual_cont
local expected_win = vim.api.nvim_get_current_win()
vim.api.nvim_win_set_buf(expected_win, expected_buf)
vim.api.nvim_set_option_value('filetype', 'cptest', { buf = expected_buf })
vim.api.nvim_set_option_value('filetype', 'cptest', { buf = actual_buf })
vim.api.nvim_set_option_value('filetype', 'cp', { buf = expected_buf })
vim.api.nvim_set_option_value('filetype', 'cp', { buf = actual_buf })
vim.api.nvim_set_option_value('winbar', 'Expected', { win = expected_win })
vim.api.nvim_set_option_value('winbar', 'Actual', { win = actual_win })
@ -53,8 +53,8 @@ local function create_vim_diff_layout(parent_win, expected_content, actual_conte
local expected_win = vim.api.nvim_get_current_win()
vim.api.nvim_win_set_buf(expected_win, expected_buf)
vim.api.nvim_set_option_value('filetype', 'cptest', { buf = expected_buf })
vim.api.nvim_set_option_value('filetype', 'cptest', { buf = actual_buf })
vim.api.nvim_set_option_value('filetype', 'cp', { buf = expected_buf })
vim.api.nvim_set_option_value('filetype', 'cp', { buf = actual_buf })
vim.api.nvim_set_option_value('winbar', 'Expected', { win = expected_win })
vim.api.nvim_set_option_value('winbar', 'Actual', { win = actual_win })
@ -96,7 +96,7 @@ local function create_git_diff_layout(parent_win, expected_content, actual_conte
local diff_win = vim.api.nvim_get_current_win()
vim.api.nvim_win_set_buf(diff_win, diff_buf)
vim.api.nvim_set_option_value('filetype', 'cptest', { buf = diff_buf })
vim.api.nvim_set_option_value('filetype', 'cp', { buf = diff_buf })
vim.api.nvim_set_option_value('winbar', 'Expected vs Actual', { win = diff_win })
local diff_backend = require('cp.ui.diff')
@ -132,7 +132,7 @@ local function create_single_layout(parent_win, content)
vim.cmd('resize ' .. math.floor(vim.o.lines * 0.35))
local win = vim.api.nvim_get_current_win()
vim.api.nvim_win_set_buf(win, buf)
vim.api.nvim_set_option_value('filetype', 'cptest', { buf = buf })
vim.api.nvim_set_option_value('filetype', 'cp', { buf = buf })
return {
buffers = { buf },

View file

@ -1,5 +1,8 @@
local M = {}
---@class RunOpts
---@field debug? boolean
local config_module = require('cp.config')
local layouts = require('cp.ui.layouts')
local logger = require('cp.log')
@ -51,19 +54,13 @@ function M.toggle_interactive()
local platform, contest_id = state.get_platform(), state.get_contest_id()
if not platform then
logger.log(
'No platform configured. Use :CP <platform> <contest> [--{lang=<lang>,debug}] first.',
vim.log.levels.ERROR
)
logger.log('No platform configured.', vim.log.levels.ERROR)
return
end
if not contest_id then
logger.log(
('No contest %s configured for platform %s. Use :CP <platform> <contest> [--{lang=<lang>,debug}] to set up first.'):format(
contest_id,
platform
),
('No contest %s configured for platform %s.'):format(contest_id, platform),
vim.log.levels.ERROR
)
return
@ -118,8 +115,8 @@ function M.toggle_interactive()
state.set_active_panel('interactive')
end
---@param debug? boolean
function M.toggle_run_panel(debug)
---@param run_opts? RunOpts
function M.toggle_run_panel(run_opts)
if state.get_active_panel() == 'run' then
if current_diff_layout then
current_diff_layout.cleanup()
@ -152,10 +149,7 @@ function M.toggle_run_panel(debug)
if not contest_id then
logger.log(
('No contest %s configured for platform %s. Use :CP <platform> <contest> [--{lang=<lang>,debug}] to set up first.'):format(
contest_id,
platform
),
('No contest %s configured for platform %s.'):format(contest_id, platform),
vim.log.levels.ERROR
)
return
@ -187,13 +181,6 @@ function M.toggle_run_panel(debug)
)
local config = config_module.get_config()
if config.hooks and config.hooks.before_run then
config.hooks.before_run(state)
end
if debug and config.hooks and config.hooks.before_debug then
config.hooks.before_debug(state)
end
local run = require('cp.runner.run')
local input_file = state.get_input_file()
logger.log(('run panel: checking test cases for %s'):format(input_file or 'none'))
@ -210,7 +197,7 @@ function M.toggle_run_panel(debug)
local tab_buf = utils.create_buffer_with_options()
local main_win = vim.api.nvim_get_current_win()
vim.api.nvim_win_set_buf(main_win, tab_buf)
vim.api.nvim_set_option_value('filetype', 'cptest', { buf = tab_buf })
vim.api.nvim_set_option_value('filetype', 'cp', { buf = tab_buf })
local test_windows = { tab_win = main_win }
local test_buffers = { tab_buf = tab_buf }
@ -282,6 +269,17 @@ function M.toggle_run_panel(debug)
setup_keybindings_for_buffer(test_buffers.tab_buf)
if config.hooks and config.hooks.before_run then
vim.schedule_wrap(function()
config.hooks.before_run(state)
end)
end
if run_opts and run_opts.debug and config.hooks and config.hooks.before_debug then
vim.schedule_wrap(function()
config.hooks.before_debug(state)
end)
end
local execute = require('cp.runner.execute')
local compile_result = execute.compile_problem()
if compile_result.success then

View file

@ -23,22 +23,26 @@ end, {
if num_args == 2 then
local candidates = {}
local state = require('cp.state')
local platform, contest_id = state.get_platform(), state.get_contest_id()
local platform = state.get_platform()
local contest_id = state.get_contest_id()
if platform and contest_id then
vim.list_extend(candidates, actions)
local cache = require('cp.cache')
cache.load()
local contest_data = cache.get_contest_data(platform, contest_id)
if contest_data and contest_data.problems then
for _, problem in ipairs(contest_data.problems) do
table.insert(candidates, problem.id)
end
if contest_data and contest_data.index_map then
local ids = vim.tbl_keys(contest_data.index_map)
table.sort(ids)
vim.list_extend(candidates, ids)
end
else
vim.list_extend(candidates, platforms)
table.insert(candidates, 'cache')
table.insert(candidates, 'pick')
end
return vim.tbl_filter(function(cmd)
return cmd:find(ArgLead, 1, true) == 1
end, candidates)