From 916c9174a61ee413f2c1cf5ef9b6cba19781f56a Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:37:33 -0500 Subject: [PATCH] fix(commands): canonicalize CF contest IDs and auto-restore on action (#314) ## Problem Two gaps in `commands/init.lua`. Codeforces contest IDs were passed through raw, so full URLs (e.g. `https://codeforces.com/contest/1933/problem/A`) or problem IDs with trailing letters (e.g. `1933A`) caused scraper URL construction to fail. Separately, action commands like `:CP run` silently failed when invoked with no active contest instead of attempting to recover from the current file's cached state. ## Solution Add `canonicalize_cf_contest` to normalize URLs and strip trailing problem letters in `parse_command`. Add a guard in `handle_command` that calls `restore_from_current_file()` before dispatching any contest-requiring action when no platform is active, returning early only if no cached state is found. Closes #306. Closes #308. --- lua/cp/commands/init.lua | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/lua/cp/commands/init.lua b/lua/cp/commands/init.lua index 4c594bd..de6d307 100644 --- a/lua/cp/commands/init.lua +++ b/lua/cp/commands/init.lua @@ -25,6 +25,20 @@ local actions = constants.ACTIONS ---@field language? string ---@field subcommand? string +---@param str string +---@return string +local function canonicalize_cf_contest(str) + local id = str:match('/contest/(%d+)') or str:match('/problemset/problem/(%d+)') + if id then + return id + end + local num = str:match('^(%d+)[A-Za-z]') + if num then + return num + end + return str +end + --- Turn raw args into normalized structure to later dispatch ---@param args string[] The raw command-line mode args ---@return ParsedCommand @@ -246,16 +260,24 @@ local function parse_command(args) if args[2] == 'login' or args[2] == 'logout' then return { type = 'action', action = args[2], platform = first } end + local contest = args[2] + if first == 'codeforces' then + contest = canonicalize_cf_contest(contest) + end return { type = 'contest_setup', platform = first, - contest = args[2], + contest = contest, } elseif #args == 4 and args[3] == '--lang' then + local contest = args[2] + if first == 'codeforces' then + contest = canonicalize_cf_contest(contest) + end return { type = 'contest_setup', platform = first, - contest = args[2], + contest = contest, language = args[4], } else @@ -296,6 +318,15 @@ function M.handle_command(opts) local restore = require('cp.restore') restore.restore_from_current_file() elseif cmd.type == 'action' then + local CONTEST_ACTIONS = + { 'run', 'panel', 'edit', 'interact', 'stress', 'submit', 'next', 'prev', 'pick' } + if vim.tbl_contains(CONTEST_ACTIONS, cmd.action) and not state.get_platform() then + local restore = require('cp.restore') + if not restore.restore_from_current_file() then + return + end + end + local setup = require('cp.setup') local ui = require('cp.ui.views')