From aae98a5796c818b222d36428bfb5b8b389001dca Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sat, 4 Oct 2025 17:45:49 -0400 Subject: [PATCH] disable scraper disabling --- doc/cp.nvim.txt | 27 +++++++++------- lua/cp/config.lua | 61 +++++------------------------------- lua/cp/constants.lua | 6 ++++ lua/cp/restore.lua | 16 +++------- lua/cp/runner/execute.lua | 65 ++++++++++++++++----------------------- lua/cp/runner/run.lua | 47 ++++++++++++---------------- lua/cp/scraper.lua | 1 - lua/cp/setup.lua | 15 ++------- lua/cp/ui/panel.lua | 16 +++++----- 9 files changed, 88 insertions(+), 166 deletions(-) diff --git a/doc/cp.nvim.txt b/doc/cp.nvim.txt index 70e2504..0628b14 100644 --- a/doc/cp.nvim.txt +++ b/doc/cp.nvim.txt @@ -120,8 +120,8 @@ Here's an example configuration with lazy.nvim: >lua cmd = 'CP', build = 'uv sync', opts = { - contests = { - default = { + platforms = { + cses = { cpp = { compile = { 'g++', '{source}', '-o', '{binary}', '-std=c++17', '-fdiagnostic-colors=always' }, @@ -138,7 +138,6 @@ Here's an example configuration with lazy.nvim: >lua }, snippets = {}, debug = false, - scrapers = { 'atcoder', 'codeforces', 'cses' }, run_panel = { ansi = true, diff_mode = 'vim', @@ -156,23 +155,29 @@ Here's an example configuration with lazy.nvim: >lua < By default, all contests are configured to use C++ with the g++ compiler and ISO standard -17. Python is also configured with the system executable python as a non-default option. Consult lua/cp/config.lua for -more information. +17. Python is also configured with the system executable python. For example, to run CodeForces contests with Python, only the following config is required: { - contests = { + platforms = { codeforces = { - default_langauge = 'python' + default_language = 'python' } } + } + +Any language is supported, provided the proper configuration. For example, to +run CSES problems with Rust: + + { + } *cp.Config* Fields: ~ - {contests} (table) Contest configurations. + {platforms} (table) Contest configurations. {hooks} (|cp.Hooks|) Hook functions called at various stages. {snippets} (table[]) LuaSnip snippet definitions. {debug} (boolean, default: false) Show info messages @@ -189,11 +194,12 @@ is required: Should return full filename with extension. (default: concatenates contest_id and problem_id, lowercased) - *cp.ContestConfig* + *cp.PlatformConfig* Fields: ~ {cpp} (|LanguageConfig|) C++ language configuration. {python} (|LanguageConfig|) Python language configuration. - {default_language} (string, default: "cpp") Default language for contests. + {default_language} (string, default: "cpp") Default language for + platform contests. *cp.LanguageConfig* Fields: ~ @@ -201,7 +207,6 @@ is required: {source}, {binary} placeholders. {test} (string[]) Test execution command template. {debug} (string[], optional) Debug compile command template. - {extension} (string) File extension (e.g. "cc", "py"). {executable} (string, optional) Executable name for interpreted languages. *cp.RunPanelConfig* diff --git a/lua/cp/config.lua b/lua/cp/config.lua index 0bf814a..21686f1 100644 --- a/lua/cp/config.lua +++ b/lua/cp/config.lua @@ -1,14 +1,12 @@ ----@class LanguageConfig +---@class PlatformLanguageConfig ---@field compile? string[] Compile command template ---@field test string[] Test execution command template ---@field debug? string[] Debug command template ---@field executable? string Executable name ----@field version? number Language version ---@field extension? string File extension ----@class ContestConfig ----@field cpp LanguageConfig ----@field python LanguageConfig +---@class PlatformConfig +---@field [string] PlatformLanguageConfig ---@field default_language? string ---@class Hooks @@ -28,22 +26,20 @@ ---@field git DiffGitConfig ---@class cp.Config ----@field platforms table +---@field platforms table ---@field snippets any[] ---@field hooks Hooks ---@field debug boolean ----@field scrapers string[] ---@field filename? fun(contest: string, contest_id: string, problem_id?: string, config: cp.Config, language?: string): string ---@field run_panel RunPanelConfig ---@field diff DiffConfig ---@field picker string|nil ---@class cp.PartialConfig ----@field platforms? table +---@field platforms? table ---@field snippets? any[] ---@field hooks? Hooks ---@field debug? boolean ----@field scrapers? string[] ---@field filename? fun(contest: string, contest_id: string, problem_id?: string, config: cp.Config, language?: string): string ---@field run_panel? RunPanelConfig ---@field diff? DiffConfig @@ -64,7 +60,6 @@ local default_contest_config = { test = { '{source}' }, debug = { '{source}' }, executable = 'python', - extension = 'py', }, default_language = 'cpp', } @@ -111,7 +106,6 @@ function M.setup(user_config) snippets = { user_config.snippets, { 'table', 'nil' }, true }, hooks = { user_config.hooks, { 'table', 'nil' }, true }, debug = { user_config.debug, { 'boolean', 'nil' }, true }, - scrapers = { user_config.scrapers, { 'table', 'nil' }, true }, filename = { user_config.filename, { 'function', 'nil' }, true }, run_panel = { user_config.run_panel, { 'table', 'nil' }, true }, diff = { user_config.diff, { 'table', 'nil' }, true }, @@ -136,22 +130,6 @@ function M.setup(user_config) end end - if user_config.scrapers then - for _, platform_name in ipairs(user_config.scrapers) do - if type(platform_name) ~= 'string' then - error(('Invalid scraper value type. Expected string, got %s'):format(type(platform_name))) - end - if not vim.tbl_contains(constants.PLATFORMS, platform_name) then - error( - ("Invalid platform '%s' in scrapers config. Valid platforms: %s"):format( - platform_name, - table.concat(constants.PLATFORMS, ', ') - ) - ) - end - end - end - if user_config.picker then if not vim.tbl_contains({ 'telescope', 'fzf-lua' }, user_config.picker) then error(("Invalid picker '%s'. Must be 'telescope' or 'fzf-lua'"):format(user_config.picker)) @@ -206,34 +184,9 @@ function M.setup(user_config) }) for _, platform_config in pairs(config.platforms) do - for lang_name, lang_config in pairs(platform_config) do - if type(lang_config) == 'table' and not lang_config.extension then - if lang_name == 'cpp' then - lang_config.extension = 'cpp' - elseif lang_name == 'python' then - lang_config.extension = 'py' - end - end - end - - vim.print(platform_config) if not platform_config.default_language then - local available_langs = {} - for lang_name, lang_config in pairs(platform_config) do - if type(lang_config) == 'table' and lang_name ~= 'default_language' then - table.insert(available_langs, lang_name) - end - end - - if vim.tbl_isemtpy(available_langs) then - error('No language configurations found') - end - - vim.print('sorting langs') - --- arbitrarily break ties - table.sort(available_langs) - vim.print(available_langs) - platform_config.default_language = available_langs[1] + -- arbitrarily choose a language + platform_config.default_language = vim.tbl_keys(platform_config)[1] end end diff --git a/lua/cp/constants.lua b/lua/cp/constants.lua index 7d81242..dce8751 100644 --- a/lua/cp/constants.lua +++ b/lua/cp/constants.lua @@ -24,6 +24,12 @@ M.canonical_filetypes = { [M.PYTHON] = 'python', } +---@type table +M.canonical_filetype_to_extension = { + [M.CPP] = 'cc', + [M.PYTHON] = 'py', +} + ---@type table M.signal_codes = { [128] = 'SIGILL', diff --git a/lua/cp/restore.lua b/lua/cp/restore.lua index 2a02f96..ba2f075 100644 --- a/lua/cp/restore.lua +++ b/lua/cp/restore.lua @@ -5,15 +5,11 @@ local logger = require('cp.log') local state = require('cp.state') function M.restore_from_current_file() - local current_file = vim.fn.expand('%:p') - if current_file == '' then - logger.log('No file is currently open.', vim.log.levels.ERROR) - return false - end - cache.load() + + local current_file = vim.fn.expand('%:p') local file_state = cache.get_file_state(current_file) - if not file_state then + if current_file or not file_state then logger.log('No cached state found for current file.', vim.log.levels.ERROR) return false end @@ -27,13 +23,9 @@ function M.restore_from_current_file() ) local setup = require('cp.setup') - if not setup.set_platform(file_state.platform) then - return false - end - + local _ = setup.set_platform(file_state.platform) state.set_contest_id(file_state.contest_id) state.set_problem_id(file_state.problem_id) - setup.setup_contest(file_state.platform, file_state.contest_id, file_state.problem_id) return true diff --git a/lua/cp/runner/execute.lua b/lua/cp/runner/execute.lua index 9a70343..ffd428a 100644 --- a/lua/cp/runner/execute.lua +++ b/lua/cp/runner/execute.lua @@ -7,26 +7,28 @@ ---@field peak_mb number ---@field signal string|nil +---@class SubstitutableCommand +---@field source string substituted via '{source}' +---@field binary string substitued via '{binary}' + local M = {} local constants = require('cp.constants') local logger = require('cp.log') local utils = require('cp.utils') -local filetype_to_language = constants.filetype_to_language - -local function get_language_from_file(source_file, contest_config) - local ext = vim.fn.fnamemodify(source_file, ':e') - return filetype_to_language[ext] or contest_config.default_language -end - +---@param cmd_template string[] +---@param substitutions SubstitutableCommand +---@return string[] string normalized with substitutions local function substitute_template(cmd_template, substitutions) local out = {} - for _, a in ipairs(cmd_template) do - local s = a - for k, v in pairs(substitutions) do - s = s:gsub('{' .. k .. '}', v) + for _, arg in ipairs(cmd_template) do + if arg == '{source}' and substitutions.source then + table.insert(out, substitutions.source) + elseif arg == '{binary}' and substitutions.binary then + table.insert(out, substitutions.binary) + else + table.insert(out, arg) end - table.insert(out, s) end return out end @@ -39,12 +41,10 @@ function M.build_command(cmd_template, executable, substitutions) return cmd end -function M.compile(language_config, substitutions) - if not language_config.compile then - return { code = 0, stdout = '' } - end - - local cmd = substitute_template(language_config.compile, substitutions) +---@param compile_cmd string[] +---@param substitutions SubstitutableCommand +function M.compile(compile_cmd, substitutions) + local cmd = substitute_template(compile_cmd, substitutions) local sh = table.concat(cmd, ' ') .. ' 2>&1' local t0 = vim.uv.hrtime() @@ -164,32 +164,19 @@ function M.run(cmd, stdin, timeout_ms, memory_mb) } end -function M.compile_problem(contest_config, is_debug) +function M.compile_problem() local state = require('cp.state') - local source_file = state.get_source_file() - if not source_file then - return { success = false, output = 'No source file found.' } - end - local language = get_language_from_file(source_file, contest_config) - local language_config = contest_config[language] - if not language_config then - return { success = false, output = ('No configuration for language %s.'):format(language) } - end - - local binary_file = state.get_binary_file() - local substitutions = { source = source_file, binary = binary_file } - - local chosen = (is_debug and language_config.debug) and language_config.debug - or language_config.compile - if not chosen then + local config = require('cp.config').get_config() + local platform = state.get_platform() or '' + local language = config.platforms[platform].default_language + local compile_config = config.platforms[platform][language].compile + if not compile_config then return { success = true, output = nil } end - local saved = language_config.compile - language_config.compile = chosen - local r = M.compile(language_config, substitutions) - language_config.compile = saved + local substitutions = { source = state.get_source_file(), binary = state.get_binary_file() } + local r = M.compile(compile_config, substitutions) if r.code ~= 0 then return { success = false, output = r.stdout or 'unknown error' } diff --git a/lua/cp/runner/run.lua b/lua/cp/runner/run.lua index dbfb52b..03857f6 100644 --- a/lua/cp/runner/run.lua +++ b/lua/cp/runner/run.lua @@ -31,8 +31,11 @@ local M = {} local cache = require('cp.cache') +local config = require('cp.config').get_config() local constants = require('cp.constants') +local execute = require('cp.runner.execute') local logger = require('cp.log') +local state = require('cp.state') ---@type RunPanelState local run_panel_state = { @@ -90,42 +93,35 @@ local function create_sentinal_panel_data(test_cases) return out end ----@param language_config LanguageConfig ----@param substitutions table +---@param cmd string[] +---@param executable string ---@return string[] -local function build_command(language_config, substitutions) - local execute = require('cp.runner.execute') - return execute.build_command(language_config.test, language_config.executable, substitutions) +local function build_command(cmd, executable, substitutions) + return execute.build_command(cmd, executable, substitutions) end ----@param contest_config ContestConfig ----@param cp_config cp.Config ---@param test_case RanTestCase ---@return { status: "pass"|"fail"|"tle"|"mle", actual: string, actual_highlights: Highlight[], error: string, stderr: string, time_ms: number, code: integer, ok: boolean, signal: string, tled: boolean, mled: boolean, rss_mb: number } -local function run_single_test_case(contest_config, cp_config, test_case) - local state = require('cp.state') - local exec = require('cp.runner.execute') - +local function run_single_test_case(test_case) local source_file = state.get_source_file() - local ext = vim.fn.fnamemodify(source_file or '', ':e') - local lang_name = constants.filetype_to_language[ext] or contest_config.default_language - local language_config = contest_config[lang_name] local binary_file = state.get_binary_file() local substitutions = { source = source_file, binary = binary_file } - local cmd = build_command(language_config, substitutions) + local platform_config = config.platforms[state.get_platform() or ''] + local language_config = platform_config[platform_config.default_language] + local cmd = build_command(language_config.test, language_config.executable, substitutions) local stdin_content = (test_case.input or '') .. '\n' local timeout_ms = (run_panel_state.constraints and run_panel_state.constraints.timeout_ms) or 0 local memory_mb = run_panel_state.constraints and run_panel_state.constraints.memory_mb or 0 - local r = exec.run(cmd, stdin_content, timeout_ms, memory_mb) + local r = execute.run(cmd, stdin_content, timeout_ms, memory_mb) local ansi = require('cp.ui.ansi') local out = r.stdout or '' local highlights = {} if out ~= '' then - if cp_config.run_panel.ansi then + if config.run_panel.ansi then local parsed = ansi.parse_ansi_text(out) out = table.concat(parsed.lines, '\n') highlights = parsed.highlights @@ -134,7 +130,7 @@ local function run_single_test_case(contest_config, cp_config, test_case) end end - local max_lines = cp_config.run_panel.max_output_lines + local max_lines = config.run_panel.max_output_lines local lines = vim.split(out, '\n') if #lines > max_lines then local trimmed = {} @@ -180,9 +176,8 @@ local function run_single_test_case(contest_config, cp_config, test_case) } end ----@param state table ---@return boolean -function M.load_test_cases(state) +function M.load_test_cases() local tcs = cache.get_test_cases( state.get_platform() or '', state.get_contest_id() or '', @@ -201,18 +196,16 @@ function M.load_test_cases(state) return #tcs > 0 end ----@param contest_config ContestConfig ----@param cp_config cp.Config ---@param index number ---@return boolean -function M.run_test_case(contest_config, cp_config, index) +function M.run_test_case(index) local tc = run_panel_state.test_cases[index] if not tc then return false end tc.status = 'running' - local r = run_single_test_case(contest_config, cp_config, tc) + local r = run_single_test_case(tc) tc.status = r.status tc.actual = r.actual @@ -230,13 +223,11 @@ function M.run_test_case(contest_config, cp_config, index) return true end ----@param contest_config ContestConfig ----@param cp_config cp.Config ---@return RanTestCase[] -function M.run_all_test_cases(contest_config, cp_config) +function M.run_all_test_cases() local results = {} for i = 1, #run_panel_state.test_cases do - M.run_test_case(contest_config, cp_config, i) + M.run_test_case(i) results[i] = run_panel_state.test_cases[i] end return results diff --git a/lua/cp/scraper.lua b/lua/cp/scraper.lua index 5a973e5..c877f9a 100644 --- a/lua/cp/scraper.lua +++ b/lua/cp/scraper.lua @@ -4,7 +4,6 @@ local utils = require('cp.utils') local function syshandle(result) if result.code ~= 0 then - vim.print(('<%s>'):format(result.stderr)) local msg = 'Scraper failed: ' .. (result.stderr or 'Unknown error') return { success = false, error = msg } end diff --git a/lua/cp/setup.lua b/lua/cp/setup.lua index e4705f5..afab845 100644 --- a/lua/cp/setup.lua +++ b/lua/cp/setup.lua @@ -42,19 +42,13 @@ end ---@param contest_id string ---@param problem_id string|nil function M.setup_contest(platform, contest_id, problem_id) - local config = config_module.get_config() - if not vim.tbl_contains(config.scrapers, platform) then - logger.log(('Scraping disabled for %s.'):format(platform), vim.log.levels.WARN) - return - end - state.set_contest_id(contest_id) cache.load() local function proceed(contest_data) local problems = contest_data.problems local pid = problems[(problem_id and contest_data.index_map[problem_id] or 1)].id - M.setup_problem(pid, language) + M.setup_problem(pid) local cached_len = #vim.tbl_filter(function(p) return not vim.tbl_isempty(cache.get_test_cases(platform, contest_id, p.id)) @@ -96,8 +90,7 @@ function M.setup_contest(platform, contest_id, problem_id) end ---@param problem_id string ----@param language? string -function M.setup_problem(problem_id, language) +function M.setup_problem(problem_id) local platform = state.get_platform() if not platform then logger.log('No platform set.', vim.log.levels.ERROR) @@ -111,10 +104,8 @@ function M.setup_problem(problem_id, language) vim.schedule(function() vim.cmd.only({ mods = { silent = true } }) + local language = config.platforms[platform].default_language local source_file = state.get_source_file(language) - if not source_file then - return - end vim.cmd.e(source_file) local source_buf = vim.api.nvim_get_current_buf() diff --git a/lua/cp/ui/panel.lua b/lua/cp/ui/panel.lua index e4a3922..7b7ee6a 100644 --- a/lua/cp/ui/panel.lua +++ b/lua/cp/ui/panel.lua @@ -90,10 +90,8 @@ function M.toggle_interactive() vim.cmd(('mksession! %s'):format(state.saved_interactive_session)) vim.cmd('silent only') - local config = config_module.get_config() - local contest_config = config.platforms[state.get_platform() or ''] local execute = require('cp.runner.execute') - local compile_result = execute.compile_problem(contest_config, false) + local compile_result = execute.compile_problem() if not compile_result.success then require('cp.runner.run').handle_compilation_failure(compile_result.output) return @@ -120,7 +118,8 @@ function M.toggle_interactive() state.set_active_panel('interactive') end -function M.toggle_run_panel(is_debug) +---@param debug? boolean +function M.toggle_run_panel(debug) if state.get_active_panel() == 'run' then if current_diff_layout then current_diff_layout.cleanup() @@ -191,7 +190,7 @@ function M.toggle_run_panel(is_debug) if config.hooks and config.hooks.before_run then config.hooks.before_run(state) end - if is_debug and config.hooks and config.hooks.before_debug then + if debug and config.hooks and config.hooks.before_debug then config.hooks.before_debug(state) end @@ -199,7 +198,7 @@ function M.toggle_run_panel(is_debug) local input_file = state.get_input_file() logger.log(('run panel: checking test cases for %s'):format(input_file or 'none')) - if not run.load_test_cases(state) then + if not run.load_test_cases() then logger.log('no test cases found', vim.log.levels.WARN) return end @@ -284,10 +283,9 @@ function M.toggle_run_panel(is_debug) setup_keybindings_for_buffer(test_buffers.tab_buf) local execute = require('cp.runner.execute') - local contest_config = config.platforms[state.get_platform() or ''] - local compile_result = execute.compile_problem(contest_config, is_debug) + local compile_result = execute.compile_problem() if compile_result.success then - run.run_all_test_cases(contest_config, config) + run.run_all_test_cases() else run.handle_compilation_failure(compile_result.output) end