Commit graph

1017 commits

Author SHA1 Message Date
38cd0482f0
ci: remove unused var 2026-03-05 10:37:13 -05:00
3ecd200da7
refactor(codeforces): use separate fetches for login and submit
Problem: the single `do_login_and_submit` page action navigated between
pages within one `session.fetch` call, which was fragile and couldn't
leverage `solve_cloudflare` for the Turnstile gate on the submit page.
The submit button click also blocked on navigation completion, causing
timeouts when CF was slow to process.

Solution: split into three separate `session.fetch` calls (homepage
login check, `/enter` login, `/contest/{id}/submit`) with
`solve_cloudflare=True` on login and submit. Use `no_wait_after=True`
on the submit click with a doubled nav timeout. Extract `span.error`
text on submit failure instead of a generic timeout message.
2026-03-05 10:35:36 -05:00
7368747946
perf(atcoder): bail out early from _solve_turnstile when no iframe present
Problem: `_solve_turnstile` looped 6 times with ~20s per iteration
(15s bounding_box timeout + 5s wait_for_function) when no Turnstile
iframe existed on the page, causing a 120-second delay on pages that
don't require Turnstile verification.

Solution: check for existing token and iframe presence before entering
the retry loop. `iframe_loc.count()` returns immediately when no
matching elements exist, avoiding the expensive timeout cascade.
2026-03-05 10:35:27 -05:00
1afe41103f
ci: format 2026-03-05 01:39:59 -05:00
2cdde85d36 refactor: centralize timeout constants in scrapers/timeouts.py
Problem: each scraper defined its own timeout constants
(`TIMEOUT_S`, `TIMEOUT_SECONDS`) with inconsistent values (15s vs 30s)
and browser timeouts were scattered as magic numbers (60000, 15000,
5000, 500).

Solution: introduce `scrapers/timeouts.py` with named constants for
HTTP requests, browser session/navigation/element/turnstile/settle
timeouts, and submission polling. All six scrapers now import from
the shared module.
2026-03-05 01:35:40 -05:00
f4055b071b feat(codeforces): implement submit via headless browser
Problem: Codeforces submit was a stub returning "not yet implemented".

Solution: use StealthySession (same pattern as AtCoder) to handle
Cloudflare Turnstile on the login page, fill credentials, navigate to
the contest submit form, upload source via file input, and cache
cookies at `~/.cache/cp-nvim/codeforces-cookies.json` so repeat
submits skip the login entirely. Uses a single browser page action
that checks for the submit form before navigating, avoiding redundant
page loads and Turnstile challenges.
2026-03-05 01:31:30 -05:00
027fae65a4 perf(cses): cache API token across submits
Problem: every `:CP submit` on CSES ran the full 5-request login flow
(~1.5 s overhead) even when the token from a previous submit was still
valid.

Solution: persist the API token in credentials via a `credentials`
ndjson event. On subsequent submits, validate the cached token with a
single GET before falling back to the full login.
2026-03-05 01:18:16 -05:00
08593d828d docs: add table of contents to vimdoc 2026-03-05 01:18:09 -05:00
Barrett Ruth
e9f72dfbbc
feat(cses): implement submit via REST API (#299)
## 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 — no browser dependencies needed. Tested end-to-end with a real
CSES account (verdict: `ACCEPTED`).

Also updates `scraper.lua` to pass the full ndjson event object to
`on_status` and handle `credentials` events for future platform use.
2026-03-05 01:07:57 -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