diff --git a/doc/cp.nvim.txt b/doc/cp.nvim.txt index c8b17ce..2956d4b 100644 --- a/doc/cp.nvim.txt +++ b/doc/cp.nvim.txt @@ -102,6 +102,27 @@ COMMANDS *cp-commands* :CP C --lang python < + Edit Commands ~ + :CP edit [n] + Open grid test editor showing all test cases. + Tests displayed as 2×N grid (2 rows, N columns): + • Top row: Test inputs (editable) + • Bottom row: Expected outputs (editable) + + Optional [n]: Jump cursor to test n's input buffer + + Changes saved to both cache and disk on exit, + taking effect immediately in :CP run and CLI. + + Keybindings: + q Save all and exit editor + Normal window navigation + + Examples: > + :CP edit " Edit all tests + :CP edit 3 " Edit all, start at test 3 +< + State Restoration ~ :CP Restore state from current file. Automatically detects platform, contest, problem, @@ -109,10 +130,16 @@ COMMANDS *cp-commands* switching files to restore your CP environment. Cache Commands ~ - :CP cache clear [contest] - Clear the cache data for the specified contest, - or all contests if none specified. - + :CP cache clear [platform] [contest] + Clear cache data at different granularities: + • No args: Clear all cached data + • [platform]: Clear all data for a platform + • [platform] [contest]: Clear specific contest + Examples: > + :CP cache clear + :CP cache clear codeforces + :CP cache clear codeforces 1848 +< :CP cache read View the cache in a pretty-printed lua buffer. Exit with q. @@ -563,14 +590,18 @@ Input: |VerdictFormatData| table with test results Output: |VerdictFormatResult| table with formatted line and optional highlights *VerdictFormatData* - {index} (integer) Test case number - {status} (table) { text: string, highlight_group: string } - {time_ms} (number) Execution time in milliseconds - {time_limit_ms} (number) Time limit in milliseconds - {memory_mb} (number) Peak memory usage in megabytes - {memory_limit_mb} (number) Memory limit in megabytes - {exit_code} (integer) Process exit code - {signal} (string|nil) Signal name for crashes (e.g. "SIGSEGV") + {index} (integer) Test case number + {status} (table) { text: string, highlight_group: string } + {time_ms} (number) Execution time in milliseconds + {time_limit_ms} (number) Time limit in milliseconds + {memory_mb} (number) Peak memory usage in megabytes + {memory_limit_mb} (number) Memory limit in megabytes + {exit_code} (integer) Process exit code + {signal} (string|nil) Signal name for crashes (e.g. "SIGSEGV") + {time_actual_width} (integer|nil) Dynamic width for time value alignment + {time_limit_width} (integer|nil) Dynamic width for time limit alignment + {mem_actual_width} (integer|nil) Dynamic width for memory value alignment + {mem_limit_width} (integer|nil) Dynamic width for memory limit alignment *VerdictFormatResult* {line} (string) The formatted verdict line diff --git a/lua/cp/commands/cache.lua b/lua/cp/commands/cache.lua index e85e7ea..aba8bf5 100644 --- a/lua/cp/commands/cache.lua +++ b/lua/cp/commands/cache.lua @@ -39,7 +39,21 @@ function M.handle_cache_command(cmd) vim.api.nvim_set_current_buf(buf) elseif cmd.subcommand == 'clear' then cache.load() - if cmd.platform then + if cmd.platform and cmd.contest then + if vim.tbl_contains(platforms, cmd.platform) then + cache.clear_contest_data(cmd.platform, cmd.contest) + logger.log( + ("Cache cleared for %s contest '%s'"):format( + constants.PLATFORM_DISPLAY_NAMES[cmd.platform], + cmd.contest + ), + vim.log.levels.INFO, + true + ) + else + logger.log(("Unknown platform '%s'."):format(cmd.platform), vim.log.levels.ERROR) + end + elseif cmd.platform then if vim.tbl_contains(platforms, cmd.platform) then cache.clear_platform(cmd.platform) logger.log( diff --git a/lua/cp/commands/init.lua b/lua/cp/commands/init.lua index f48727d..b585a5b 100644 --- a/lua/cp/commands/init.lua +++ b/lua/cp/commands/init.lua @@ -40,10 +40,12 @@ local function parse_command(args) end if vim.tbl_contains({ 'clear', 'read' }, subcommand) then local platform = args[3] + local contest = args[4] return { type = 'cache', subcommand = subcommand, platform = platform, + contest = contest, } else return { type = 'error', message = 'unknown cache subcommand: ' .. subcommand } @@ -55,6 +57,22 @@ local function parse_command(args) else return { type = 'action', action = 'interact' } end + elseif first == 'edit' then + local test_index = nil + if #args >= 2 then + local idx = tonumber(args[2]) + if not idx then + return { + type = 'error', + message = ("Invalid argument '%s': expected test number"):format(args[2]), + } + end + if idx < 1 or idx ~= math.floor(idx) then + return { type = 'error', message = ("'%s' is not a valid test index"):format(idx) } + end + test_index = idx + end + return { type = 'action', action = 'edit', test_index = test_index } elseif first == 'run' or first == 'panel' then local debug = false local test_index = nil @@ -189,6 +207,9 @@ function M.handle_command(opts) elseif cmd.action == 'pick' then local picker = require('cp.commands.picker') picker.handle_pick_action(cmd.language) + elseif cmd.action == 'edit' then + local edit = require('cp.ui.edit') + edit.toggle_edit(cmd.test_index) end elseif cmd.type == 'problem_jump' then local platform = state.get_platform() diff --git a/lua/cp/config.lua b/lua/cp/config.lua index 46b13b4..764f324 100644 --- a/lua/cp/config.lua +++ b/lua/cp/config.lua @@ -43,6 +43,10 @@ ---@field memory_limit_mb number ---@field exit_code integer ---@field signal string|nil +---@field time_actual_width? integer +---@field time_limit_width? integer +---@field mem_actual_width? integer +---@field mem_limit_width? integer ---@class VerdictHighlight ---@field col_start integer diff --git a/lua/cp/constants.lua b/lua/cp/constants.lua index b19e06b..9d1f0cc 100644 --- a/lua/cp/constants.lua +++ b/lua/cp/constants.lua @@ -1,7 +1,7 @@ local M = {} M.PLATFORMS = { 'atcoder', 'codeforces', 'cses' } -M.ACTIONS = { 'run', 'panel', 'next', 'prev', 'pick', 'cache', 'interact' } +M.ACTIONS = { 'run', 'panel', 'next', 'prev', 'pick', 'cache', 'interact', 'edit' } M.PLATFORM_DISPLAY_NAMES = { atcoder = 'AtCoder', diff --git a/lua/cp/helpers.lua b/lua/cp/helpers.lua index e31b6f9..57f87ca 100644 --- a/lua/cp/helpers.lua +++ b/lua/cp/helpers.lua @@ -51,17 +51,28 @@ end ---@param data VerdictFormatData ---@return VerdictFormatResult function M.default_verdict_formatter(data) - local time_data = string.format('%.2f', data.time_ms) .. '/' .. data.time_limit_ms - local mem_data = string.format('%.0f', data.memory_mb) - .. '/' - .. string.format('%.0f', data.memory_limit_mb) + local time_actual = string.format('%.2f', data.time_ms) + local time_limit = tostring(data.time_limit_ms) + local mem_actual = string.format('%.0f', data.memory_mb) + local mem_limit = string.format('%.0f', data.memory_limit_mb) local exit_str = data.signal and string.format('%d (%s)', data.exit_code, data.signal) or tostring(data.exit_code) + local time_actual_w = data.time_actual_width or 6 + local time_limit_w = data.time_limit_width or 4 + local mem_actual_w = data.mem_actual_width or 3 + local mem_limit_w = data.mem_limit_width or 3 + local test_num_part = 'Test ' .. data.index .. ':' local status_part = M.pad_right(data.status.text, 3) - local time_part = time_data .. ' ms' - local mem_part = mem_data .. ' MB' + local time_part = M.pad_left(time_actual, time_actual_w) + .. '/' + .. M.pad_left(time_limit, time_limit_w) + .. ' ms' + local mem_part = M.pad_left(mem_actual, mem_actual_w) + .. '/' + .. M.pad_left(mem_limit, mem_limit_w) + .. ' MB' local exit_part = 'exit: ' .. exit_str local line = test_num_part diff --git a/lua/cp/setup.lua b/lua/cp/setup.lua index 991242e..b7173e1 100644 --- a/lua/cp/setup.lua +++ b/lua/cp/setup.lua @@ -244,7 +244,12 @@ function M.setup_problem(problem_id, language) if vim.api.nvim_buf_is_valid(prov.bufnr) then vim.api.nvim_buf_set_name(prov.bufnr, source_file) vim.bo[prov.bufnr].swapfile = true - vim.cmd(string.format('silent keepalt noautocmd write! %s', vim.fn.fnameescape(source_file))) + -- selene: allow(mixed_table) + vim.cmd.write({ + vim.fn.fnameescape(source_file), + bang = true, + mods = { silent = true, noautocmd = true, keepalt = true }, + }) state.set_solution_win(vim.api.nvim_get_current_win()) if config.hooks and config.hooks.setup_code and not vim.b[prov.bufnr].cp_setup_done then local ok = pcall(config.hooks.setup_code, state) diff --git a/lua/cp/ui/edit.lua b/lua/cp/ui/edit.lua new file mode 100644 index 0000000..a19d06d --- /dev/null +++ b/lua/cp/ui/edit.lua @@ -0,0 +1,246 @@ +local M = {} + +local cache = require('cp.cache') +local config_module = require('cp.config') +local helpers = require('cp.helpers') +local logger = require('cp.log') +local state = require('cp.state') +local utils = require('cp.utils') + +---@class TestBufferPair +---@field input_buf integer +---@field expected_buf integer +---@field input_win integer +---@field expected_win integer + +---@class EditState +---@field test_buffers TestBufferPair[] +---@field test_cases TestCase[] +---@field constraints ProblemConstraints? + +---@type EditState? +local edit_state = nil + +local function setup_keybindings(buf) + vim.keymap.set('n', 'q', function() + M.toggle_edit() + end, { buffer = buf, silent = true, desc = 'Save and exit test editor' }) +end + +local function load_test_into_buffer(test_index) + if not edit_state then + return + end + + local tc = edit_state.test_cases[test_index] + local pair = edit_state.test_buffers[test_index] + + if not tc or not pair then + return + end + + local input_lines = vim.split(tc.input or '', '\n', { plain = true, trimempty = false }) + vim.api.nvim_buf_set_lines(pair.input_buf, 0, -1, false, input_lines) + + local expected_lines = vim.split(tc.expected or '', '\n', { plain = true, trimempty = false }) + vim.api.nvim_buf_set_lines(pair.expected_buf, 0, -1, false, expected_lines) + + vim.api.nvim_buf_set_name(pair.input_buf, string.format('cp://test-%d-input', test_index)) + vim.api.nvim_buf_set_name(pair.expected_buf, string.format('cp://test-%d-expected', test_index)) +end + +local function save_all_tests() + if not edit_state then + return + end + + local platform = state.get_platform() + local contest_id = state.get_contest_id() + local problem_id = state.get_problem_id() + + if not platform or not contest_id or not problem_id then + return + end + + for i, pair in ipairs(edit_state.test_buffers) do + if + vim.api.nvim_buf_is_valid(pair.input_buf) and vim.api.nvim_buf_is_valid(pair.expected_buf) + then + local input_lines = vim.api.nvim_buf_get_lines(pair.input_buf, 0, -1, false) + local expected_lines = vim.api.nvim_buf_get_lines(pair.expected_buf, 0, -1, false) + + edit_state.test_cases[i].input = table.concat(input_lines, '\n') + edit_state.test_cases[i].expected = table.concat(expected_lines, '\n') + end + end + + cache.set_test_cases( + platform, + contest_id, + problem_id, + edit_state.test_cases, + edit_state.constraints and edit_state.constraints.timeout_ms or 0, + edit_state.constraints and edit_state.constraints.memory_mb or 0, + false + ) + + local config = config_module.get_config() + local base_name = config.filename and config.filename(platform, contest_id, problem_id, config) + or config_module.default_filename(contest_id, problem_id) + + vim.fn.mkdir('io', 'p') + + for i, tc in ipairs(edit_state.test_cases) do + local input_file = string.format('io/%s.%d.cpin', base_name, i) + local expected_file = string.format('io/%s.%d.cpout', base_name, i) + + local input_content = (tc.input or ''):gsub('\r', '') + local expected_content = (tc.expected or ''):gsub('\r', '') + + vim.fn.writefile(vim.split(input_content, '\n', { trimempty = true }), input_file) + vim.fn.writefile(vim.split(expected_content, '\n', { trimempty = true }), expected_file) + end + + logger.log('Saved all test cases') +end + +function M.toggle_edit(test_index) + if edit_state then + save_all_tests() + edit_state = nil + + local saved = state.get_saved_session() + if saved then + vim.fn.delete(saved) + state.set_saved_session(nil) + end + + vim.cmd.only({ mods = { silent = true } }) + local source_file = state.get_source_file() + if source_file and vim.fn.filereadable(source_file) == 1 then + vim.cmd.edit(source_file) + end + + local views = require('cp.ui.views') + views.ensure_io_view() + + logger.log('Closed test editor') + return + end + + local platform, contest_id, problem_id = + state.get_platform(), state.get_contest_id(), state.get_problem_id() + + if not platform or not contest_id or not problem_id then + logger.log('No problem context. Run :CP first.', vim.log.levels.ERROR) + return + end + + cache.load() + local test_cases = cache.get_test_cases(platform, contest_id, problem_id) + + if not test_cases or #test_cases == 0 then + logger.log('No test cases available for editing.', vim.log.levels.ERROR) + return + end + + local timeout_ms, memory_mb = cache.get_constraints(platform, contest_id, problem_id) + local constraints = (timeout_ms and memory_mb) + and { timeout_ms = timeout_ms, memory_mb = memory_mb } + or nil + + local target_index = test_index or 1 + if target_index < 1 or target_index > #test_cases then + logger.log( + ('Test %d does not exist (only %d tests available)'):format(target_index, #test_cases), + vim.log.levels.ERROR + ) + return + end + + local io_view_state = state.get_io_view_state() + if io_view_state then + if io_view_state.output_buf and vim.api.nvim_buf_is_valid(io_view_state.output_buf) then + vim.api.nvim_buf_delete(io_view_state.output_buf, { force = true }) + end + if io_view_state.input_buf and vim.api.nvim_buf_is_valid(io_view_state.input_buf) then + vim.api.nvim_buf_delete(io_view_state.input_buf, { force = true }) + end + state.set_io_view_state(nil) + end + + local session_file = vim.fn.tempname() + state.set_saved_session(session_file) + -- selene: allow(mixed_table) + vim.cmd.mksession({ session_file, bang = true }) + vim.cmd.only({ mods = { silent = true } }) + + local test_buffers = {} + local num_tests = #test_cases + + for _ = 1, num_tests - 1 do + vim.cmd.vsplit() + end + + vim.cmd('1 wincmd w') + + for col = 1, num_tests do + vim.cmd.split() + + vim.cmd.wincmd('k') + local input_win = vim.api.nvim_get_current_win() + local input_buf = utils.create_buffer_with_options() + vim.api.nvim_win_set_buf(input_win, input_buf) + vim.bo[input_buf].modifiable = true + vim.bo[input_buf].readonly = false + vim.bo[input_buf].buftype = 'nofile' + vim.bo[input_buf].buflisted = false + helpers.clearcol(input_buf) + + vim.cmd.wincmd('j') + local expected_win = vim.api.nvim_get_current_win() + local expected_buf = utils.create_buffer_with_options() + vim.api.nvim_win_set_buf(expected_win, expected_buf) + vim.bo[expected_buf].modifiable = true + vim.bo[expected_buf].readonly = false + vim.bo[expected_buf].buftype = 'nofile' + vim.bo[expected_buf].buflisted = false + helpers.clearcol(expected_buf) + + test_buffers[col] = { + input_buf = input_buf, + expected_buf = expected_buf, + input_win = input_win, + expected_win = expected_win, + } + + vim.cmd.wincmd('k') + vim.cmd.wincmd('l') + end + + edit_state = { + test_buffers = test_buffers, + test_cases = test_cases, + constraints = constraints, + } + + for i = 1, num_tests do + load_test_into_buffer(i) + end + + for _, pair in ipairs(test_buffers) do + setup_keybindings(pair.input_buf) + setup_keybindings(pair.expected_buf) + end + + if + test_buffers[target_index] + and vim.api.nvim_win_is_valid(test_buffers[target_index].input_win) + then + vim.api.nvim_set_current_win(test_buffers[target_index].input_win) + end + + logger.log(('Editing %d test cases'):format(num_tests)) +end + +return M diff --git a/lua/cp/ui/layouts.lua b/lua/cp/ui/layouts.lua index 730c17e..4e737d3 100644 --- a/lua/cp/ui/layouts.lua +++ b/lua/cp/ui/layouts.lua @@ -11,7 +11,7 @@ local function create_none_diff_layout(parent_win, expected_content, actual_cont vim.api.nvim_set_current_win(parent_win) vim.cmd.split() - vim.cmd('resize ' .. math.floor(vim.o.lines * 0.35)) + vim.cmd.resize(math.floor(vim.o.lines * 0.35)) local actual_win = vim.api.nvim_get_current_win() vim.api.nvim_win_set_buf(actual_win, actual_buf) @@ -50,7 +50,7 @@ local function create_vim_diff_layout(parent_win, expected_content, actual_conte vim.api.nvim_set_current_win(parent_win) vim.cmd.split() - vim.cmd('resize ' .. math.floor(vim.o.lines * 0.35)) + vim.cmd.resize(math.floor(vim.o.lines * 0.35)) local actual_win = vim.api.nvim_get_current_win() vim.api.nvim_win_set_buf(actual_win, actual_buf) @@ -98,7 +98,7 @@ local function create_git_diff_layout(parent_win, expected_content, actual_conte vim.api.nvim_set_current_win(parent_win) vim.cmd.split() - vim.cmd('resize ' .. math.floor(vim.o.lines * 0.35)) + vim.cmd.resize(math.floor(vim.o.lines * 0.35)) local diff_win = vim.api.nvim_get_current_win() vim.api.nvim_win_set_buf(diff_win, diff_buf) @@ -135,7 +135,7 @@ local function create_single_layout(parent_win, content) vim.api.nvim_set_current_win(parent_win) vim.cmd.split() - vim.cmd('resize ' .. math.floor(vim.o.lines * 0.35)) + vim.cmd.resize(math.floor(vim.o.lines * 0.35)) local win = vim.api.nvim_get_current_win() vim.api.nvim_win_set_buf(win, buf) vim.api.nvim_set_option_value('filetype', 'cp', { buf = buf }) diff --git a/lua/cp/ui/views.lua b/lua/cp/ui/views.lua index 23e2fc6..ab7a407 100644 --- a/lua/cp/ui/views.lua +++ b/lua/cp/ui/views.lua @@ -40,7 +40,7 @@ function M.toggle_interactive(interactor_cmd) end end if state.saved_interactive_session then - vim.cmd(('source %s'):format(state.saved_interactive_session)) + vim.cmd.source(state.saved_interactive_session) vim.fn.delete(state.saved_interactive_session) state.saved_interactive_session = nil end @@ -75,8 +75,9 @@ function M.toggle_interactive(interactor_cmd) end state.saved_interactive_session = vim.fn.tempname() - vim.cmd(('mksession! %s'):format(state.saved_interactive_session)) - vim.cmd('silent only') + -- selene: allow(mixed_table) + vim.cmd.mksession({ state.saved_interactive_session, bang = true }) + vim.cmd.only({ mods = { silent = true } }) local execute = require('cp.runner.execute') local run = require('cp.runner.run') @@ -104,7 +105,7 @@ function M.toggle_interactive(interactor_cmd) vim.log.levels.ERROR ) if state.saved_interactive_session then - vim.cmd(('source %s'):format(state.saved_interactive_session)) + vim.cmd.source(state.saved_interactive_session) vim.fn.delete(state.saved_interactive_session) state.saved_interactive_session = nil end @@ -122,7 +123,7 @@ function M.toggle_interactive(interactor_cmd) cmdline = vim.fn.shellescape(binary) end - vim.cmd('terminal ' .. cmdline) + vim.cmd.terminal(cmdline) local term_buf = vim.api.nvim_get_current_buf() local term_win = vim.api.nvim_get_current_win() @@ -139,7 +140,7 @@ function M.toggle_interactive(interactor_cmd) end end if state.saved_interactive_session then - vim.cmd(('source %s'):format(state.saved_interactive_session)) + vim.cmd.source(state.saved_interactive_session) vim.fn.delete(state.saved_interactive_session) state.saved_interactive_session = nil end @@ -247,6 +248,23 @@ function M.ensure_io_view() current_test_index = 1, }) + local source_buf = vim.api.nvim_win_get_buf(solution_win) + vim.api.nvim_create_autocmd('BufDelete', { + buffer = source_buf, + callback = function() + local io = state.get_io_view_state() + if io then + if io.output_buf and vim.api.nvim_buf_is_valid(io.output_buf) then + vim.api.nvim_buf_delete(io.output_buf, { force = true }) + end + if io.input_buf and vim.api.nvim_buf_is_valid(io.input_buf) then + vim.api.nvim_buf_delete(io.input_buf, { force = true }) + end + state.set_io_view_state(nil) + end + end, + }) + if cfg.hooks and cfg.hooks.setup_io_output then pcall(cfg.hooks.setup_io_output, output_buf, state) end @@ -400,6 +418,25 @@ function M.run_io_view(test_index, debug) local formatter = config.ui.run.format_verdict + local max_time_actual = 0 + local max_time_limit = 0 + local max_mem_actual = 0 + local max_mem_limit = 0 + + for _, idx in ipairs(test_indices) do + local tc = test_state.test_cases[idx] + max_time_actual = math.max(max_time_actual, #string.format('%.2f', tc.time_ms or 0)) + max_time_limit = math.max( + max_time_limit, + #tostring(test_state.constraints and test_state.constraints.timeout_ms or 0) + ) + max_mem_actual = math.max(max_mem_actual, #string.format('%.0f', tc.rss_mb or 0)) + max_mem_limit = math.max( + max_mem_limit, + #string.format('%.0f', test_state.constraints and test_state.constraints.memory_mb or 0) + ) + end + for _, idx in ipairs(test_indices) do local tc = test_state.test_cases[idx] @@ -425,6 +462,10 @@ function M.run_io_view(test_index, debug) exit_code = tc.code or 0, signal = (tc.code and tc.code >= 128) and require('cp.constants').signal_codes[tc.code] or nil, + time_actual_width = max_time_actual, + time_limit_width = max_time_limit, + mem_actual_width = max_mem_actual, + mem_limit_width = max_mem_limit, } local result = formatter(format_data) @@ -484,7 +525,7 @@ function M.toggle_panel(panel_opts) end local saved = state.get_saved_session() if saved then - vim.cmd(('source %s'):format(saved)) + vim.cmd.source(saved) vim.fn.delete(saved) state.set_saved_session(nil) end @@ -542,8 +583,9 @@ function M.toggle_panel(panel_opts) local session_file = vim.fn.tempname() state.set_saved_session(session_file) - vim.cmd(('mksession! %s'):format(session_file)) - vim.cmd('silent only') + -- selene: allow(mixed_table) + vim.cmd.mksession({ session_file, bang = true }) + vim.cmd.only({ mods = { silent = true } }) local tab_buf = utils.create_buffer_with_options() helpers.clearcol(tab_buf) @@ -589,8 +631,9 @@ function M.toggle_panel(panel_opts) and vim.api.nvim_win_is_valid(test_windows.tab_win) then vim.api.nvim_win_set_cursor(test_windows.tab_win, { current_line, 0 }) + -- selene: allow(mixed_table) vim.api.nvim_win_call(test_windows.tab_win, function() - vim.cmd('normal! zz') + vim.cmd.normal({ 'zz', bang = true }) end) end end diff --git a/plugin/cp.lua b/plugin/cp.lua index 61f3b3d..b91a3d5 100644 --- a/plugin/cp.lua +++ b/plugin/cp.lua @@ -69,6 +69,23 @@ end, { elseif args[2] == 'interact' then local utils = require('cp.utils') return filter_candidates(utils.cwd_executables()) + elseif args[2] == 'edit' then + local state = require('cp.state') + local platform = state.get_platform() + local contest_id = state.get_contest_id() + local problem_id = state.get_problem_id() + local candidates = {} + if platform and contest_id and problem_id then + local cache = require('cp.cache') + cache.load() + local test_cases = cache.get_test_cases(platform, contest_id, problem_id) + if test_cases then + for i = 1, #test_cases do + table.insert(candidates, tostring(i)) + end + end + end + return filter_candidates(candidates) elseif args[2] == 'run' or args[2] == 'panel' then local state = require('cp.state') local platform = state.get_platform() @@ -96,7 +113,9 @@ end, { end elseif num_args == 4 then if args[2] == 'cache' and args[3] == 'clear' then - return filter_candidates(platforms) + local candidates = vim.list_extend({}, platforms) + table.insert(candidates, '') + return filter_candidates(candidates) elseif args[3] == '--lang' then local platform = require('cp.state').get_platform() return filter_candidates(get_enabled_languages(platform)) @@ -115,7 +134,12 @@ end, { return filter_candidates(candidates) end elseif num_args == 5 then - if vim.tbl_contains(platforms, args[2]) then + if args[2] == 'cache' and args[3] == 'clear' and vim.tbl_contains(platforms, args[4]) then + local cache = require('cp.cache') + cache.load() + local contests = cache.get_cached_contest_ids(args[4]) + return filter_candidates(contests) + elseif vim.tbl_contains(platforms, args[2]) then if args[4] == '--lang' then return filter_candidates(get_enabled_languages(args[2])) else