Merge branch 'main' into feat/color

This commit is contained in:
Barrett Ruth 2025-09-20 11:48:49 -04:00
commit 8e0b2bdb6c
11 changed files with 441 additions and 172 deletions

View file

@ -1,9 +1,10 @@
*cp.txt* Competitive programming plugin for Neovim *cp.txt* Competitive programming plugin for Neovim *cp.txt*
Author: Barrett Ruth <br.barrettruth@gmail.com> Author: Barrett Ruth <br.barrettruth@gmail.com>
License: Same terms as Vim itself (see |license|) License: Same terms as Vim itself (see |license|)
INTRODUCTION *cp* *cp.nvim* ==============================================================================
INTRODUCTION *cp* *cp.nvim*
cp.nvim is a competitive programming plugin that automates problem setup, cp.nvim is a competitive programming plugin that automates problem setup,
compilation, and testing workflow for online judges. compilation, and testing workflow for online judges.
@ -11,7 +12,8 @@ compilation, and testing workflow for online judges.
Supported platforms: AtCoder, Codeforces, CSES Supported platforms: AtCoder, Codeforces, CSES
Supported languages: C++, Python Supported languages: C++, Python
REQUIREMENTS *cp-requirements* ==============================================================================
REQUIREMENTS *cp-requirements*
- Neovim 0.10.0+ - Neovim 0.10.0+
- uv package manager (https://docs.astral.sh/uv/) - uv package manager (https://docs.astral.sh/uv/)
@ -20,209 +22,295 @@ REQUIREMENTS *cp-requirements*
Optional: Optional:
- LuaSnip for template expansion (https://github.com/L3MON4D3/LuaSnip) - LuaSnip for template expansion (https://github.com/L3MON4D3/LuaSnip)
COMMANDS *cp-commands* ==============================================================================
COMMANDS *cp-commands*
*:CP* :CP *:CP*
cp.nvim uses a single :CP command with intelligent argument parsing: cp.nvim uses a single :CP command with intelligent argument parsing:
State Restoration ~ State Restoration ~
:CP Restore contest context from current file.
:CP Restore contest context from current file.
Automatically detects platform, contest, problem, Automatically detects platform, contest, problem,
and language from cached state. Use this after and language from cached state. Use this after
switching files to restore your CP environment. switching files to restore your CP environment.
Requires previous setup with full :CP command. Requires previous setup with full :CP command.
Setup Commands ~ Setup Commands ~
:CP {platform} {contest_id} {problem_id} [--lang={language}]
:CP {platform} {contest_id} {problem_id} [--lang={language}]
Full setup: set platform, load contest metadata, Full setup: set platform, load contest metadata,
and set up specific problem. Scrapes test cases and set up specific problem. Scrapes test cases
and creates source file. and creates source file.
Example: :CP codeforces 1933 a Example: >
Example: :CP codeforces 1933 a --lang=python :CP codeforces 1933 a
:CP codeforces 1933 a --lang=python
:CP {platform} {contest_id} Contest setup: set platform and load contest <
:CP {platform} {contest_id}
Contest setup: set platform and load contest
metadata for navigation. Caches problem list. metadata for navigation. Caches problem list.
Example: :CP atcoder abc324 Example: >
:CP atcoder abc324
:CP {platform} Platform setup: set platform only. <
Example: :CP cses :CP {platform} Platform setup: set platform only.
Example: >
:CP {problem_id} [--lang={language}] :CP cses
<
:CP {problem_id} [--lang={language}]
Problem switch: switch to different problem Problem switch: switch to different problem
within current contest context. within current contest context.
Example: :CP b (switch to problem b) Example: >
Example: :CP b --lang=python :CP b
:CP b --lang=python
Action Commands ~ <
Action Commands ~
:CP run [--debug] Toggle run panel for individual test case :CP run [--debug] Toggle run panel for individual test case
debugging. Shows per-test results with redesigned debugging. Shows per-test results with redesigned
layout for efficient comparison. layout for efficient comparison.
Use --debug flag to compile with debug flags Use --debug flag to compile with debug flags.
Requires contest setup first. Requires contest setup first.
Navigation Commands ~ Navigation Commands ~
:CP next Navigate to next problem in current contest.
:CP next Navigate to next problem in current contest.
Stops at last problem (no wrapping). Stops at last problem (no wrapping).
:CP prev Navigate to previous problem in current contest. Navigation Commands ~
:CP prev Navigate to previous problem in current contest.
Stops at first problem (no wrapping). Stops at first problem (no wrapping).
CONFIGURATION *cp-config* Command Flags ~
*cp-flags*
Flags can be used with setup and action commands:
--lang={language} Specify language for the problem.
--lang {language} Alternative syntax for language specification.
Supported languages: cpp, python
Example: >
:CP atcoder abc324 a --lang=python
:CP b --lang cpp
<
--debug Enable debug compilation with additional flags.
Uses the `debug` command template instead of
`compile`. Typically includes debug symbols and
sanitizers for memory error detection.
Example: >
:CP run --debug
<
Note: Debug compilation may be slower but provides
better error reporting for runtime issues.
Template Variables ~
*cp-template-vars*
Command templates support variable substitution using `{variable}` syntax:
• {source} Source file path (e.g. "abc324a.cpp")
• {binary} Output binary path (e.g. "build/abc324a.run")
• {version} Language version when specified in config
• {contest} Contest identifier (e.g. "abc324", "1933")
• {problem} Problem identifier (e.g. "a", "b")
Example template: >
compile = { 'g++', '{source}', '-o', '{binary}', '-std=c++{version}' }
< Would expand to: >
g++ abc324a.cpp -o build/abc324a.run -std=c++17
<
==============================================================================
CONFIGURATION *cp-config*
cp.nvim works out of the box. No setup required. cp.nvim works out of the box. No setup required.
Here's an example configuration with lazy.nvim: > Here's an example configuration with lazy.nvim: >lua
{ {
'barrett-ruth/cp.nvim', 'barrett-ruth/cp.nvim',
cmd = 'CP', cmd = 'CP',
opts = { opts = {
contests = { contests = {
default = { default = {
cpp = { cpp = {
compile = { 'g++', '{source}', '-o', '{binary}', '-std=c++17' }, compile = { 'g++', '{source}', '-o', '{binary}',
test = { '{binary}' }, '-std=c++17' },
debug = { 'g++', '{source}', '-o', '{binary}', '-std=c++17', '-g', '-fsanitize=address,undefined' }, test = { '{binary}' },
}, debug = { 'g++', '{source}', '-o', '{binary}',
python = { '-std=c++17', '-g',
test = { 'python3', '{source}' }, '-fsanitize=address,undefined' },
}, },
python = {
test = { 'python3', '{source}' },
},
},
}, },
}, snippets = {},
snippets = {}, hooks = {
hooks = { before_run = nil,
before_run = nil, before_debug = nil,
before_debug = nil, setup_code = nil,
setup_code = nil, },
}, debug = false,
debug = false, scrapers = { 'atcoder', 'codeforces', 'cses' },
scrapers = { ... }, -- all scrapers enabled by default filename = default_filename, -- <contest id> + <problem id>
filename = default_filename, -- <contest id> + <problem id> run_panel = {
run_panel = { diff_mode = 'vim',
diff_mode = 'vim', next_test_key = '<c-n>',
next_test_key = '<c-n>', prev_test_key = '<c-p>',
prev_test_key = '<c-p>', toggle_diff_key = 't',
toggle_diff_key = 't', max_output_lines = 50,
max_output_lines = 50, },
}, diff = {
diff = { git = {
git = { args = { 'diff', '--no-index', '--word-diff=plain',
args = { 'diff', '--no-index', '--word-diff=plain', '--word-diff-regex=.', '--no-prefix' }, '--word-diff-regex=.', '--no-prefix' },
},
}, },
},
} }
} }
< <
*cp.Config* *cp.Config*
Fields: ~ Fields: ~
- {contests} (`table<string,ContestConfig>`) Contest configurations. {contests} (table<string,ContestConfig>) Contest configurations.
- {hooks} (`cp.Hooks`) Hook functions called at various stages. {hooks} (|cp.Hooks|) Hook functions called at various stages.
- {snippets} (`table[]`) LuaSnip snippet definitions. {snippets} (table[]) LuaSnip snippet definitions.
- {debug} (`boolean`, default: `false`) Show info messages {debug} (boolean, default: false) Show info messages
during operation. during operation.
- {scrapers} (`table<string>`) List of enabled scrapers. {scrapers} (table<string>) List of enabled scrapers.
Default: all scrapers enabled Default: all scrapers enabled
- {run_panel} (`RunPanelConfig`) Test panel behavior configuration. {run_panel} (|RunPanelConfig|) Test panel behavior configuration.
- {diff} (`DiffConfig`) Diff backend configuration. {diff} (|DiffConfig|) Diff backend configuration.
- {filename}? (`function`) Custom filename generation function. {filename} (function, optional) Custom filename generation.
`function(contest, contest_id, problem_id, config, language)` function(contest, contest_id, problem_id, config, language)
Should return full filename with extension. Should return full filename with extension.
(default: `default_filename` - concatenates contest_id and problem_id, lowercased) (default: concatenates contest_id and problem_id, lowercased)
*cp.ContestConfig*
*cp.ContestConfig*
Fields: ~ Fields: ~
- {cpp} (`LanguageConfig`) C++ language configuration. {cpp} (|LanguageConfig|) C++ language configuration.
- {python} (`LanguageConfig`) Python language configuration. {python} (|LanguageConfig|) Python language configuration.
- {default_language} (`string`, default: `"cpp"`) Default language when {default_language} (string, default: "cpp") Default language when
`--lang` not specified. --lang not specified.
*cp.LanguageConfig*
*cp.LanguageConfig*
Fields: ~ Fields: ~
- {compile}? (`string[]`) Compile command template with {compile} (string[], optional) Compile command template with
`{version}`, `{source}`, `{binary}` placeholders. {version}, {source}, {binary} placeholders.
- {test} (`string[]`) Test execution command template. {test} (string[]) Test execution command template.
- {debug}? (`string[]`) Debug compile command template. {debug} (string[], optional) Debug compile command template.
- {version}? (`number`) Language version (e.g. 20, 23 for C++). {version} (number, optional) Language version (e.g. 20, 23 for C++).
- {extension} (`string`) File extension (e.g. "cc", "py"). {extension} (string) File extension (e.g. "cc", "py").
- {executable}? (`string`) Executable name for interpreted languages. {executable} (string, optional) Executable name for interpreted languages.
*cp.RunPanelConfig*
*cp.RunPanelConfig*
Fields: ~ 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. Git provides character-level precision, vim uses
- {next_test_key} (`string`, default: `"<c-n>"`) Key to navigate to next test case. built-in diff.
- {prev_test_key} (`string`, default: `"<c-p>"`) Key to navigate to previous test case. {next_test_key} (string, default: "<c-n>") Key to navigate to next test case.
- {toggle_diff_key} (`string`, default: `"t"`) Key to toggle diff mode between vim and git. {prev_test_key} (string, default: "<c-p>") Key to navigate to previous test case.
- {max_output_lines} (`number`, default: `50`) Maximum lines of test output to display. {toggle_diff_key} (string, default: "t") Key to toggle diff mode.
{max_output_lines} (number, default: 50) Maximum lines of test output.
*cp.DiffConfig*
*cp.DiffConfig*
Fields: ~ Fields: ~
- {git} (`DiffGitConfig`) Git diff backend configuration. {git} (|cp.DiffGitConfig|) Git diff backend configuration.
*cp.Hooks*
*cp.DiffGitConfig*
Fields: ~ Fields: ~
- {before_run}? (`function`) Called before test panel opens. {args} (string[]) Command-line arguments for git diff.
`function(ctx: ProblemContext)` Default: { 'diff', '--no-index', '--word-diff=plain',
- {before_debug}? (`function`) Called before debug compilation. '--word-diff-regex=.', '--no-prefix' }
`function(ctx: ProblemContext)` • --no-index: Compare files outside git repository
- {setup_code}? (`function`) Called after source file is opened. • --word-diff=plain: Character-level diff markers
• --word-diff-regex=.: Split on every character
• --no-prefix: Remove a/ b/ prefixes from output
*cp.Hooks*
Fields: ~
{before_run} (function, optional) Called before test panel opens.
function(ctx: ProblemContext)
{before_debug} (function, optional) Called before debug compilation.
function(ctx: ProblemContext)
{setup_code} (function, optional) Called after source file is opened.
Good for configuring buffer settings. Good for configuring buffer settings.
`function(ctx: ProblemContext)` function(ctx: ProblemContext)
*ProblemContext* *ProblemContext*
Context object passed to hook functions containing problem information.
Fields: ~ Fields: ~
- {contest} (`string`) Platform name (e.g. "atcoder", "codeforces") {contest} (string) Platform name (e.g. "atcoder", "codeforces")
- {contest_id} (`string`) Contest ID (e.g. "abc123", "1933") {contest_id} (string) Contest ID (e.g. "abc123", "1933")
- {problem_id}? (`string`) Problem ID (e.g. "a", "b") - nil for CSES {problem_id} (string, optional) Problem ID (e.g. "a", "b") - nil for CSES
- {source_file} (`string`) Source filename (e.g. "abc123a.cpp") {source_file} (string) Source filename (e.g. "abc123a.cpp")
- {binary_file} (`string`) Binary output path (e.g. "build/abc123a.run") {binary_file} (string) Binary output path (e.g. "build/abc123a.run")
- {input_file} (`string`) Test input path (e.g. "io/abc123a.cpin") {input_file} (string) Test input path (e.g. "io/abc123a.cpin")
- {output_file} (`string`) Program output path (e.g. "io/abc123a.cpout") {output_file} (string) Program output path (e.g. "io/abc123a.cpout")
- {expected_file} (`string`) Expected output path (e.g. "io/abc123a.expected") {expected_file} (string) Expected output path (e.g. "io/abc123a.expected")
- {problem_name} (`string`) Display name (e.g. "abc123a") {problem_name} (string) Display name (e.g. "abc123a")
WORKFLOW *cp-workflow* Example usage in hook: >lua
hooks = {
setup_code = function(ctx)
print("Setting up " .. ctx.problem_name)
print("Source file: " .. ctx.source_file)
end
}
<
For the sake of consistency and simplicity, cp.nvim extracts contest/problem identifiers from ==============================================================================
URLs. This means that, for example, CodeForces/AtCoder contests are configured by WORKFLOW *cp-workflow*
their round id rather than round number. See below.
PLATFORM-SPECIFIC USAGE *cp-platforms* For the sake of consistency and simplicity, cp.nvim extracts contest/problem
identifiers from URLs. This means that, for example, CodeForces/AtCoder
contests are configured by their round id rather than round number. See below.
==============================================================================
PLATFORM-SPECIFIC USAGE *cp-platforms*
AtCoder ~ AtCoder ~
*cp-atcoder* *cp-atcoder*
URL format: https://atcoder.jp/contests/abc123/tasks/abc123_a URL format: https://atcoder.jp/contests/abc123/tasks/abc123_a
AtCoder contests use consistent naming patterns where contest ID and problem
ID are combined to form the task name.
Platform characteristics:
• Contest types: ABC (Beginner), ARC (Regular), AGC (Grand), etc.
• Problem naming: Contest ID + problem letter (e.g. "abc324_a")
• Multi-test problems: Handled with conditional compilation directives
• Template features: Includes fast I/O and common competitive programming
headers
In terms of cp.nvim, this corresponds to: In terms of cp.nvim, this corresponds to:
- Platform: atcoder - Platform: atcoder
- Contest ID: abc123 - Contest ID: abc123 (from URL path segment)
- Problem ID: a - Problem ID: a (single letter, extracted from task name)
Usage examples: > Usage examples: >
:CP atcoder abc123 a " Full setup: problem A from contest ABC123 :CP atcoder abc324 a " Full setup: problem A from contest ABC324
:CP atcoder abc123 " Contest setup: load contest metadata only :CP atcoder abc324 " Contest setup: load contest metadata only
:CP b " Switch to problem B (if contest loaded) :CP b " Switch to problem B (if contest loaded)
:CP next " Navigate to next problem in contest :CP next " Navigate to next problem in contest
< <
Note: AtCoder template includes optimizations
for multi-test case problems commonly found
in contests.
Codeforces ~ Codeforces ~
*cp-codeforces* *cp-codeforces*
URL format: https://codeforces.com/contest/1234/problem/A URL format: https://codeforces.com/contest/1234/problem/A
Codeforces uses numeric contest IDs with letter-based problem identifiers.
Educational rounds, gym contests, and regular contests all follow this pattern.
Platform characteristics:
• Contest types: Regular, Educational, Div. 1/2/3, Global rounds
• Problem naming: Numeric contest + problem letter
• Multi-test support: Template includes test case loop structure
• Interactive problems: Supported with flush handling
• Time/memory limits: Typically 1-2 seconds, 256 MB
In terms of cp.nvim, this corresponds to: In terms of cp.nvim, this corresponds to:
- Platform: codeforces - Platform: codeforces
- Contest ID: 1234 - Contest ID: 1234 (numeric, from URL)
- Problem ID: a (lowercase) - Problem ID: a (lowercase letter, normalized from URL)
Usage examples: > Usage examples: >
:CP codeforces 1934 a " Full setup: problem A from contest 1934 :CP codeforces 1934 a " Full setup: problem A from contest 1934
@ -230,38 +318,51 @@ Usage examples: >
:CP c " Switch to problem C (if contest loaded) :CP c " Switch to problem C (if contest loaded)
:CP prev " Navigate to previous problem in contest :CP prev " Navigate to previous problem in contest
< <
Note: Problem IDs are automatically converted
to lowercase for consistency.
CSES ~ CSES ~
*cp-cses* *cp-cses*
URL format: https://cses.fi/problemset/task/1068 URL format: https://cses.fi/problemset/task/1068
CSES is organized by categories rather than contests. Currently all problems CSES (Code Submission Evaluation System) is organized by problem categories
are grouped under "CSES Problem Set" category. rather than traditional contests. All problems are accessible individually.
Platform characteristics:
• Organization: Category-based (Introductory, Sorting, Dynamic Programming)
• Problem numbering: Sequential numeric IDs (1001, 1068, etc.)
• Difficulty progression: Problems increase in complexity within categories
• No time pressure: Educational focus rather than contest environment
• Cache expiry: 30 days (problems may be updated periodically)
In terms of cp.nvim, this corresponds to: In terms of cp.nvim, this corresponds to:
- Platform: cses - Platform: cses
- Contest ID: "CSES Problem Set" (category) - Contest ID: Problem ID (1068) - used as both contest and problem identifier
- Problem ID: 1068 (numeric) - Problem ID: nil (not applicable for CSES structure)
Usage examples: > Usage examples: >
:CP cses 1068 " Set up problem 1068 from CSES :CP cses 1068 " Set up problem 1068 from CSES
:CP 1070 " Switch to problem 1070 (if CSES loaded) :CP 1070 " Switch to problem 1070 (if CSES context loaded)
:CP next " Navigate to next problem in CSES :CP next " Navigate to next problem in CSES sequence
< <
COMPLETE WORKFLOW EXAMPLE *cp-example* Note: CSES problems are treated as individual
entities rather than contest problems.
==============================================================================
COMPLETE WORKFLOW EXAMPLE *cp-example*
Example: Setting up and solving AtCoder contest ABC324 Example: Setting up and solving AtCoder contest ABC324
1. Browse to https://atcoder.jp/contests/abc324 1. Browse to https://atcoder.jp/contests/abc324
2. Set up contest and load metadata: > 2. Set up contest and load metadata: >
:CP atcoder abc324 :CP atcoder abc324
< This caches all problems (A, B, ...) for navigation < This caches all problems (A, B, ...) for navigation
3. Start with problem A: > 3. Start with problem A: >
:CP a :CP a
<
Or do both at once with: Or do both at once with: >
:CP atcoder abc324 a :CP atcoder abc324 a
< This creates a.cc and scrapes test cases < This creates a.cc and scrapes test cases
4. Code your solution, then test: > 4. Code your solution, then test: >
@ -272,27 +373,29 @@ Example: Setting up and solving AtCoder contest ABC324
5. If needed, debug with sanitizers: > 5. If needed, debug with sanitizers: >
:CP run --debug :CP run --debug
< <
6. Move to next problem: > 6. Move to next problem: >
:CP next :CP next
< This automatically sets up problem B < This automatically sets up problem B
6. Continue solving problems with :CP next/:CP prev navigation 7. Continue solving problems with :CP next/:CP prev navigation
7. Switch to another file (e.g., previous contest): >
8. Switch to another file (e.g., previous contest): >
:e ~/contests/abc323/a.cpp :e ~/contests/abc323/a.cpp
:CP :CP
< Automatically restores abc323 contest context < Automatically restores abc323 contest context
8. Submit solutions on AtCoder website 9. Submit solutions on AtCoder website
<
RUN PANEL *cp-run* ==============================================================================
RUN PANEL *cp-run*
The run 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 layout optimized for modern screens. Shows test status with competitive
programming terminology and efficient space usage. programming terminology and efficient space usage.
Activation ~ Activation ~
*:CP-run* *:CP-run*
:CP run [--debug] Toggle run panel on/off. When activated, :CP run [--debug] Toggle run panel on/off. When activated,
replaces current layout with test interface. replaces current layout with test interface.
Automatically compiles and runs all tests. Automatically compiles and runs all tests.
@ -302,9 +405,7 @@ Activation ~
Interface ~ Interface ~
The run panel uses a professional table layout with precise column alignment: The run panel uses the following table layout: >
(observe that the diff is indeed highlighted, not the weird amalgamation of
characters below) >
┌─────┬────────┬──────────────┬───────────┬──────────┬─────────────┐ ┌─────┬────────┬──────────────┬───────────┬──────────┬─────────────┐
│ # │ Status │ Runtime (ms) │ Time (ms) │ Mem (MB) │ Exit Code │ │ # │ Status │ Runtime (ms) │ Time (ms) │ Mem (MB) │ Exit Code │
@ -324,7 +425,6 @@ characters below) >
│100 │ │100 │
│hello w[-o-]r{+o+}ld │ │hello w[-o-]r{+o+}ld │
└──────────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────────┘
<
Status Indicators ~ Status Indicators ~
@ -336,7 +436,7 @@ Test cases use competitive programming terminology with color highlighting:
RTE Runtime Error (non-zero exit) - Purple RTE Runtime Error (non-zero exit) - Purple
Highlight Groups ~ Highlight Groups ~
*cp-highlights* *cp-highlights*
cp.nvim defines the following highlight groups for status indicators: cp.nvim defines the following highlight groups for status indicators:
CpTestAC Green foreground for AC status CpTestAC Green foreground for AC status
@ -346,16 +446,20 @@ cp.nvim defines the following highlight groups for status indicators:
CpTestPending Gray foreground for pending tests CpTestPending Gray foreground for pending tests
You can customize these colors by linking to other highlight groups in your You can customize these colors by linking to other highlight groups in your
colorscheme or by redefining them: > colorscheme or by redefining them: >lua
vim.api.nvim_set_hl(0, 'CpTestAC', { link = 'DiffAdd' }) vim.api.nvim_set_hl(0, 'CpTestAC', { link = 'DiffAdd' })
vim.api.nvim_set_hl(0, 'CpTestWA', { fg = '#ff0000' }) vim.api.nvim_set_hl(0, 'CpTestWA', { fg = '#ff0000' })
< <
Keymaps ~ Keymaps ~
*cp-test-keys* *cp-test-keys*
<c-n> Navigate to next test case (configurable via run_panel.next_test_key) <c-n> Navigate to next test case (configurable via
<c-p> Navigate to previous test case (configurable via run_panel.prev_test_key) run_panel.next_test_key)
t Toggle diff mode between vim and git (configurable via run_panel.toggle_diff_key) <c-p> Navigate to previous test case (configurable via
q Exit test panel and restore layout run_panel.prev_test_key)
t Toggle diff mode between vim and git (configurable
via run_panel.toggle_diff_key)
q Exit test panel and restore layout
Diff Modes ~ Diff Modes ~
@ -374,9 +478,10 @@ execution pipeline, but with isolated input/output for
precise failure analysis. All tests are automatically run when the precise failure analysis. All tests are automatically run when the
panel opens. panel opens.
FILE STRUCTURE *cp-files* ==============================================================================
FILE STRUCTURE *cp-files*
cp.nvim creates the following file structure upon problem setup: 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 (e.g. a.cc, b.py)
build/ build/
@ -385,22 +490,117 @@ cp.nvim creates the following file structure upon problem setup:
{problem_id}.n.cpin " nth test input {problem_id}.n.cpin " nth test input
{problem_id}.n.cpout " nth program output {problem_id}.n.cpout " nth program output
{problem_id}.expected " Expected output {problem_id}.expected " Expected output
<
The plugin automatically manages this structure and navigation between problems The plugin automatically manages this structure and navigation between problems
maintains proper file associations. maintains proper file associations.
SNIPPETS *cp-snippets* ==============================================================================
CACHING SYSTEM *cp-caching*
cp.nvim maintains a persistent cache to improve performance and enable offline
functionality. The cache stores contest metadata, problem lists, test cases,
and file-to-context mappings.
Cache Location ~
*cp-cache-location*
Cache is stored at: >
vim.fn.stdpath('data') .. '/cp-nvim.json'
<
Typically resolves to:
• Linux/macOS: `~/.local/share/nvim/cp-nvim.json`
• Windows: `%LOCALAPPDATA%\nvim-data\cp-nvim.json`
Cache Structure ~
*cp-cache-structure*
The cache contains four main sections:
contest_data Contest metadata and problem lists
• Indexed by: `platform:contest_id`
• Contains: Problem names, IDs, URLs, constraints
• Expiry: Platform-dependent (see |cp-cache-expiry|)
test_cases Scraped test case input/output pairs
• Indexed by: `platform:contest_id:problem_id`
• Contains: Input data, expected output, test case count
• Expiry: Never (local test data persists)
file_states File-to-context mapping
• Indexed by: Absolute file path
• Contains: Platform, contest_id, problem_id, language
• Purpose: Enables context restoration with `:CP`
timestamps Last update times for cache validation
• Tracks: When each cache entry was last refreshed
• Used for: Expiry checking and incremental updates
Cache Expiry Policy ~
*cp-cache-expiry*
Different data types have different expiry policies:
AtCoder/Codeforces contest data Never expires (contests are immutable)
CSES problem data 30 days (problems may be updated)
Test cases Never expires (local test data)
File states Never expires (tracks user workspace)
Manual Cache Management ~
*cp-cache-management*
While cache management is automatic, you can manually intervene:
Clear specific contest cache: >
:lua require('cp.cache').clear_contest('atcoder', 'abc324')
<
Clear all cache data: >
:lua require('cp.cache').clear_all()
<
Force refresh contest metadata: >
:lua require('cp.cache').refresh_contest('codeforces', '1934')
<
View cache statistics: >
:lua print(vim.inspect(require('cp.cache').get_stats()))
<
Note: Manual cache operations require Lua
knowledge and are primarily for debugging.
Offline Functionality ~
*cp-cache-offline*
The cache enables limited offline functionality:
✓ Restore context from cached file states
✓ Navigate between cached problems in a contest
✓ Access cached test cases for local development
✓ Use cached templates and configuration
✗ Scrape new problems without internet connection
✗ Download new contest metadata
✗ Update problem constraints or test cases
Performance Considerations ~
*cp-cache-performance*
The cache provides several performance benefits:
• Instant context restoration: No network requests needed
• Fast problem navigation: Problem lists loaded from cache
• Reduced scraping: Test cases cached after first download
• Batch operations: Multiple problems can be set up quickly
Cache size typically remains under 1MB even with extensive usage.
==============================================================================
SNIPPETS *cp-snippets*
cp.nvim integrates with LuaSnip for automatic template expansion. Built-in cp.nvim integrates with LuaSnip for automatic template expansion. Built-in
snippets include basic C++ and Python templates for each contest type. snippets include basic C++ and Python templates for each contest type.
Snippet trigger names must match the following format exactly: Snippet trigger names must match the following format exactly: >
cp.nvim/{platform} cp.nvim/{platform}
<
Custom snippets can be added via the `snippets` configuration field. Custom snippets can be added via the `snippets` configuration field.
HEALTH CHECK *cp-health* ==============================================================================
HEALTH CHECK *cp-health*
Run |:checkhealth| cp to verify your setup. Run |:checkhealth| cp to verify your setup.

View file

@ -1,12 +1,15 @@
describe('cp.cache', function() describe('cp.cache', function()
local cache local cache
local spec_helper = require('spec.spec_helper')
before_each(function() before_each(function()
spec_helper.setup()
cache = require('cp.cache') cache = require('cp.cache')
cache.load() cache.load()
end) end)
after_each(function() after_each(function()
spec_helper.teardown()
cache.clear_contest_data('atcoder', 'test_contest') cache.clear_contest_data('atcoder', 'test_contest')
cache.clear_contest_data('codeforces', 'test_contest') cache.clear_contest_data('codeforces', 'test_contest')
cache.clear_contest_data('cses', 'test_contest') cache.clear_contest_data('cses', 'test_contest')

View file

@ -1,10 +1,16 @@
describe('cp.config', function() describe('cp.config', function()
local config local config
local spec_helper = require('spec.spec_helper')
before_each(function() before_each(function()
spec_helper.setup()
config = require('cp.config') config = require('cp.config')
end) end)
after_each(function()
spec_helper.teardown()
end)
describe('setup', function() describe('setup', function()
it('returns defaults with nil input', function() it('returns defaults with nil input', function()
local result = config.setup() local result = config.setup()

View file

@ -1,5 +1,15 @@
describe('cp.diff', function() describe('cp.diff', function()
local diff = require('cp.diff') local spec_helper = require('spec.spec_helper')
local diff
before_each(function()
spec_helper.setup()
diff = require('cp.diff')
end)
after_each(function()
spec_helper.teardown()
end)
describe('get_available_backends', function() describe('get_available_backends', function()
it('returns vim and git backends', function() it('returns vim and git backends', function()

View file

@ -2,8 +2,10 @@ describe('cp.execute', function()
local execute local execute
local mock_system_calls local mock_system_calls
local temp_files local temp_files
local spec_helper = require('spec.spec_helper')
before_each(function() before_each(function()
spec_helper.setup()
execute = require('cp.execute') execute = require('cp.execute')
mock_system_calls = {} mock_system_calls = {}
temp_files = {} temp_files = {}
@ -60,6 +62,7 @@ describe('cp.execute', function()
after_each(function() after_each(function()
vim.system = vim.system_original or vim.system vim.system = vim.system_original or vim.system
spec_helper.teardown()
temp_files = {} temp_files = {}
end) end)

View file

@ -1,5 +1,15 @@
describe('cp.highlight', function() describe('cp.highlight', function()
local highlight = require('cp.highlight') local spec_helper = require('spec.spec_helper')
local highlight
before_each(function()
spec_helper.setup()
highlight = require('cp.highlight')
end)
after_each(function()
spec_helper.teardown()
end)
describe('parse_git_diff', function() describe('parse_git_diff', function()
it('skips git diff headers', function() it('skips git diff headers', function()

View file

@ -1,10 +1,16 @@
describe('cp.problem', function() describe('cp.problem', function()
local problem local problem
local spec_helper = require('spec.spec_helper')
before_each(function() before_each(function()
spec_helper.setup()
problem = require('cp.problem') problem = require('cp.problem')
end) end)
after_each(function()
spec_helper.teardown()
end)
describe('create_context', function() describe('create_context', function()
local base_config = { local base_config = {
contests = { contests = {

View file

@ -1,5 +1,14 @@
describe('cp.test_render', function() describe('cp.run_render', function()
local run_render = require('cp.run_render') local run_render = require('cp.run_render')
local spec_helper = require('spec.spec_helper')
before_each(function()
spec_helper.setup()
end)
after_each(function()
spec_helper.teardown()
end)
describe('get_status_info', function() describe('get_status_info', function()
it('returns AC for pass status', function() it('returns AC for pass status', function()

View file

@ -3,8 +3,10 @@ describe('cp.scrape', function()
local mock_cache local mock_cache
local mock_system_calls local mock_system_calls
local temp_files local temp_files
local spec_helper = require('spec.spec_helper')
before_each(function() before_each(function()
spec_helper.setup()
temp_files = {} temp_files = {}
mock_cache = { mock_cache = {
@ -85,6 +87,7 @@ describe('cp.scrape', function()
after_each(function() after_each(function()
package.loaded['cp.cache'] = nil package.loaded['cp.cache'] = nil
vim.system = vim.system_original or vim.system vim.system = vim.system_original or vim.system
spec_helper.teardown()
temp_files = {} temp_files = {}
end) end)

View file

@ -1,8 +1,11 @@
describe('cp.snippets', function() describe('cp.snippets', function()
local snippets local snippets
local mock_luasnip local mock_luasnip
local spec_helper = require('spec.spec_helper')
before_each(function() before_each(function()
spec_helper.setup()
package.loaded['cp.snippets'] = nil
snippets = require('cp.snippets') snippets = require('cp.snippets')
mock_luasnip = { mock_luasnip = {
snippet = function(trigger, body) snippet = function(trigger, body)
@ -31,6 +34,8 @@ describe('cp.snippets', function()
end) end)
after_each(function() after_each(function()
spec_helper.teardown()
package.loaded['cp.snippets'] = nil
package.loaded['luasnip'] = nil package.loaded['luasnip'] = nil
package.loaded['luasnip.extras.fmt'] = nil package.loaded['luasnip.extras.fmt'] = nil
end) end)

14
spec/spec_helper.lua Normal file
View file

@ -0,0 +1,14 @@
local M = {}
function M.setup()
package.loaded['cp.log'] = {
log = function() end,
set_config = function() end,
}
end
function M.teardown()
package.loaded['cp.log'] = nil
end
return M