Commit graph

1019 commits

Author SHA1 Message Date
e13eed0c4d
fix(utils): redraw before submit env notify; simplify message 2026-03-04 19:26:35 -05:00
690469bd99
fix(utils): discover nix submitEnv dynamically in dev checkout
Problem: In a dev checkout, `_nix_submit_cmd` is nil (only baked in by
the nix derivation). The uv fallback fails with code 127 on NixOS because
`uv` is not a bare system binary — it's only available via the FHS-wrapped
`cp-nvim-submit` script produced by `mkSubmitEnv`.

Solution: Add `discover_nix_submit_cmd` mirroring `discover_nix_python`:
runs `nix build #submitEnv --no-link --print-out-paths`, caches the result
in `stdpath('cache')/cp-nvim/nix-submit`, and sets `_nix_submit_cmd`.
`run_scraper` calls `setup_nix_submit_env()` before spawning submit.
2026-03-04 19:22:57 -05:00
128ff04621
fix(utils): always use uv for submit when nix submit cmd unavailable
Problem: In a dev checkout on NixOS, `_nix_submit_cmd` is nil but
`_nix_python` is set from the discovery cache. `get_python_submit_cmd`
fell through to `get_python_cmd`, which returned the nix-built Python —
a derivation that deliberately excludes `scrapling`.

Solution: Fall back to `uv run` instead of `get_python_cmd` so submit
always gets a full dependency environment when `_nix_submit_cmd` is nil.
2026-03-04 19:19:33 -05:00
2854f5bb23
fix(atcoder): suppress ty unresolved-import for optional deps 2026-03-04 19:04:43 -05:00
574e6b3a79
ci: format 2026-03-04 19:03:54 -05:00
9272a9660e
feat(submit): show progress notifications during submit
Problem: `M.submit` gave no UI feedback between credential entry and
final result, leaving users staring at a silent hang for 10-30s.

Solution: Add `STATUS_MSGS` map and emit an immediate `vim.notify` on
submit start. Pass an `on_status` handler to `scraper.submit` that fires
a notification for each phase (`checking_login`, `logging_in`, etc.).
2026-03-04 19:02:37 -05:00
ecbe60cbd8
feat(scraper): add stdin pipe to NDJSON spawn; stream submit with on_status
Problem: The NDJSON spawn path had no stdin support, so `M.submit` used
one-shot `vim.system()` with no live feedback. Status events from the
scraper were never surfaced to Neovim.

Solution: Conditionally create a `stdin_pipe` in the NDJSON path and
write `opts.stdin` after spawn. Switch `M.submit` to `ndjson=true`; route
`ev.status` events to a new `on_status` callback and `ev.success` to the
existing `callback`. A `done` flag prevents double-callback on crash.
2026-03-04 19:02:37 -05:00
6faf9c7537
refactor(atcoder): extract submit helpers to module level; emit status events
Problem: `_submit_sync` was a deeply nested closure containing
`_solve_turnstile` and the browser-install block as further nesting.
Status events went to stderr, which `run_scraper()` silently discards.

Solution: Extract `_TURNSTILE_JS`, `_solve_turnstile`, `_ensure_browser`,
and `_submit_headless` to module level. Status events (`installing_browser`,
`checking_login`, `logging_in`, `submitting`) now print to stdout as NDJSON.
`submit()` delegates to `asyncio.to_thread(_submit_headless, ...)`.
2026-03-04 19:02:37 -05:00
Barrett Ruth
886a99eb95
Merge branch 'main' into refactor/cache-credentials-nesting 2026-03-04 14:49:14 -05:00
90f7e9b20b
ci: scripts + format 2026-03-04 13:52:20 -05:00
8c8e49d75c
ci: scripts + format 2026-03-04 13:51:56 -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
f2e312f860
refactor(cache): nest credentials under platform namespace
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.
2026-03-04 13:15:47 -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
29cb81eca2
refactor(credentials): promote login/logout to top-level actions
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:03:40 -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