more docs

This commit is contained in:
Barrett Ruth 2025-10-01 21:36:53 -04:00
parent 7eb314b02c
commit 6b8a1e2087
10 changed files with 83 additions and 142 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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,
},
})

View file

@ -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

View file

@ -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', '<c-r>', 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

View file

@ -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

View file

@ -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

View file

@ -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