Commit graph

1011 commits

Author SHA1 Message Date
Barrett Ruth
7dffa0d7c8
Merge branch 'main' into feat/cses-submit 2026-03-05 01:07:23 -05:00
fa47630e0b fix(submit): clear command line after password prompt
Problem: the first status notification after `inputsecret` rendered on
the same line as the password prompt.

Solution: call `vim.cmd.redraw()` after `inputrestore` to flush the
command line before the callback fires notifications.
2026-03-05 01:06:35 -05:00
972044fd0f feat(cses): implement submit via REST API
Problem: CSES submit was a stub returning "not yet implemented".

Solution: authenticate via web login + API token bridge (POST
`/login` form, then POST `/api/login` and confirm the auth page),
submit source to `/api/courses/problemset/submissions` with
base64-encoded content, and poll for verdict. Uses the same
username/password credential model as AtCoder.

Also update `scraper.lua` to pass the full ndjson event to `on_status`
(instead of just the status string) and handle `credentials` events
for platforms that return updated credentials.
2026-03-05 01:03:53 -05:00
Barrett Ruth
e674265527
fix(setup): prevent spurious swap file warnings on :CP (#297)
## Problem

`setup_problem` explicitly set `swapfile = true` on provisional buffers,
overriding the user's global `noswapfile` setting. The resulting `.swp`
files triggered E325 warnings on subsequent `:e` calls — especially
during the restore path, which redundantly re-opened the current buffer.

## Solution

Remove the `swapfile` override so the user's setting is respected, and
skip the `:e` call in `setup_problem` when the current buffer already
matches the target source file.
2026-03-05 00:37:29 -05:00
Barrett Ruth
c194f12eee
feat(atcoder): extract submit helpers; add live status notifications (#294)
## Problem

`_submit_sync` was a 170-line nested closure with `_solve_turnstile` and
the browser-install block further nested inside it. Status events went
to
stderr, which `run_scraper()` silently discards, leaving the user with a
10–30s silent hang after credential entry. The NDJSON spawn path also
lacked stdin support, so submit had no streaming path at all.

## Solution

Extract `_TURNSTILE_JS`, `_solve_turnstile`, `_ensure_browser`, and
`_submit_headless` to module level in `atcoder.py`; status events
(`installing_browser`, `checking_login`, `logging_in`, `submitting`) now
print to stdout as NDJSON. Add stdin pipe support to the NDJSON spawn
path in `scraper.lua` and switch `M.submit` to streaming with an
`on_status` callback. Wire `on_status` in `submit.lua` to fire
`vim.notify` for each phase transition.
2026-03-04 19:27:29 -05:00
Barrett Ruth
1bc0aa41b6
refactor(cache): nest credentials under platform namespace (#293)
## Problem

Credentials lived in a top-level `_credentials` namespace, requiring
special
preservation logic in `clear_all()` and a separate key hierarchy from
the
platform data they belong to.

## Solution

Move credentials from `_credentials.<platform>` to
`<platform>._credentials`.
Migrate v1 caches on load, skip underscore-prefixed keys when
enumerating
contest IDs and summaries, and simplify `clear_all()` now that no
special
preservation is needed.

Stacked on #292.
2026-03-04 13:37:22 -05:00
Barrett Ruth
49e0ae3885
refactor(credentials): promote login/logout to top-level actions (#292)
## Problem

`:CP credentials login/logout/clear` is verbose and inconsistent with
other
actions that are all top-level (`:CP run`, `:CP submit`, etc.). The
clear-all
subcommand is also unnecessary since re-logging in overwrites existing
credentials.

## Solution

Replace `:CP credentials {login,logout,clear}` with `:CP login
[platform]`
and `:CP logout [platform]`. Remove the clear-all command and the
credentials
subcommand dispatch — login/logout are now regular actions routed
through the
standard action dispatcher.
2026-03-04 13:09:32 -05:00
Barrett Ruth
98ac0aa7a7
refactor(credentials): rename set/clear to login/logout/clear (#291)
## Problem

The `set` and `clear` subcommands don't clearly convey their intent —
`set`
reads like a generic setter rather than an auth action, and `clear`
overloads
single-platform and all-platform semantics in one subcommand.

## Solution

Rename `set` to `login`, split `clear` into `logout` (per-platform,
defaults
to active) and `clear` (all platforms).

New API:
- `:CP credentials login [platform]` — prompt and save credentials
- `:CP credentials logout [platform]` — remove credentials for one
platform
- `:CP credentials clear` — remove all stored credentials
2026-03-04 12:53:37 -05:00
Barrett Ruth
18a60da2d8
misc (#290)
fix atcoder :CP logins
propagate scraper error codes
2026-03-04 12:47:48 -05:00
baaaa95b27 ci: format 2026-03-04 00:50:21 -05:00
900fd70935 fix(edit): clean up buffers on close and support :w to save
Problem: closing the test editor left cp://test-N-* buffers alive,
causing E95 on reopen. The nofile buftype also rejected :w, which
was counterintuitive in an editable grid.

Solution: delete all test buffers in toggle_edit teardown. Switch
buftype to acwrite with a BufWriteCmd autocmd that persists test
cases and clears the modified flag. Hoist save_all_tests above
setup_keybindings so the autocmd closure can reference it.
2026-03-04 00:50:21 -05:00
f17eb32e8c fix: pass in index to :CP panel <n> 2026-03-04 00:30:39 -05:00
4f88b19a82 refactor(run): remove I/O view test navigation keymaps
Problem: <c-n>/<c-p> in the I/O view buffers required the cursor
to leave the source file to work, re-ran the solution on each
press, and gave no indication of which test was active. The
workflow is better served by :CP run <n> for a specific test or
:CP panel for full inspection.

Solution: remove navigate_test, next_test_key/prev_test_key config
options, and the associated current_test_index state field.
2026-03-04 00:26:22 -05:00
217476f5f3 fix(scraper): coerce vim.NIL precision to nil before cache write
Problem: vim.json.decode maps JSON null to vim.NIL (userdata), but
cache.set_test_cases validates precision as number|nil, causing a
type error on every scrape where precision is absent.

Solution: guard the precision field when building the callback
table, converting vim.NIL to nil.
2026-03-04 00:26:22 -05:00
488260f769 ci: format 2026-03-03 16:46:07 -05:00
a04702d87c refactor: replace :CP login with :CP credentials subcommand
Problem: :CP login was a poor API — no way to clear credentials without
raw Lua, and the single command didn't scale to multiple operations.

Solution: replace login with a :CP credentials subcommand following the
same pattern as :CP cache. :CP credentials set [platform] prompts and
saves; :CP credentials clear [platform] removes one or all platforms.
Add cache.clear_credentials(), rename login.lua to credentials.lua,
update parse/dispatch/tab-complete, and rewrite vimdoc accordingly.
2026-03-03 16:46:07 -05:00
3e0b7beabf feat: add :CP login command for explicit credential management
Problem: credentials were only set implicitly on first :CP submit.
There was no way to update wrong credentials, log out, or set
credentials ahead of time without editing the cache JSON manually.

Solution: add :CP login [platform] which always prompts for username
and password and overwrites any saved credentials for that platform.
Omitting the platform falls back to the active platform. Wire the
command through constants, parse_command, handle_command, and add
tab-completion (suggests platform names). Document in vimdoc under
the SUBMIT section and in the commands reference.
2026-03-03 16:28:54 -05:00
a08d1f0c5e refactor(submit): consolidate credentials into main cache file
Problem: credentials were stored in a separate file,
cp-nvim-credentials.json, alongside the main cp-nvim.json cache.
Two files for one plugin's persistent state was unnecessary.

Solution: add get_credentials/set_credentials to cache.lua, storing
credentials under _credentials[platform] in the shared cache. Update
clear_all() to preserve _credentials across cache wipes. Remove the
separate file, load_credentials, and save_credentials from submit.lua.
2026-03-03 16:24:12 -05:00
52cf54d05c docs: document new platforms, commands, and features
Problem: vimdoc only covered AtCoder, Codeforces, and CSES, and had no
entries for race, stress, or submit — all of which shipped in this
branch. The platform list was also stale and the workflow example
pointed users to the AtCoder website to submit manually.

Solution: add CodeChef, USACO, and Kattis to the supported platforms
list and platform-specific usage section (including Kattis's
dual single-problem/full-contest behavior). Document :CP stress,
:CP race, and :CP submit in the commands section, add their <Plug>
mappings, and add dedicated STRESS TESTING, RACE, and SUBMIT sections.
Update get_active_panel() to list its return values, add the
cp.race.status() API under the statusline section, and update the
workflow example step 8 to use :CP submit.
2026-03-03 16:02:09 -05:00
7e48ba05cf feat(kattis): rewrite scraper to support real contests
Problem: scrape_contest_list paginated the entire Kattis problem database
(3000+ problems) treating each as a "contest". scrape_contest_metadata
only handled single-problem access. stream_tests_for_category_async could
not fetch tests for multiple problems in a real contest.

Solution: replace the paginated problem loop with a single GET to
/contests that returns ~150 real timed contests. Add contest-aware path
to scrape_contest_metadata that fetches /contests/{id}/problems and
returns all problem slugs; fall back to single-problem path when the ID
is not a contest. Add _stream_single_problem helper and update
stream_tests_for_category_async to fan out concurrently over all contest
problem slugs before falling back to the single-problem path.
2026-03-03 16:02:09 -05:00
e79f992e0b fix: resolve lua typecheck warnings in race and scraper
Problem: luals flagged undefined-field on uv timer methods because
race_state.timer was untyped, and undefined-field on env_extra/stdin
because they were missing from the run_scraper opts annotation.

Solution: hoist race_state.timer into a typed local before the nil
check so luals can narrow through it; add env_extra and stdin to the
opts inline type in run_scraper.
2026-03-03 15:09:41 -05:00
de5a20c567 fix: resolve typecheck errors in cache, atcoder, cses, and usaco
Problem: lua typecheck flagged missing start_time field on ContestSummary;
ty flagged BeautifulSoup Tag/NavigableString union on csrf_input.get(),
a 3-tuple unpack where _extract_problem_info now returns 4 values in
cses.py, and an untyped list assignment in usaco.py.

Solution: add start_time? to ContestSummary LuaDoc, guard csrf_input
with hasattr check and type: ignore, unpack precision from
_extract_problem_info in cses.py callers, and use cast() in usaco.py.
2026-03-03 15:09:41 -05:00
bad219e578 ci: format 2026-03-03 15:09:41 -05:00
ad90d564ca fix(views): fix interactive guard logic and add stress panel support
Problem: toggle_interactive() had its condition inverted — it blocked
:CP interact on non-interactive problems while showing the message "This
problem is interactive", and passed through on interactive ones. The
panel guard in toggle_panel() was also missing a nil-check on
contest_data.index_map, which could crash if the index map was absent.

Solution: invert the toggle_interactive() guard to match the symmetrical
pattern in toggle_view(), fix the error message to say "not interactive",
and add the missing index_map guard. Also handle the stress panel type
in M.disable() so :CP stress can be toggled off.
2026-03-03 15:09:41 -05:00
bfa2cf893c feat: wire race, stress, and submit commands and keymaps
Add command parsing and dispatch for :CP race, :CP race stop, :CP stress,
and :CP submit. Add tab-completion for race (platform/contest/--lang),
stress (cwd executables at arg 2 and 3), and race stop. Add
<Plug>(cp-stress), <Plug>(cp-submit), and <Plug>(cp-race-stop) keymaps.
2026-03-03 15:09:41 -05:00
a75694e9e0 feat(submit): add solution submission UI
Add submit.lua that reads credentials from a local JSON store (prompting
via vim.ui.input/inputsecret on first use), reads the source file, and
delegates to scraper.submit(). Add language_ids.py with platform-to-
language-ID mappings for atcoder, codeforces, and cses.
2026-03-03 15:09:41 -05:00
39b7b3d83f feat(stress): add stress test loop
Add stress.lua that auto-detects or accepts generator and brute solution
files, compiles C++ if needed, and launches scripts/stress.py in a
terminal buffer with session save/restore and cleanup autocmds.

Add scripts/stress.py as a standalone loop that runs generator → brute →
candidate, comparing outputs and exiting on the first mismatch.
2026-03-03 15:09:41 -05:00
f5c1b978a2 feat(race): add contest countdown timer
Add race.lua with a 1-second vim.uv timer that counts down to a contest
start time and auto-calls setup.setup_contest() at T=0. Exposes
M.start(), M.stop(), and M.status() for command dispatch and statusline
integration.
2026-03-03 15:09:41 -05:00
4e8da84882 feat(platforms): add kattis and usaco scrapers
Add KattisScraper and USACOScraper with contest list, metadata, and
test case fetching. Register kattis and usaco in PLATFORMS,
PLATFORM_DISPLAY_NAMES, and default platform configs.
2026-03-03 15:09:41 -05:00
90bd13580b feat(scraper): add precision extraction, start_time, and submit support
Problem: problem pages contain floating-point precision requirements and
contest start timestamps that were not being extracted or stored. The
submit workflow also needed a foundation in the scraper layer.

Solution: add extract_precision() to base.py and propagate through all
scrapers into cache. Add start_time to ContestSummary and extract it
from AtCoder and Codeforces. Add SubmitResult model, abstract submit()
method, submit CLI case with get_language_id() resolution, stdin/env_extra
support in run_scraper, and a full AtCoder submit implementation; stub
the remaining platforms.
2026-03-03 15:09:41 -05:00
865e3b5928 refactor: rename epsilon to precision in runner
Problem: the tolerance field for floating-point comparison was named
`epsilon`, which is an implementation detail, not the user-visible concept.

Solution: rename to `precision` in run.lua type annotations, internal
variables, and comparison logic.
2026-03-03 15:09:41 -05:00
dc9cb10f3a doc: update 2026-03-03 00:54:13 -05:00
72ea6249f4 ci: format 2026-03-03 00:46:59 -05:00
0e88e0f182 fix(utils): skip uv python setup on NixOS
Problem: uv downloads glibc-linked Python binaries that NixOS cannot
run, causing setup_python_env to fail with exit status 127.

Solution: detect NixOS via /etc/NIXOS and bypass the uv sync path,
falling through directly to nix-based Python discovery.
2026-03-03 00:46:59 -05:00
add022af8c refactor(hooks): replace flat hooks API with setup/on namespaces
Problem: the hooks API conflated distinct lifecycle scopes under a flat
table with inconsistent naming (setup_code, before_run, setup_io_input),
making it hard to reason about when each hook fires.

Solution: introduce two namespaces — hooks.setup.{contest,code,io} for
one-time initialization and hooks.on.{enter,run,debug} for recurring
events. hooks.setup.contest fires once when a contest dir is newly
created; hooks.on.enter is registered as a buffer-scoped BufEnter
autocmd and fires immediately after setup.code. The provisional buffer
setup_code callsite is removed as it ran on an unresolved temp buffer.
2026-03-03 00:46:59 -05:00
6a395af98f feat(config): add templates.cursor_marker for post-template cursor placement
Problem: after apply_template writes a file's content to the buffer,
cursor positioning was left entirely to the user's setup_code hook,
forcing everyone to reimplement the same placeholder-stripping logic.

Solution: add an optional templates.cursor_marker config key. When set,
apply_template scans the written lines for the marker, strips it, and
positions the cursor there via bufwinid so it works in both the
provisional and existing-file paths.
2026-03-03 00:46:59 -05:00
d3324aafa3 fix(config): propagate template through platform overrides
Problem: CpPlatformOverrides lacked a template field and merge_lang()
never copied ov.template into the effective language config, so
per-platform template overrides were silently dropped.

Solution: add template? to CpPlatformOverrides and forward it in
merge_lang(), matching how extension is handled.
2026-03-03 00:46:59 -05:00
24b088e8e9 style(runner): add luacats to compare_outputs 2026-02-26 23:02:40 -05:00
e685a8089f feat: add epsilon tolerance for floating-point output comparison
Problem: output comparison used exact string equality after whitespace
normalisation, causing correct solutions to fail on problems where
floating-point answers are accepted within a tolerance (e.g. 1e-6).

Solution: add an optional ui.panel.epsilon config value. When set,
actual and expected output are compared token-by-token: numeric tokens
are compared with math.abs(a - b) <= epsilon, non-numeric tokens fall
back to exact string equality. Per-problem epsilon can also be stored
in the cache and takes precedence over the global default.
2026-02-26 23:00:35 -05:00
84d12758c2 style(setup): apply stylua formatting 2026-02-26 22:57:39 -05:00
2c25ec616a feat: add per-language template file support
Problem: new solution files were always created empty, requiring users
to manually paste boilerplate or rely on editor snippets that fire
outside cp.nvim's control.

Solution: add an optional template field to the language config. When
set to a file path, its contents are written into every newly created
solution buffer before the setup_code hook runs. Existing files are
never overwritten.
2026-02-26 22:57:39 -05:00
ce5648f9cf docs: add statusline integration recipes
Problem: cp.nvim exposed no documentation showing how to integrate its
runtime state into a statusline. Users had to discover the state module
API by reading source.

Solution: add a STATUSLINE INTEGRATION section to the vimdoc with a
state API reference and recipes for vanilla statusline, lualine, and
heirline. Also anchors the *cp.State* help tag referenced in prose
elsewhere in the doc.
2026-02-26 22:56:36 -05:00
585cf2a077 feat(runner): run test cases in parallel
Problem: test cases were executed sequentially, each waiting for the
previous process to finish before starting the next. On problems with
many test cases this meant wall-clock run time scaled linearly.

Solution: fan out all test case processes simultaneously. A remaining
counter fires on_done once all callbacks have returned. on_each is
called per completion as before; callers that pass on_each ignore its
arguments so the index semantics change is non-breaking.
2026-02-26 22:55:50 -05:00
81f5273840 chore: convert .luarc.json to nested format and add busted library
Problem: .luarc.json used the flat dotted-key format which is not the
canonical LuaLS schema. The busted library was also missing, so LuaLS
could not resolve types in test files.

Solution: rewrite .luarc.json using nested objects and add
${3rd}/busted/library to workspace.library.
2026-02-26 22:47:05 -05:00
d274e0c117 fix(cache): replace stale M._cache field with get_raw_cache accessor
Problem: M._cache = cache_data captured the initial empty table reference
at module load time. After M.load() reassigns cache_data to the decoded
JSON, M._cache is permanently stale and returns the wrong table.

Solution: remove the field assignment and expose get_raw_cache() which
closes over cache_data and always returns the current table.
2026-02-26 22:46:06 -05:00
3cb872a65f fix: replace deprecated vim.loop with vim.uv
Problem: vim.loop is deprecated since Neovim 0.10 in favour of vim.uv.
Five call sites across scraper.lua, setup.lua, utils.lua, and health.lua
still referenced the old alias.

Solution: replace every vim.loop reference with vim.uv directly.
2026-02-26 22:45:07 -05:00
4ccab9ee1f
fix(config): add bit to ignored filetypes 2026-02-26 19:09:16 -05:00
48c08825b2
ci: add missing packages 2026-02-23 18:16:22 -05:00
4d58db8520
ci: nix config migration 2026-02-23 18:04:17 -05:00
591f70a237
build(flake): add lua-language-server to devShell
Problem: lua-language-server is not available in the dev shell, making
it impossible to run local type-checking diagnostics.

Solution: add lua-language-server to the devShell packages.
2026-02-23 17:37:47 -05:00