From 6b8a1e208794a2202b6279d09d9144412cba9218 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Wed, 1 Oct 2025 21:36:53 -0400 Subject: [PATCH] more docs --- doc/cp.nvim.txt | 18 +++---- lua/cp/cache.lua | 94 +++++++++++++++++------------------- lua/cp/commands/cache.lua | 11 ++++- lua/cp/commands/init.lua | 4 +- lua/cp/pickers/fzf_lua.lua | 6 +-- lua/cp/pickers/init.lua | 18 +++---- lua/cp/pickers/telescope.lua | 10 ++-- lua/cp/runner/run.lua | 52 +------------------- lua/cp/state.lua | 10 ---- plugin/cp.lua | 2 +- 10 files changed, 83 insertions(+), 142 deletions(-) diff --git a/doc/cp.nvim.txt b/doc/cp.nvim.txt index 07d4312..1574af7 100644 --- a/doc/cp.nvim.txt +++ b/doc/cp.nvim.txt @@ -56,13 +56,6 @@ COMMANDS *cp-commands* :CP {platform} Platform setup: set platform only. Example: > :CP cses -< - :CP {problem_id} [--lang={language}] - Problem switch: switch to different problem - within current contest context. - Example: > - :CP b - :CP b --lang=python < Action Commands ~ :CP run [--debug] Toggle run panel for individual test case @@ -82,6 +75,15 @@ COMMANDS *cp-commands* :CP prev Navigate to previous problem in current contest. Stops at first problem (no wrapping). + Cache Commands ~ + :CP cache clear [contest] + Clear the cache data (contest list, problem + data, file states) for the specified contest, + or all contests if none specified + + :CP cache read + View the cache in a pretty-printed lua buffer. + Command Flags ~ *cp-flags* Flags can be used with setup and action commands: @@ -285,7 +287,6 @@ URL format: https://atcoder.jp/contests/abc123/tasks/abc123_a Usage examples: > :CP atcoder abc324 a " Full setup: problem A from contest ABC324 :CP atcoder abc324 " Contest setup: load contest metadata only - :CP b " Switch to problem B (if contest loaded) :CP next " Navigate to next problem in contest < Note: AtCoder template includes optimizations @@ -303,7 +304,6 @@ URL format: https://codeforces.com/contest/1234/problem/A Usage examples: > :CP codeforces 1934 a " Full setup: problem A from contest 1934 :CP codeforces 1934 " Contest setup: load contest metadata only - :CP c " Switch to problem C (if contest loaded) :CP prev " Navigate to previous problem in contest < Note: Problem IDs are automatically converted diff --git a/lua/cp/cache.lua b/lua/cp/cache.lua index 49d5b42..3c2fcfb 100644 --- a/lua/cp/cache.lua +++ b/lua/cp/cache.lua @@ -111,6 +111,11 @@ function M.set_contest_data(platform, contest_id, problems) cache_data[platform][contest_id] = { problems = problems, } + cache_data[platform][contest_id].index_map = {} + + for i, problem in ipairs(problems) do + cache_data[platform][contest_id].index_map[problem.id] = i + end M.save() end @@ -140,16 +145,22 @@ function M.get_test_cases(platform, contest_id, problem_id) problem_id = { problem_id, { 'string', 'nil' }, true }, }) - local problem_key = problem_id and (contest_id .. '_' .. problem_id) or contest_id - if not cache_data[platform] or not cache_data[platform][problem_key] then + if + not cache_data[platform] + or not cache_data[platform][contest_id] + or not cache_data[platform][contest_id].problems + or not cache_data[platform][contest_id].index_map + then return nil end - return cache_data[platform][problem_key].test_cases + + local index = cache_data[platform][contest_id].index_map[problem_id] + return cache_data[platform][contest_id].problems[index].test_cases end ---@param platform string ---@param contest_id string ----@param problem_id? string +---@param problem_id string ---@param test_cases CachedTestCase[] ---@param timeout_ms? number ---@param memory_mb? number @@ -173,21 +184,12 @@ function M.set_test_cases( interactive = { interactive, { 'boolean', 'nil' }, true }, }) - local problem_key = problem_id and (contest_id .. '_' .. problem_id) or contest_id - if not cache_data[platform] then - cache_data[platform] = {} - end - if not cache_data[platform][problem_key] then - cache_data[platform][problem_key] = {} - end + local index = cache_data[platform][contest_id].index_map[problem_id] + + cache_data[platform][contest_id].problems[index].test_cases = test_cases + cache_data[platform][contest_id].problems[index].timeout_ms = timeout_ms or 0 + cache_data[platform][contest_id].problems[index].memory_mb = memory_mb or 0 - cache_data[platform][problem_key].test_cases = test_cases - if timeout_ms then - cache_data[platform][problem_key].timeout_ms = timeout_ms - end - if memory_mb then - cache_data[platform][problem_key].memory_mb = memory_mb - end M.save() end @@ -202,12 +204,9 @@ function M.get_constraints(platform, contest_id, problem_id) problem_id = { problem_id, { 'string', 'nil' }, true }, }) - local problem_key = problem_id and (contest_id .. '_' .. problem_id) or contest_id - if not cache_data[platform] or not cache_data[platform][problem_key] then - return nil, nil - end + local index = cache_data[platform][contest_id].index_map[problem_id] - local problem_data = cache_data[platform][problem_key] + local problem_data = cache_data[platform][contest_id].problems[index] return problem_data.timeout_ms, problem_data.memory_mb end @@ -242,42 +241,34 @@ function M.set_file_state(file_path, platform, contest_id, problem_id, language) end ---@param platform string ----@return table[]? +---@return table[] function M.get_contest_list(platform) - if not cache_data.contest_lists or not cache_data.contest_lists[platform] then - return nil + local contest_list = {} + for contest_id, contest_data in pairs(cache_data[platform] or {}) do + table.insert(contest_list, { + id = contest_id, + name = contest_data.name, + display_name = contest_data.display_name, + }) end - - return cache_data.contest_lists[platform].contests + return contest_list end ---@param platform string ---@param contests table[] function M.set_contest_list(platform, contests) - if not cache_data.contest_lists then - cache_data.contest_lists = {} + cache_data[platform] = cache_data[platform] or {} + for _, contest in ipairs(contests) do + cache_data[platform][contest.id] = cache_data[platform][contest] or {} + cache_data[platform][contest.id].display_name = contest.display_name + cache_data[platform][contest.id].name = contest.name end - cache_data.contest_lists[platform] = { - contests = contests, - } - M.save() end ----@param platform string -function M.clear_contest_list(platform) - if cache_data.contest_lists and cache_data.contest_lists[platform] then - cache_data.contest_lists[platform] = nil - M.save() - end -end - function M.clear_all() - cache_data = { - file_states = {}, - contest_lists = {}, - } + cache_data = {} M.save() end @@ -286,10 +277,15 @@ function M.clear_platform(platform) if cache_data[platform] then cache_data[platform] = nil end - if cache_data.contest_lists and cache_data.contest_lists[platform] then - cache_data.contest_lists[platform] = nil - end + M.save() end +---@return string +function M.get_data_pretty() + M.load() + + return vim.inspect(cache_data) +end + return M diff --git a/lua/cp/commands/cache.lua b/lua/cp/commands/cache.lua index 23135aa..f36a84e 100644 --- a/lua/cp/commands/cache.lua +++ b/lua/cp/commands/cache.lua @@ -7,7 +7,16 @@ local logger = require('cp.log') local platforms = constants.PLATFORMS function M.handle_cache_command(cmd) - if cmd.subcommand == 'clear' then + if cmd.subcommand == 'read' then + local data = cache.get_data_pretty() + + local buf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_set_name(buf, 'cp.nvim://cache.lua') + vim.api.nvim_buf_set_lines(buf, 0, -1, false, vim.split(data, '\n')) + vim.bo[buf].filetype = 'lua' + + vim.api.nvim_set_current_buf(buf) + elseif cmd.subcommand == 'clear' then cache.load() if cmd.platform then if vim.tbl_contains(platforms, cmd.platform) then diff --git a/lua/cp/commands/init.lua b/lua/cp/commands/init.lua index b798ef5..b4ce499 100644 --- a/lua/cp/commands/init.lua +++ b/lua/cp/commands/init.lua @@ -44,11 +44,11 @@ local function parse_command(args) if not subcommand then return { type = 'error', message = 'cache command requires subcommand: clear' } end - if subcommand == 'clear' then + if vim.tbl_contains({ 'clear', 'read' }, subcommand) then local platform = filtered_args[3] return { type = 'cache', - subcommand = 'clear', + subcommand = subcommand, platform = platform, } else diff --git a/lua/cp/pickers/fzf_lua.lua b/lua/cp/pickers/fzf_lua.lua index a8310af..da29d74 100644 --- a/lua/cp/pickers/fzf_lua.lua +++ b/lua/cp/pickers/fzf_lua.lua @@ -2,11 +2,11 @@ local picker_utils = require('cp.pickers') local M = {} -local function contest_picker(platform) +local function contest_picker(platform, refresh) local constants = require('cp.constants') local platform_display_name = constants.PLATFORM_DISPLAY_NAMES[platform] or platform local fzf = require('fzf-lua') - local contests = picker_utils.get_contests_for_platform(platform) + local contests = picker_utils.get_platform_contests(platform, refresh) if vim.tbl_isempty(contests) then vim.notify( @@ -48,7 +48,7 @@ local function contest_picker(platform) ['ctrl-r'] = function() local cache = require('cp.cache') cache.clear_contest_list(platform) - contest_picker(platform) + contest_picker(platform, true) end, }, }) diff --git a/lua/cp/pickers/init.lua b/lua/cp/pickers/init.lua index f97e73b..a3321a3 100644 --- a/lua/cp/pickers/init.lua +++ b/lua/cp/pickers/init.lua @@ -38,8 +38,9 @@ end ---Get list of contests for a specific platform ---@param platform string Platform identifier (e.g. "codeforces", "atcoder") +---@param refresh? boolean Whether to skip caching and append new contests ---@return cp.ContestItem[] -function M.get_contests_for_platform(platform) +function M.get_platform_contests(platform, refresh) logger.log( ('Loading %s contests...'):format(constants.PLATFORM_DISPLAY_NAMES[platform]), vim.log.levels.INFO, @@ -48,21 +49,13 @@ function M.get_contests_for_platform(platform) cache.load() - local picker_contests = cache.get_contest_list(platform) or {} + local picker_contests = cache.get_contest_list(platform) - if vim.tbl_isempty(picker_contests) then + if refresh or vim.tbl_isempty(picker_contests) then logger.log(('Cache miss on %s contests'):format(platform)) local contests = scraper.scrape_contest_list(platform) cache.set_contest_list(platform, contests) - - for _, contest in ipairs(contests or {}) do - table.insert(picker_contests, { - id = contest.id, - name = contest.name, - display_name = contest.display_name, - }) - end end logger.log( @@ -70,6 +63,9 @@ function M.get_contests_for_platform(platform) vim.log.levels.INFO, true ) + + picker_contests = cache.get_contest_list(platform) + return picker_contests end diff --git a/lua/cp/pickers/telescope.lua b/lua/cp/pickers/telescope.lua index 496b056..9b3c0db 100644 --- a/lua/cp/pickers/telescope.lua +++ b/lua/cp/pickers/telescope.lua @@ -8,10 +8,10 @@ local picker_utils = require('cp.pickers') local M = {} -local function contest_picker(opts, platform) +local function contest_picker(opts, platform, refresh) local constants = require('cp.constants') - local platform_display_name = constants.PLATFORM_DISPLAY_NAMES[platform] or platform - local contests = picker_utils.get_contests_for_platform(platform) + local platform_display_name = constants.PLATFORM_DISPLAY_NAMES[platform] + local contests = picker_utils.get_platform_contests(platform, refresh) if vim.tbl_isempty(contests) then vim.notify( @@ -48,10 +48,8 @@ local function contest_picker(opts, platform) end) map('i', '', function() - local cache = require('cp.cache') - cache.clear_contest_list(platform) actions.close(prompt_bufnr) - contest_picker(opts, platform) + contest_picker(opts, platform, true) end) return true diff --git a/lua/cp/runner/run.lua b/lua/cp/runner/run.lua index 84e71af..4462d0c 100644 --- a/lua/cp/runner/run.lua +++ b/lua/cp/runner/run.lua @@ -83,34 +83,6 @@ local function parse_test_cases_from_cache(platform, contest_id, problem_id) return test_cases end ----@param input_file string ----@return TestCase[] -local function parse_test_cases_from_files(input_file, _) - local base_name = vim.fn.fnamemodify(input_file, ':r') - local test_cases = {} - - local i = 1 - while true do - local individual_input_file = base_name .. '.' .. i .. '.cpin' - local individual_expected_file = base_name .. '.' .. i .. '.cpout' - - if - vim.fn.filereadable(individual_input_file) == 1 - and vim.fn.filereadable(individual_expected_file) == 1 - then - local input_content = table.concat(vim.fn.readfile(individual_input_file), '\n') - local expected_content = table.concat(vim.fn.readfile(individual_expected_file), '\n') - - table.insert(test_cases, create_test_case(i, input_content, expected_content)) - i = i + 1 - else - break - end - end - - return test_cases -end - ---@param platform string ---@param contest_id string ---@param problem_id string? @@ -136,28 +108,11 @@ end local function run_single_test_case(contest_config, cp_config, test_case) local state = require('cp.state') local source_file = state.get_source_file() - if not source_file then - return { - status = 'fail', - actual = '', - error = 'No source file found', - time_ms = 0, - } - end local language = vim.fn.fnamemodify(source_file, ':e') local language_name = constants.filetype_to_language[language] or contest_config.default_language local language_config = contest_config[language_name] - if not language_config then - return { - status = 'fail', - actual = '', - error = 'No language configuration', - time_ms = 0, - } - end - local function substitute_template(cmd_template, substitutions) local result = {} for _, arg in ipairs(cmd_template) do @@ -208,6 +163,7 @@ local function run_single_test_case(contest_config, cp_config, test_case) ok = false, signal = nil, timed_out = false, + actual_highlights = {}, } end end @@ -298,11 +254,7 @@ function M.load_test_cases(state) state.get_problem_id() ) or {} - if vim.tbl_isempty(test_cases) then - local input_file = state.get_input_file() - local expected_file = state.get_expected_file() - test_cases = parse_test_cases_from_files(input_file, expected_file) - end + -- TODO: re-fetch/cache-populating mechanism to ge the test cases if not in the cache run_panel_state.test_cases = test_cases run_panel_state.current_index = 1 diff --git a/lua/cp/state.lua b/lua/cp/state.lua index f827a50..497e5d5 100644 --- a/lua/cp/state.lua +++ b/lua/cp/state.lua @@ -7,8 +7,6 @@ ---@field set_problem_id fun(problem_id: string) ---@field get_active_panel fun(): string? ---@field set_active_panel fun(): string? ----@field get_test_cases fun(): table[]? ----@field set_test_cases fun(test_cases: table[]) ---@field get_saved_session fun(): table? ---@field set_saved_session fun(session: table) ---@field get_context fun(): {platform: string?, contest_id: string?, problem_id: string?} @@ -56,14 +54,6 @@ function M.set_problem_id(problem_id) state.problem_id = problem_id end -function M.get_test_cases() - return state.test_cases -end - -function M.set_test_cases(test_cases) - state.test_cases = test_cases -end - function M.get_saved_session() return state.saved_session end diff --git a/plugin/cp.lua b/plugin/cp.lua index 37c6f36..193beeb 100644 --- a/plugin/cp.lua +++ b/plugin/cp.lua @@ -46,7 +46,7 @@ end, { if args[2] == 'cache' then return vim.tbl_filter(function(cmd) return cmd:find(ArgLead, 1, true) == 1 - end, { 'clear' }) + end, { 'clear', 'read' }) end elseif num_args == 4 then if args[2] == 'cache' and args[3] == 'clear' then