Merge branch 'main' into fix/submit-hardening

# Conflicts:
#	scrapers/atcoder.py
#	scrapers/codeforces.py
This commit is contained in:
Barrett Ruth 2026-03-05 14:18:01 -05:00
commit 427d03ec2d
Signed by: barrett
GPG key ID: A6C96C9349D2FC81
21 changed files with 154 additions and 129 deletions

View file

@ -47,26 +47,24 @@ function M.handle_cache_command(cmd)
constants.PLATFORM_DISPLAY_NAMES[cmd.platform],
cmd.contest
),
vim.log.levels.INFO,
true
{ level = vim.log.levels.INFO, override = true }
)
else
logger.log(("Unknown platform '%s'."):format(cmd.platform), vim.log.levels.ERROR)
logger.log(("Unknown platform '%s'."):format(cmd.platform), { level = vim.log.levels.ERROR })
end
elseif cmd.platform then
if vim.tbl_contains(platforms, cmd.platform) then
cache.clear_platform(cmd.platform)
logger.log(
("Cache cleared for platform '%s'"):format(constants.PLATFORM_DISPLAY_NAMES[cmd.platform]),
vim.log.levels.INFO,
true
{ level = vim.log.levels.INFO, override = true }
)
else
logger.log(("Unknown platform '%s'."):format(cmd.platform), vim.log.levels.ERROR)
logger.log(("Unknown platform '%s'."):format(cmd.platform), { level = vim.log.levels.ERROR })
end
else
cache.clear_all()
logger.log('Cache cleared', vim.log.levels.INFO, true)
logger.log('Cache cleared', { level = vim.log.levels.INFO, override = true })
end
end
end

View file

@ -83,8 +83,6 @@ local function parse_command(args)
else
return { type = 'action', action = 'interact' }
end
elseif first == 'login' or first == 'logout' then
return { type = 'action', action = first, platform = args[2] }
elseif first == 'stress' then
return {
type = 'action',
@ -245,6 +243,9 @@ local function parse_command(args)
message = 'Too few arguments - specify a contest.',
}
elseif #args == 2 then
if args[2] == 'login' or args[2] == 'logout' then
return { type = 'action', action = args[2], platform = first }
end
return {
type = 'contest_setup',
platform = first,
@ -287,7 +288,7 @@ function M.handle_command(opts)
local cmd = parse_command(opts.fargs)
if cmd.type == 'error' then
logger.log(cmd.message, vim.log.levels.ERROR)
logger.log(cmd.message, { level = vim.log.levels.ERROR })
return
end
@ -336,7 +337,7 @@ function M.handle_command(opts)
local problem_id = cmd.problem_id
if not (platform and contest_id) then
logger.log('No contest is currently active.', vim.log.levels.ERROR)
logger.log('No contest is currently active.', { level = vim.log.levels.ERROR })
return
end
@ -351,7 +352,7 @@ function M.handle_command(opts)
contest_id,
problem_id
),
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
return
end

View file

@ -12,7 +12,7 @@ function M.handle_pick_action(language)
if not (config.ui and config.ui.picker) then
logger.log(
'No picker configured. Set ui.picker = "{telescope,fzf-lua}" in your config.',
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
return
end
@ -25,13 +25,13 @@ function M.handle_pick_action(language)
if not ok then
logger.log(
'telescope.nvim is not available. Install telescope.nvim xor change your picker config.',
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
return
end
local ok_cp, telescope_picker = pcall(require, 'cp.pickers.telescope')
if not ok_cp then
logger.log('Failed to load telescope integration.', vim.log.levels.ERROR)
logger.log('Failed to load telescope integration.', { level = vim.log.levels.ERROR })
return
end
@ -41,13 +41,13 @@ function M.handle_pick_action(language)
if not ok then
logger.log(
'fzf-lua is not available. Install fzf-lua or change your picker config',
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
return
end
local ok_cp, fzf_picker = pcall(require, 'cp.pickers.fzf_lua')
if not ok_cp then
logger.log('Failed to load fzf-lua integration.', vim.log.levels.ERROR)
logger.log('Failed to load fzf-lua integration.', { level = vim.log.levels.ERROR })
return
end

View file

@ -13,8 +13,6 @@ M.ACTIONS = {
'race',
'stress',
'submit',
'login',
'logout',
}
M.PLATFORM_DISPLAY_NAMES = {

View file

@ -7,37 +7,37 @@ local state = require('cp.state')
function M.login(platform)
platform = platform or state.get_platform()
if not platform then
logger.log('No platform specified. Usage: :CP login <platform>', vim.log.levels.ERROR)
logger.log('No platform specified. Usage: :CP login <platform>', { level = vim.log.levels.ERROR })
return
end
vim.ui.input({ prompt = platform .. ' username: ' }, function(username)
if not username or username == '' then
logger.log('Cancelled', vim.log.levels.WARN)
logger.log('Cancelled', { level = vim.log.levels.WARN })
return
end
vim.fn.inputsave()
local password = vim.fn.inputsecret(platform .. ' password: ')
vim.fn.inputrestore()
if not password or password == '' then
logger.log('Cancelled', vim.log.levels.WARN)
logger.log('Cancelled', { level = vim.log.levels.WARN })
return
end
cache.load()
cache.set_credentials(platform, { username = username, password = password })
logger.log(platform .. ' credentials saved', vim.log.levels.INFO, true)
logger.log(platform .. ' credentials saved', { level = vim.log.levels.INFO, override = true })
end)
end
function M.logout(platform)
platform = platform or state.get_platform()
if not platform then
logger.log('No platform specified. Usage: :CP logout <platform>', vim.log.levels.ERROR)
logger.log('No platform specified. Usage: :CP logout <platform>', { level = vim.log.levels.ERROR })
return
end
cache.load()
cache.clear_credentials(platform)
logger.log(platform .. ' credentials cleared', vim.log.levels.INFO, true)
logger.log(platform .. ' credentials cleared', { level = vim.log.levels.INFO, override = true })
end
return M

View file

@ -7,7 +7,7 @@ local logger = require('cp.log')
M.helpers = helpers
if vim.fn.has('nvim-0.10.0') == 0 then
logger.log('Requires nvim-0.10.0+', vim.log.levels.ERROR)
logger.log('Requires nvim-0.10.0+', { level = vim.log.levels.ERROR })
return {}
end

View file

@ -1,12 +1,27 @@
local M = {}
function M.log(msg, level, override)
---@class LogOpts
---@field level? integer
---@field override? boolean
---@field sync? boolean
---@param msg string
---@param opts? LogOpts
function M.log(msg, opts)
local debug = require('cp.config').get_config().debug or false
level = level or vim.log.levels.INFO
opts = opts or {}
local level = opts.level or vim.log.levels.INFO
local override = opts.override or false
local sync = opts.sync or false
if level >= vim.log.levels.WARN or override or debug then
vim.schedule(function()
local notify = function()
vim.notify(('[cp.nvim]: %s'):format(msg), level)
end)
end
if sync then
notify()
else
vim.schedule(notify)
end
end
end

View file

@ -42,24 +42,14 @@ function M.get_platform_contests(platform, refresh)
local picker_contests = cache.get_contest_summaries(platform)
if refresh or vim.tbl_isempty(picker_contests) then
logger.log(
('Loading %s contests...'):format(constants.PLATFORM_DISPLAY_NAMES[platform]),
vim.log.levels.INFO,
true
)
local display_name = constants.PLATFORM_DISPLAY_NAMES[platform]
logger.log(('Fetching %s contests...'):format(display_name), { level = vim.log.levels.INFO, override = true, sync = true })
local contests = scraper.scrape_contest_list(platform)
cache.set_contest_summaries(platform, contests)
picker_contests = cache.get_contest_summaries(platform)
logger.log(
('Loaded %d %s contests.'):format(
#picker_contests,
constants.PLATFORM_DISPLAY_NAMES[platform]
),
vim.log.levels.INFO,
true
)
logger.log(('Fetched %d %s contests.'):format(#picker_contests, display_name), { level = vim.log.levels.INFO, override = true })
end
return picker_contests

View file

@ -22,15 +22,15 @@ end
function M.start(platform, contest_id, language)
if not platform or not vim.tbl_contains(constants.PLATFORMS, platform) then
logger.log('Invalid platform', vim.log.levels.ERROR)
logger.log('Invalid platform', { level = vim.log.levels.ERROR })
return
end
if not contest_id or contest_id == '' then
logger.log('Contest ID required', vim.log.levels.ERROR)
logger.log('Contest ID required', { level = vim.log.levels.ERROR })
return
end
if race_state.timer then
logger.log('Race already active. Use :CP race stop first.', vim.log.levels.WARN)
logger.log('Race already active. Use :CP race stop first.', { level = vim.log.levels.WARN })
return
end
@ -38,7 +38,7 @@ function M.start(platform, contest_id, language)
local start_time = cache.get_contest_start_time(platform, contest_id)
if not start_time then
logger.log('Fetching contest list...', vim.log.levels.INFO, true)
logger.log('Fetching contest list...', { level = vim.log.levels.INFO, override = true })
local contests = scraper.scrape_contest_list(platform)
if contests and #contests > 0 then
cache.set_contest_summaries(platform, contests)
@ -52,14 +52,14 @@ function M.start(platform, contest_id, language)
constants.PLATFORM_DISPLAY_NAMES[platform] or platform,
contest_id
),
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
return
end
local remaining = start_time - os.time()
if remaining <= 0 then
logger.log('Contest has already started, setting up...', vim.log.levels.INFO, true)
logger.log('Contest has already started, setting up...', { level = vim.log.levels.INFO, override = true })
require('cp.setup').setup_contest(platform, contest_id, nil, language)
return
end
@ -75,8 +75,7 @@ function M.start(platform, contest_id, language)
contest_id,
format_countdown(remaining)
),
vim.log.levels.INFO,
true
{ level = vim.log.levels.INFO, override = true }
)
local timer = vim.uv.new_timer()
@ -97,7 +96,7 @@ function M.start(platform, contest_id, language)
race_state.contest_id = nil
race_state.language = nil
race_state.start_time = nil
logger.log('Contest started!', vim.log.levels.INFO, true)
logger.log('Contest started!', { level = vim.log.levels.INFO, override = true })
require('cp.setup').setup_contest(p, c, nil, l)
else
vim.notify(
@ -116,7 +115,7 @@ end
function M.stop()
local timer = race_state.timer
if not timer then
logger.log('No active race', vim.log.levels.WARN)
logger.log('No active race', { level = vim.log.levels.WARN })
return
end
timer:stop()
@ -126,7 +125,7 @@ function M.stop()
race_state.contest_id = nil
race_state.language = nil
race_state.start_time = nil
logger.log('Race cancelled', vim.log.levels.INFO, true)
logger.log('Race cancelled', { level = vim.log.levels.INFO, override = true })
end
function M.status()

View file

@ -11,7 +11,7 @@ function M.restore_from_current_file()
local current_file = (vim.uv.fs_realpath(vim.fn.expand('%:p')) or vim.fn.expand('%:p'))
local file_state = cache.get_file_state(current_file)
if not file_state then
logger.log('No cached state found for current file.', vim.log.levels.ERROR)
logger.log('No cached state found for current file.', { level = vim.log.levels.ERROR })
return false
end

View file

@ -52,7 +52,7 @@ function M.compile(compile_cmd, substitutions, on_complete)
r.stdout = ansi.bytes_to_string(r.stdout or '')
if r.code == 0 then
logger.log(('Compilation successful in %.1fms.'):format(dt), vim.log.levels.INFO)
logger.log(('Compilation successful in %.1fms.'):format(dt), { level = vim.log.levels.INFO })
else
logger.log(('Compilation failed in %.1fms.'):format(dt))
end

View file

@ -245,7 +245,7 @@ function M.load_test_cases()
state.get_problem_id()
)
logger.log(('Loaded %d test case(s)'):format(#tcs), vim.log.levels.INFO)
logger.log(('Loaded %d test case(s)'):format(#tcs), { level = vim.log.levels.INFO })
return #tcs > 0
end
@ -259,7 +259,7 @@ function M.run_combined_test(debug, on_complete)
)
if not combined then
logger.log('No combined test found', vim.log.levels.ERROR)
logger.log('No combined test found', { level = vim.log.levels.ERROR })
on_complete(nil)
return
end
@ -330,8 +330,7 @@ function M.run_all_test_cases(indices, debug, on_each, on_done)
if #to_run == 0 then
logger.log(
('Finished %s %d test cases.'):format(debug and 'debugging' or 'running', 0),
vim.log.levels.INFO,
true
{ level = vim.log.levels.INFO, override = true }
)
on_done(panel_state.test_cases)
return
@ -349,8 +348,7 @@ function M.run_all_test_cases(indices, debug, on_each, on_done)
if remaining == 0 then
logger.log(
('Finished %s %d test cases.'):format(debug and 'debugging' or 'running', total),
vim.log.levels.INFO,
true
{ level = vim.log.levels.INFO, override = true }
)
on_done(panel_state.test_cases)
end

View file

@ -16,7 +16,7 @@ local function syshandle(result)
end
local msg = 'Failed to parse scraper output: ' .. tostring(data)
logger.log(msg, vim.log.levels.ERROR)
logger.log(msg, { level = vim.log.levels.ERROR })
return { success = false, error = msg }
end
@ -37,7 +37,7 @@ end
local function run_scraper(platform, subcommand, args, opts)
if not utils.setup_python_env() then
local msg = 'no Python environment available (install uv or nix)'
logger.log(msg, vim.log.levels.ERROR)
logger.log(msg, { level = vim.log.levels.ERROR })
if opts and opts.on_exit then
opts.on_exit({ success = false, error = msg })
end
@ -125,7 +125,7 @@ local function run_scraper(platform, subcommand, args, opts)
if stdin_pipe and not stdin_pipe:is_closing() then
stdin_pipe:close()
end
logger.log('Failed to start scraper process', vim.log.levels.ERROR)
logger.log('Failed to start scraper process', { level = vim.log.levels.ERROR })
return { success = false, error = 'spawn failed' }
end
@ -221,7 +221,7 @@ function M.scrape_contest_metadata(platform, contest_id, callback)
constants.PLATFORM_DISPLAY_NAMES[platform],
contest_id
),
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
return
end
@ -232,7 +232,7 @@ function M.scrape_contest_metadata(platform, contest_id, callback)
constants.PLATFORM_DISPLAY_NAMES[platform],
contest_id
),
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
return
end
@ -251,7 +251,7 @@ function M.scrape_contest_list(platform)
platform,
(result and result.error) or 'unknown'
),
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
return {}
end
@ -261,9 +261,15 @@ end
---@param platform string
---@param contest_id string
---@param callback fun(data: table)|nil
function M.scrape_all_tests(platform, contest_id, callback)
---@param on_done fun()|nil
function M.scrape_all_tests(platform, contest_id, callback, on_done)
run_scraper(platform, 'tests', { contest_id }, {
ndjson = true,
on_exit = function()
if type(on_done) == 'function' then
vim.schedule(on_done)
end
end,
on_event = function(ev)
if ev.done then
return
@ -275,7 +281,7 @@ function M.scrape_all_tests(platform, contest_id, callback)
contest_id,
ev.error
),
vim.log.levels.WARN
{ level = vim.log.levels.WARN }
)
return
end

View file

@ -16,7 +16,7 @@ local function apply_template(bufnr, lang_id, platform)
end
local path = vim.fn.expand(eff.template)
if vim.fn.filereadable(path) ~= 1 then
logger.log(('[cp.nvim] template not readable: %s'):format(path), vim.log.levels.WARN)
logger.log(('[cp.nvim] template not readable: %s'):format(path), { level = vim.log.levels.WARN })
return
end
local lines = vim.fn.readfile(path)
@ -112,11 +112,12 @@ local function start_tests(platform, contest_id, problems)
return not vim.tbl_isempty(cache.get_test_cases(platform, contest_id, p.id))
end, problems)
if cached_len ~= #problems then
local to_fetch = #problems - cached_len
logger.log(('Fetching %s/%s problem tests...'):format(cached_len, #problems))
scraper.scrape_all_tests(platform, contest_id, function(ev)
local cached_tests = {}
if not ev.interactive and vim.tbl_isempty(ev.tests) then
logger.log(("No tests found for problem '%s'."):format(ev.problem_id), vim.log.levels.WARN)
logger.log(("No tests found for problem '%s'."):format(ev.problem_id), { level = vim.log.levels.WARN })
end
for i, t in ipairs(ev.tests) do
cached_tests[i] = { index = i, input = t.input, expected = t.expected }
@ -142,6 +143,8 @@ local function start_tests(platform, contest_id, problems)
require('cp.utils').update_buffer_content(io_state.input_buf, input_lines, nil, nil)
end
end
end, function()
logger.log(('Loaded %d test%s.'):format(to_fetch, to_fetch == 1 and '' or 's'), { level = vim.log.levels.INFO, override = true })
end)
end
end
@ -160,7 +163,7 @@ function M.setup_contest(platform, contest_id, problem_id, language)
if language then
local lang_result = config_module.get_language_for_platform(platform, language)
if not lang_result.valid then
logger.log(lang_result.error, vim.log.levels.ERROR)
logger.log(lang_result.error, { level = vim.log.levels.ERROR })
return
end
end
@ -206,7 +209,7 @@ function M.setup_contest(platform, contest_id, problem_id, language)
token = vim.uv.hrtime(),
})
logger.log('Fetching contests problems...', vim.log.levels.INFO, true)
logger.log('Fetching contests problems...', { level = vim.log.levels.INFO, override = true })
scraper.scrape_contest_metadata(
platform,
contest_id,
@ -242,7 +245,7 @@ end
function M.setup_problem(problem_id, language)
local platform = state.get_platform()
if not platform then
logger.log('No platform/contest/problem configured.', vim.log.levels.ERROR)
logger.log('No platform/contest/problem configured.', { level = vim.log.levels.ERROR })
return
end
@ -263,7 +266,7 @@ function M.setup_problem(problem_id, language)
if language then
local lang_result = config_module.get_language_for_platform(platform, language)
if not lang_result.valid then
logger.log(lang_result.error, vim.log.levels.ERROR)
logger.log(lang_result.error, { level = vim.log.levels.ERROR })
return
end
end
@ -275,6 +278,19 @@ function M.setup_problem(problem_id, language)
return
end
if vim.fn.filereadable(source_file) == 1 then
local existing = cache.get_file_state(vim.fn.fnamemodify(source_file, ':p'))
if existing and (existing.platform ~= platform or existing.contest_id ~= (state.get_contest_id() or '') or existing.problem_id ~= problem_id) then
logger.log(
('File %q already exists for %s/%s %s.'):format(
source_file, existing.platform, existing.contest_id, existing.problem_id
),
{ level = vim.log.levels.ERROR }
)
return
end
end
local contest_dir = vim.fn.fnamemodify(source_file, ':h')
local is_new_dir = vim.fn.isdirectory(contest_dir) == 0
vim.fn.mkdir(contest_dir, 'p')
@ -397,7 +413,7 @@ function M.navigate_problem(direction, language)
local contest_id = state.get_contest_id()
local current_problem_id = state.get_problem_id()
if not platform or not contest_id or not current_problem_id then
logger.log('No platform configured.', vim.log.levels.ERROR)
logger.log('No platform configured.', { level = vim.log.levels.ERROR })
return
end
@ -409,7 +425,7 @@ function M.navigate_problem(direction, language)
constants.PLATFORM_DISPLAY_NAMES[platform],
contest_id
),
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
return
end
@ -433,7 +449,7 @@ function M.navigate_problem(direction, language)
if language then
local lang_result = config_module.get_language_for_platform(platform, language)
if not lang_result.valid then
logger.log(lang_result.error, vim.log.levels.ERROR)
logger.log(lang_result.error, { level = vim.log.levels.ERROR })
return
end
lang = language

View file

@ -36,7 +36,7 @@ local function compile_cpp(source, output)
if result.code ~= 0 then
logger.log(
('Failed to compile %s: %s'):format(source, result.stderr or ''),
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
return false
end
@ -76,7 +76,7 @@ function M.toggle(generator_cmd, brute_cmd)
end
if state.get_active_panel() then
logger.log('Another panel is already active.', vim.log.levels.WARN)
logger.log('Another panel is already active.', { level = vim.log.levels.WARN })
return
end
@ -93,14 +93,14 @@ function M.toggle(generator_cmd, brute_cmd)
if not gen_file then
logger.log(
'No generator found. Pass generator as first arg or add gen.{py,cc,cpp}.',
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
return
end
if not brute_file then
logger.log(
'No brute solution found. Pass brute as second arg or add brute.{py,cc,cpp}.',
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
return
end
@ -140,7 +140,7 @@ function M.toggle(generator_cmd, brute_cmd)
local binary = state.get_binary_file()
if not binary or binary == '' then
logger.log('No binary produced.', vim.log.levels.ERROR)
logger.log('No binary produced.', { level = vim.log.levels.ERROR })
restore_session()
return
end

View file

@ -19,7 +19,7 @@ local function prompt_credentials(platform, callback)
end
vim.ui.input({ prompt = platform .. ' username: ' }, function(username)
if not username or username == '' then
logger.log('Submit cancelled', vim.log.levels.WARN)
logger.log('Submit cancelled', { level = vim.log.levels.WARN })
return
end
vim.fn.inputsave()
@ -27,7 +27,7 @@ local function prompt_credentials(platform, callback)
vim.fn.inputrestore()
vim.cmd.redraw()
if not password or password == '' then
logger.log('Submit cancelled', vim.log.levels.WARN)
logger.log('Submit cancelled', { level = vim.log.levels.WARN })
return
end
local creds = { username = username, password = password }
@ -42,13 +42,13 @@ function M.submit(opts)
local problem_id = state.get_problem_id()
local language = (opts and opts.language) or state.get_language()
if not platform or not contest_id or not problem_id or not language then
logger.log('No active problem. Use :CP <platform> <contest> first.', vim.log.levels.ERROR)
logger.log('No active problem. Use :CP <platform> <contest> first.', { level = vim.log.levels.ERROR })
return
end
local source_file = state.get_source_file()
if not source_file or vim.fn.filereadable(source_file) ~= 1 then
logger.log('Source file not found', vim.log.levels.ERROR)
logger.log('Source file not found', { level = vim.log.levels.ERROR })
return
end
@ -71,12 +71,13 @@ function M.submit(opts)
function(result)
vim.schedule(function()
if result and result.success then
logger.log('Submitted successfully', vim.log.levels.INFO, true)
logger.log('Submitted successfully', { level = vim.log.levels.INFO, override = true })
else
logger.log(
'Submit failed: ' .. (result and result.error or 'unknown error'),
vim.log.levels.ERROR
)
local err = result and result.error or 'unknown error'
if err:match('^Login failed') then
cache.clear_credentials(platform)
end
logger.log('Submit failed: ' .. err, { level = vim.log.levels.ERROR })
end
end)
end

View file

@ -90,7 +90,7 @@ local function delete_current_test()
return
end
if #edit_state.test_buffers == 1 then
logger.log('Problems must have at least one test case.', vim.log.levels.ERROR)
logger.log('Problems must have at least one test case.', { level = vim.log.levels.ERROR })
return
end
@ -311,7 +311,7 @@ setup_keybindings = function(buf)
end
if is_tracked then
logger.log('Test buffer closed unexpectedly. Exiting editor.', vim.log.levels.WARN)
logger.log('Test buffer closed unexpectedly. Exiting editor.', { level = vim.log.levels.WARN })
M.toggle_edit()
end
end)
@ -368,7 +368,7 @@ function M.toggle_edit(test_index)
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 <platform> <contest> first.', vim.log.levels.ERROR)
logger.log('No problem context. Run :CP <platform> <contest> first.', { level = vim.log.levels.ERROR })
return
end
@ -376,7 +376,7 @@ function M.toggle_edit(test_index)
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)
logger.log('No test cases available for editing.', { level = vim.log.levels.ERROR })
return
end
@ -389,7 +389,7 @@ function M.toggle_edit(test_index)
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
{ level = vim.log.levels.ERROR }
)
return
end

View file

@ -53,7 +53,7 @@ function M.toggle_interactive(interactor_cmd)
end
if state.get_active_panel() then
logger.log('Another panel is already active.', vim.log.levels.WARN)
logger.log('Another panel is already active.', { level = vim.log.levels.WARN })
return
end
@ -62,7 +62,7 @@ function M.toggle_interactive(interactor_cmd)
if not platform or not contest_id or not problem_id then
logger.log(
'No platform/contest/problem configured. Use :CP <platform> <contest> [...] first.',
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
return
end
@ -74,7 +74,7 @@ function M.toggle_interactive(interactor_cmd)
and contest_data.index_map
and not contest_data.problems[contest_data.index_map[problem_id]].interactive
then
logger.log('This problem is not interactive. Use :CP {run,panel}.', vim.log.levels.ERROR)
logger.log('This problem is not interactive. Use :CP {run,panel}.', { level = vim.log.levels.ERROR })
return
end
@ -103,7 +103,7 @@ function M.toggle_interactive(interactor_cmd)
local binary = state.get_binary_file()
if not binary or binary == '' then
logger.log('No binary produced.', vim.log.levels.ERROR)
logger.log('No binary produced.', { level = vim.log.levels.ERROR })
restore_session()
return
end
@ -117,7 +117,7 @@ function M.toggle_interactive(interactor_cmd)
if vim.fn.executable(interactor) ~= 1 then
logger.log(
("Interactor '%s' is not executable."):format(interactor_cmd),
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
restore_session()
return
@ -354,7 +354,7 @@ function M.ensure_io_view()
if not platform or not contest_id or not problem_id then
logger.log(
'No platform/contest/problem configured. Use :CP <platform> <contest> [...] first.',
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
return
end
@ -383,7 +383,7 @@ function M.ensure_io_view()
and contest_data.index_map
and contest_data.problems[contest_data.index_map[problem_id]].interactive
then
logger.log('This problem is not interactive. Use :CP {run,panel}.', vim.log.levels.ERROR)
logger.log('This problem is not interactive. Use :CP {run,panel}.', { level = vim.log.levels.ERROR })
return
end
@ -594,12 +594,12 @@ end
function M.run_io_view(test_indices_arg, debug, mode)
if io_view_running then
logger.log('Tests already running', vim.log.levels.WARN)
logger.log('Tests already running', { level = vim.log.levels.WARN })
return
end
io_view_running = true
logger.log(('%s tests...'):format(debug and 'Debugging' or 'Running'), vim.log.levels.INFO, true)
logger.log(('%s tests...'):format(debug and 'Debugging' or 'Running'), { level = vim.log.levels.INFO, override = true })
mode = mode or 'combined'
@ -608,7 +608,7 @@ function M.run_io_view(test_indices_arg, debug, mode)
if not platform or not contest_id or not problem_id then
logger.log(
'No platform/contest/problem configured. Use :CP <platform> <contest> [...] first.',
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
io_view_running = false
return
@ -617,7 +617,7 @@ function M.run_io_view(test_indices_arg, debug, mode)
cache.load()
local contest_data = cache.get_contest_data(platform, contest_id)
if not contest_data or not contest_data.index_map then
logger.log('No test cases available.', vim.log.levels.ERROR)
logger.log('No test cases available.', { level = vim.log.levels.ERROR })
io_view_running = false
return
end
@ -634,13 +634,13 @@ function M.run_io_view(test_indices_arg, debug, mode)
if mode == 'combined' then
local combined = cache.get_combined_test(platform, contest_id, problem_id)
if not combined then
logger.log('No combined test available', vim.log.levels.ERROR)
logger.log('No combined test available', { level = vim.log.levels.ERROR })
io_view_running = false
return
end
else
if not run.load_test_cases() then
logger.log('No test cases available', vim.log.levels.ERROR)
logger.log('No test cases available', { level = vim.log.levels.ERROR })
io_view_running = false
return
end
@ -660,7 +660,7 @@ function M.run_io_view(test_indices_arg, debug, mode)
idx,
#test_state.test_cases
),
vim.log.levels.WARN
{ level = vim.log.levels.WARN }
)
io_view_running = false
return
@ -721,7 +721,7 @@ function M.run_io_view(test_indices_arg, debug, mode)
if mode == 'combined' then
local combined = cache.get_combined_test(platform, contest_id, problem_id)
if not combined then
logger.log('No combined test found', vim.log.levels.ERROR)
logger.log('No combined test found', { level = vim.log.levels.ERROR })
io_view_running = false
return
end
@ -730,7 +730,7 @@ function M.run_io_view(test_indices_arg, debug, mode)
run.run_combined_test(debug, function(result)
if not result then
logger.log('Failed to run combined test', vim.log.levels.ERROR)
logger.log('Failed to run combined test', { level = vim.log.levels.ERROR })
io_view_running = false
return
end
@ -771,7 +771,7 @@ function M.toggle_panel(panel_opts)
end
if state.get_active_panel() then
logger.log('another panel is already active', vim.log.levels.ERROR)
logger.log('another panel is already active', { level = vim.log.levels.ERROR })
return
end
@ -780,7 +780,7 @@ function M.toggle_panel(panel_opts)
if not platform or not contest_id then
logger.log(
'No platform/contest configured. Use :CP <platform> <contest> [...] first.',
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
return
end
@ -792,7 +792,7 @@ function M.toggle_panel(panel_opts)
and contest_data.index_map
and contest_data.problems[contest_data.index_map[state.get_problem_id()]].interactive
then
logger.log('This is an interactive problem. Use :CP interact instead.', vim.log.levels.WARN)
logger.log('This is an interactive problem. Use :CP interact instead.', { level = vim.log.levels.WARN })
return
end
@ -803,7 +803,7 @@ function M.toggle_panel(panel_opts)
logger.log(('run panel: checking test cases for %s'):format(input_file or 'none'))
if not run.load_test_cases() then
logger.log('no test cases found', vim.log.levels.WARN)
logger.log('no test cases found', { level = vim.log.levels.WARN })
return
end

View file

@ -152,7 +152,7 @@ local function discover_nix_submit_cmd()
:wait()
if result.code ~= 0 then
logger.log('nix build #submitEnv failed: ' .. (result.stderr or ''), vim.log.levels.WARN)
logger.log('nix build #submitEnv failed: ' .. (result.stderr or ''), { level = vim.log.levels.WARN })
return false
end
@ -160,7 +160,7 @@ local function discover_nix_submit_cmd()
local submit_cmd = store_path .. '/bin/cp-nvim-submit'
if vim.fn.executable(submit_cmd) ~= 1 then
logger.log('nix submit cmd not executable at ' .. submit_cmd, vim.log.levels.WARN)
logger.log('nix submit cmd not executable at ' .. submit_cmd, { level = vim.log.levels.WARN })
return false
end
@ -216,7 +216,7 @@ local function discover_nix_python()
:wait()
if result.code ~= 0 then
logger.log('nix build #pythonEnv failed: ' .. (result.stderr or ''), vim.log.levels.WARN)
logger.log('nix build #pythonEnv failed: ' .. (result.stderr or ''), { level = vim.log.levels.WARN })
return false
end
@ -224,7 +224,7 @@ local function discover_nix_python()
local python_path = store_path .. '/bin/python3'
if vim.fn.executable(python_path) ~= 1 then
logger.log('nix python not executable at ' .. python_path, vim.log.levels.WARN)
logger.log('nix python not executable at ' .. python_path, { level = vim.log.levels.WARN })
return false
end
@ -270,7 +270,7 @@ function M.setup_python_env()
if result.code ~= 0 then
logger.log(
'Failed to setup Python environment: ' .. (result.stderr or ''),
vim.log.levels.ERROR
{ level = vim.log.levels.ERROR }
)
return false
end
@ -292,7 +292,7 @@ function M.setup_python_env()
logger.log(
'No Python environment available. Install uv (https://docs.astral.sh/uv/) or use nix.',
vim.log.levels.WARN
{ level = vim.log.levels.WARN }
)
return false
end