Compare commits

..

4 commits

Author SHA1 Message Date
871fa321bd
ci: format 2026-03-05 18:29:07 -05:00
4381b9f861 fix(completion): deduplicate pick and cache in second-arg candidates
Problem: `pick` and `cache` were inserted unconditionally into the
candidate list, then inserted again via `vim.list_extend(candidates, actions)`
when a contest was active, producing duplicates in the completion menu.

Solution: filter `pick` and `cache` out of the `actions` extend since
they are already present in the always-visible candidate set.
2026-03-05 18:23:50 -05:00
73be6b8277 fix(setup): cancel all active panels on problem navigation
Problem: `navigate_problem` only called `views.disable()` for the `'run'`
panel; interactive and stress terminals were left alive when stepping
through problems with `:CP next/prev`. In-flight `run_io_view` callbacks
were also not invalidated since `is_new_contest` stays false for same-contest
navigation, so the generation-counter guard in `setup_contest` never fired.

Solution: call `cancel_io_view()` unconditionally in `navigate_problem`
and expand the panel dispatch to cover `'interactive'` and `'stress'`
alongside `'run'`, mirroring the contest-switch logic in `setup_contest`.
2026-03-05 18:23:39 -05:00
9cf9cd8441 refactor(commands): replace hardcoded CONTEST_ACTIONS with requires_context field
Problem: the local CONTEST_ACTIONS list in `handle_command` was a manually
maintained subset of ACTIONS that could drift — `pick` was already in it
incorrectly, requiring a separate hotfix.

Solution: encode `requires_context` on `ParsedCommand` at each parse-site
in `parse_command`, where the action's semantics are already known. `handle_command`
now checks `cmd.requires_context` directly, eliminating the parallel list.
2026-03-05 18:23:26 -05:00
3 changed files with 40 additions and 11 deletions

View file

@ -11,6 +11,7 @@ local actions = constants.ACTIONS
---@field type string
---@field error string?
---@field action? string
---@field requires_context? boolean
---@field message? string
---@field contest? string
---@field platform? string
@ -71,7 +72,7 @@ local function parse_command(args)
end
elseif first == 'race' then
if args[2] == 'stop' then
return { type = 'action', action = 'race_stop' }
return { type = 'action', action = 'race_stop', requires_context = false }
end
if not args[2] or not args[3] then
return {
@ -86,6 +87,7 @@ local function parse_command(args)
return {
type = 'action',
action = 'race',
requires_context = false,
platform = args[2],
contest = args[3],
language = language,
@ -93,14 +95,20 @@ local function parse_command(args)
elseif first == 'interact' then
local inter = args[2]
if inter and inter ~= '' then
return { type = 'action', action = 'interact', interactor_cmd = inter }
return {
type = 'action',
action = 'interact',
requires_context = true,
interactor_cmd = inter,
}
else
return { type = 'action', action = 'interact' }
return { type = 'action', action = 'interact', requires_context = true }
end
elseif first == 'stress' then
return {
type = 'action',
action = 'stress',
requires_context = true,
generator_cmd = args[2],
brute_cmd = args[3],
}
@ -119,7 +127,7 @@ local function parse_command(args)
end
test_index = idx
end
return { type = 'action', action = 'edit', test_index = test_index }
return { type = 'action', action = 'edit', requires_context = true, test_index = test_index }
elseif first == 'run' or first == 'panel' then
local debug = false
local test_indices = nil
@ -232,10 +240,22 @@ local function parse_command(args)
return {
type = 'action',
action = first,
requires_context = true,
test_indices = test_indices,
debug = debug,
mode = mode,
}
elseif first == 'pick' then
local language = nil
if #args >= 3 and args[2] == '--lang' then
language = args[3]
elseif #args >= 2 and args[2] ~= nil and args[2]:sub(1, 2) ~= '--' then
return {
type = 'error',
message = ("Unknown argument '%s' for action '%s'"):format(args[2], first),
}
end
return { type = 'action', action = 'pick', requires_context = false, language = language }
else
local language = nil
if #args >= 3 and args[2] == '--lang' then
@ -246,7 +266,7 @@ local function parse_command(args)
message = ("Unknown argument '%s' for action '%s'"):format(args[2], first),
}
end
return { type = 'action', action = first, language = language }
return { type = 'action', action = first, requires_context = true, language = language }
end
end
@ -258,7 +278,7 @@ local function parse_command(args)
}
elseif #args == 2 then
if args[2] == 'login' or args[2] == 'logout' then
return { type = 'action', action = args[2], platform = first }
return { type = 'action', action = args[2], requires_context = false, platform = first }
end
local contest = args[2]
if first == 'codeforces' then
@ -318,9 +338,7 @@ 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
if cmd.requires_context and not state.get_platform() then
local restore = require('cp.restore')
if not restore.restore_from_current_file() then
return

View file

@ -477,9 +477,15 @@ function M.navigate_problem(direction, language)
logger.log(('navigate_problem: %s -> %s'):format(current_problem_id, problems[new_index].id))
local views = require('cp.ui.views')
views.cancel_io_view()
local active_panel = state.get_active_panel()
if active_panel == 'run' then
require('cp.ui.views').disable()
views.disable()
elseif active_panel == 'interactive' then
views.cancel_interactive()
elseif active_panel == 'stress' then
require('cp.stress').cancel()
end
local lang = nil

View file

@ -44,7 +44,12 @@ end, {
table.insert(candidates, 'cache')
table.insert(candidates, 'pick')
if platform and contest_id then
vim.list_extend(candidates, actions)
vim.list_extend(
candidates,
vim.tbl_filter(function(a)
return a ~= 'pick' and a ~= 'cache'
end, actions)
)
local cache = require('cp.cache')
cache.load()
local contest_data = cache.get_contest_data(platform, contest_id)