diff --git a/lua/cp/async/init.lua b/lua/cp/async/init.lua deleted file mode 100644 index eac155f..0000000 --- a/lua/cp/async/init.lua +++ /dev/null @@ -1,25 +0,0 @@ -local M = {} - -local active_operation = nil - -function M.start_contest_operation(operation_name) - if active_operation then - error( - ("Contest operation '%s' already active, cannot start '%s'"):format( - active_operation, - operation_name - ) - ) - end - active_operation = operation_name -end - -function M.finish_contest_operation() - active_operation = nil -end - -function M.get_active_operation() - return active_operation -end - -return M diff --git a/lua/cp/async/jobs.lua b/lua/cp/async/jobs.lua deleted file mode 100644 index 55ab1bb..0000000 --- a/lua/cp/async/jobs.lua +++ /dev/null @@ -1,44 +0,0 @@ -local M = {} - -local current_jobs = {} - -function M.start_job(job_id, args, opts, callback) - opts = opts or {} - - if current_jobs[job_id] then - current_jobs[job_id]:kill(9) - current_jobs[job_id] = nil - end - - local job = vim.system(args, opts, function(result) - current_jobs[job_id] = nil - callback(result) - end) - - current_jobs[job_id] = job - return job -end - -function M.kill_job(job_id) - if current_jobs[job_id] then - current_jobs[job_id]:kill(9) - current_jobs[job_id] = nil - end -end - -function M.kill_all_jobs() - for _, job in pairs(current_jobs) do - job:kill(9) - end - current_jobs = {} -end - -function M.get_active_jobs() - local active = {} - for job_id, _ in pairs(current_jobs) do - table.insert(active, job_id) - end - return active -end - -return M diff --git a/lua/cp/async/scraper.lua b/lua/cp/async/scraper.lua deleted file mode 100644 index ff6789f..0000000 --- a/lua/cp/async/scraper.lua +++ /dev/null @@ -1,202 +0,0 @@ -local M = {} -local cache = require('cp.cache') -local jobs = require('cp.async.jobs') -local utils = require('cp.utils') - -local function check_internet_connectivity() - local result = vim.system({ 'ping', '-c', '5', '-W', '3', '8.8.8.8' }, { text = true }):wait() - return result.code == 0 -end - -function M.scrape_contest_metadata_async(platform, contest_id, callback) - vim.validate({ - platform = { platform, 'string' }, - contest_id = { contest_id, 'string' }, - callback = { callback, 'function' }, - }) - - cache.load() - - local cached_data = cache.get_contest_data(platform, contest_id) - if cached_data then - callback({ - success = true, - problems = cached_data.problems, - }) - return - end - - if not check_internet_connectivity() then - callback({ - success = false, - error = 'No internet connection available', - }) - return - end - - if not utils.setup_python_env() then - callback({ - success = false, - error = 'Python environment setup failed', - }) - return - end - - local plugin_path = utils.get_plugin_path() - - local args = { - 'uv', - 'run', - '--directory', - plugin_path, - '-m', - 'scrapers.' .. platform, - 'metadata', - contest_id, - } - - local job_id = 'contest_metadata_' .. platform .. '_' .. contest_id - - jobs.start_job(job_id, args, { - cwd = plugin_path, - text = true, - timeout = 30000, - }, function(result) - if result.code ~= 0 then - callback({ - success = false, - error = 'Failed to run metadata scraper: ' .. (result.stderr or 'Unknown error'), - }) - return - end - - local ok, data = pcall(vim.json.decode, result.stdout) - if not ok then - callback({ - success = false, - error = 'Failed to parse metadata scraper output: ' .. tostring(data), - }) - return - end - - if not data.success then - callback(data) - return - end - - local problems_list = data.problems or {} - cache.set_contest_data(platform, contest_id, problems_list) - - callback({ - success = true, - problems = problems_list, - }) - end) -end - -function M.scrape_problem_async(platform, contest_id, problem_id, callback) - vim.validate({ - platform = { platform, 'string' }, - contest_id = { contest_id, 'string' }, - problem_id = { problem_id, 'string' }, - callback = { callback, 'function' }, - }) - - if not check_internet_connectivity() then - callback({ - success = false, - problem_id = problem_id, - error = 'No internet connection available', - }) - return - end - - if not utils.setup_python_env() then - callback({ - success = false, - problem_id = problem_id, - error = 'Python environment setup failed', - }) - return - end - - local plugin_path = utils.get_plugin_path() - - local args = { - 'uv', - 'run', - '--directory', - plugin_path, - '-m', - 'scrapers.' .. platform, - 'tests', - contest_id, - problem_id, - } - - local job_id = 'problem_tests_' .. platform .. '_' .. contest_id .. '_' .. problem_id - - jobs.start_job(job_id, args, { - cwd = plugin_path, - text = true, - timeout = 30000, - }, function(result) - if result.code ~= 0 then - callback({ - success = false, - problem_id = problem_id, - error = 'Failed to run tests scraper: ' .. (result.stderr or 'Unknown error'), - }) - return - end - - local ok, data = pcall(vim.json.decode, result.stdout) - if not ok then - callback({ - success = false, - problem_id = problem_id, - error = 'Failed to parse tests scraper output: ' .. tostring(data), - }) - return - end - - if not data.success then - callback(data) - return - end - - if data.tests and #data.tests > 0 then - vim.fn.mkdir('io', 'p') - - local cached_test_cases = {} - for i, test_case in ipairs(data.tests) do - table.insert(cached_test_cases, { - index = i, - input = test_case.input, - expected = test_case.expected, - }) - end - - cache.set_test_cases( - platform, - contest_id, - problem_id, - cached_test_cases, - data.timeout_ms, - data.memory_mb - ) - end - - callback({ - success = true, - problem_id = problem_id, - test_count = data.tests and #data.tests or 0, - test_cases = data.tests, - timeout_ms = data.timeout_ms, - memory_mb = data.memory_mb, - url = data.url, - }) - end) -end - -return M diff --git a/lua/cp/async/setup.lua b/lua/cp/async/setup.lua deleted file mode 100644 index 9db5138..0000000 --- a/lua/cp/async/setup.lua +++ /dev/null @@ -1,271 +0,0 @@ -local M = {} - -local async = require('cp.async') -local async_scraper = require('cp.async.scraper') -local cache = require('cp.cache') -local config_module = require('cp.config') -local logger = require('cp.log') -local problem = require('cp.problem') -local state = require('cp.state') - -function M.setup_contest_async(contest_id, language) - if not state.get_platform() then - logger.log('no platform set', vim.log.levels.ERROR) - return - end - - async.start_contest_operation('contest_setup') - - local config = config_module.get_config() - local platform = state.get_platform() or '' - - if not vim.tbl_contains(config.scrapers, platform) then - logger.log('scraping disabled for ' .. platform, vim.log.levels.WARN) - async.finish_contest_operation() - return - end - - logger.log(('setting up contest %s %s'):format(platform, contest_id)) - - async_scraper.scrape_contest_metadata_async(platform, contest_id, function(metadata_result) - if not metadata_result.success then - logger.log( - 'failed to load contest metadata: ' .. (metadata_result.error or 'unknown error'), - vim.log.levels.ERROR - ) - async.finish_contest_operation() - return - end - - local problems = metadata_result.problems - if not problems or #problems == 0 then - logger.log('no problems found in contest', vim.log.levels.ERROR) - async.finish_contest_operation() - return - end - - logger.log(('found %d problems'):format(#problems)) - - state.set_contest_id(contest_id) - M.setup_problem_async(contest_id, problems[1].id, language) - - M.start_background_problem_scraping(contest_id, problems, config) - end) -end - -function M.setup_problem_async(contest_id, problem_id, language) - if not state.get_platform() then - logger.log('no platform set. run :CP first', vim.log.levels.ERROR) - return - end - - local config = config_module.get_config() - local problem_name = contest_id .. (problem_id or '') - logger.log(('setting up problem: %s'):format(problem_name)) - - local ctx = - problem.create_context(state.get_platform() or '', contest_id, problem_id, config, language) - - local cached_test_cases = cache.get_test_cases(state.get_platform() or '', contest_id, problem_id) - if cached_test_cases then - state.set_test_cases(cached_test_cases) - logger.log(('using cached test cases (%d)'):format(#cached_test_cases)) - elseif vim.tbl_contains(config.scrapers, state.get_platform() or '') then - logger.log('test cases not cached, will scrape in background...') - state.set_test_cases(nil) - - async_scraper.scrape_problem_async( - state.get_platform() or '', - contest_id, - problem_id, - function(scrape_result) - if scrape_result.success then - local test_count = scrape_result.test_count or 0 - logger.log( - ('scraped %d test case(s) for %s'):format(test_count, scrape_result.problem_id) - ) - state.set_test_cases(scrape_result.test_cases) - else - logger.log( - 'scraping failed: ' .. (scrape_result.error or 'unknown error'), - vim.log.levels.ERROR - ) - end - end - ) - else - logger.log(('scraping disabled for %s'):format(state.get_platform() or '')) - state.set_test_cases(nil) - end - - vim.cmd.only({ mods = { silent = true } }) - state.set_run_panel_active(false) - state.set_contest_id(contest_id) - state.set_problem_id(problem_id) - - vim.cmd.e(ctx.source_file) - local source_buf = vim.api.nvim_get_current_buf() - - if vim.api.nvim_buf_get_lines(source_buf, 0, -1, true)[1] == '' then - local constants = require('cp.constants') - local has_luasnip, luasnip = pcall(require, 'luasnip') - if has_luasnip then - local filetype = vim.api.nvim_get_option_value('filetype', { buf = source_buf }) - local language_name = constants.filetype_to_language[filetype] - local canonical_language = constants.canonical_filetypes[language_name] or language_name - local prefixed_trigger = ('cp.nvim/%s.%s'):format(state.get_platform(), canonical_language) - - vim.api.nvim_buf_set_lines(0, 0, -1, false, { prefixed_trigger }) - vim.api.nvim_win_set_cursor(0, { 1, #prefixed_trigger }) - vim.cmd.startinsert({ bang = true }) - - vim.schedule(function() - if luasnip.expandable() then - luasnip.expand() - else - vim.api.nvim_buf_set_lines(0, 0, 1, false, { '' }) - vim.api.nvim_win_set_cursor(0, { 1, 0 }) - end - vim.cmd.stopinsert() - end) - else - vim.api.nvim_input(('i%s'):format(state.get_platform())) - end - end - - if config.hooks and config.hooks.setup_code then - config.hooks.setup_code(ctx) - end - - cache.set_file_state( - vim.fn.expand('%:p'), - state.get_platform() or '', - contest_id, - problem_id, - language - ) - - logger.log(('switched to problem %s'):format(ctx.problem_name)) - async.finish_contest_operation() -end - -function M.start_background_problem_scraping(contest_id, problems, _) - cache.load() - local platform = state.get_platform() or '' - local missing_problems = {} - - for _, prob in ipairs(problems) do - local cached_tests = cache.get_test_cases(platform, contest_id, prob.id) - if not cached_tests then - table.insert(missing_problems, prob) - end - end - - if #missing_problems == 0 then - logger.log('all problems already cached') - return - end - - logger.log(('scraping %d uncached problems in background...'):format(#missing_problems)) - - local success_count = 0 - local failed_problems = {} - local total_problems = #missing_problems - - for _, prob in ipairs(missing_problems) do - async_scraper.scrape_problem_async(platform, contest_id, prob.id, function(result) - if result.success then - success_count = success_count + 1 - else - table.insert(failed_problems, prob.id) - end - - local completed = success_count + #failed_problems - if completed == total_problems then - if #failed_problems > 0 then - logger.log( - ('background scraping complete: %d/%d successful, failed: %s'):format( - success_count, - total_problems, - table.concat(failed_problems, ', ') - ), - vim.log.levels.WARN - ) - else - logger.log( - ('background scraping complete: %d/%d successful'):format(success_count, total_problems) - ) - end - end - end) - end -end - -function M.handle_full_setup_async(cmd) - async.start_contest_operation('full_setup') - - state.set_contest_id(cmd.contest) - local config = config_module.get_config() - - if vim.tbl_contains(config.scrapers, cmd.platform) then - async_scraper.scrape_contest_metadata_async(cmd.platform, cmd.contest, function(metadata_result) - if not metadata_result.success then - logger.log( - 'failed to load contest metadata: ' .. (metadata_result.error or 'unknown error'), - vim.log.levels.ERROR - ) - async.finish_contest_operation() - return - end - - logger.log( - ('loaded %d problems for %s %s'):format( - #metadata_result.problems, - cmd.platform, - cmd.contest - ), - vim.log.levels.INFO, - true - ) - - local problem_ids = vim.tbl_map(function(prob) - return prob.id - end, metadata_result.problems) - - if not vim.tbl_contains(problem_ids, cmd.problem) then - logger.log( - ("Invalid problem '%s' for contest %s %s"):format(cmd.problem, cmd.platform, cmd.contest), - vim.log.levels.ERROR - ) - async.finish_contest_operation() - return - end - - M.setup_problem_async(cmd.contest, cmd.problem, cmd.language) - end) - else - cache.load() - local contest_data = cache.get_contest_data(cmd.platform or '', cmd.contest) - if contest_data and contest_data.problems then - local problem_ids = vim.tbl_map(function(prob) - return prob.id - end, contest_data.problems) - - if not vim.tbl_contains(problem_ids, cmd.problem) then - logger.log( - ("Invalid problem '%s' for contest %s %s"):format(cmd.problem, cmd.platform, cmd.contest), - vim.log.levels.ERROR - ) - async.finish_contest_operation() - return - end - - M.setup_problem_async(cmd.contest, cmd.problem, cmd.language) - else - logger.log('no contest data available', vim.log.levels.ERROR) - async.finish_contest_operation() - end - end -end - -return M diff --git a/lua/cp/cache.lua b/lua/cp/cache.lua index d2ec4f2..d2198fb 100644 --- a/lua/cp/cache.lua +++ b/lua/cp/cache.lua @@ -89,9 +89,22 @@ function M.load() end function M.save() - vim.fn.mkdir(vim.fn.fnamemodify(cache_file, ':h'), 'p') + local ok, err = pcall(vim.fn.mkdir, vim.fn.fnamemodify(cache_file, ':h'), 'p') + if not ok then + vim.schedule(function() + vim.fn.mkdir(vim.fn.fnamemodify(cache_file, ':h'), 'p') + end) + return + end + local encoded = vim.json.encode(cache_data) - vim.fn.writefile(vim.split(encoded, '\n'), cache_file) + local lines = vim.split(encoded, '\n') + ok, err = pcall(vim.fn.writefile, lines, cache_file) + if not ok then + vim.schedule(function() + vim.fn.writefile(lines, cache_file) + end) + end end ---@param platform string diff --git a/lua/cp/commands/init.lua b/lua/cp/commands/init.lua index 8b733e0..6dafd00 100644 --- a/lua/cp/commands/init.lua +++ b/lua/cp/commands/init.lua @@ -109,7 +109,9 @@ local function parse_command(args) end function M.handle_command(opts) + logger.log(('command received: %s'):format(vim.inspect(opts.fargs))) local cmd = parse_command(opts.fargs) + logger.log(('parsed command: %s'):format(vim.inspect(cmd))) if cmd.type == 'error' then logger.log(cmd.message, vim.log.levels.ERROR) @@ -123,14 +125,19 @@ function M.handle_command(opts) end if cmd.type == 'action' then + logger.log(('handling action: %s'):format(cmd.action)) local setup = require('cp.setup') local ui = require('cp.ui.panel') if cmd.action == 'run' then + print('running') + logger.log('calling toggle_run_panel') ui.toggle_run_panel(cmd.debug) elseif cmd.action == 'next' then + logger.log('calling navigate_problem(1)') setup.navigate_problem(1, cmd.language) elseif cmd.action == 'prev' then + logger.log('calling navigate_problem(-1)') setup.navigate_problem(-1, cmd.language) elseif cmd.action == 'pick' then local picker = require('cp.commands.picker') @@ -153,25 +160,23 @@ function M.handle_command(opts) if cmd.type == 'contest_setup' then local setup = require('cp.setup') - local async_setup = require('cp.async.setup') if setup.set_platform(cmd.platform) then - async_setup.setup_contest_async(cmd.contest, cmd.language) + setup.setup_contest(cmd.platform, cmd.contest, nil, cmd.language) end return end if cmd.type == 'full_setup' then local setup = require('cp.setup') - local async_setup = require('cp.async.setup') if setup.set_platform(cmd.platform) then - async_setup.handle_full_setup_async(cmd) + setup.setup_contest(cmd.platform, cmd.contest, cmd.problem, cmd.language) end return end if cmd.type == 'problem_switch' then - local async_setup = require('cp.async.setup') - async_setup.setup_problem_async(state.get_contest_id() or '', cmd.problem, cmd.language) + local setup = require('cp.setup') + setup.setup_problem(state.get_contest_id() or '', cmd.problem, cmd.language) return end end diff --git a/lua/cp/log.lua b/lua/cp/log.lua index 7a26001..9c702b4 100644 --- a/lua/cp/log.lua +++ b/lua/cp/log.lua @@ -3,7 +3,9 @@ local M = {} function M.log(msg, level, override) level = level or vim.log.levels.INFO if level >= vim.log.levels.WARN or override then - vim.notify(('[cp.nvim]: %s'):format(msg), level) + vim.schedule(function() + vim.notify(('[cp.nvim]: %s'):format(msg), level) + end) end end diff --git a/lua/cp/pickers/init.lua b/lua/cp/pickers/init.lua index b981b59..d358137 100644 --- a/lua/cp/pickers/init.lua +++ b/lua/cp/pickers/init.lua @@ -2,7 +2,7 @@ local M = {} local cache = require('cp.cache') local logger = require('cp.log') -local scrape = require('cp.scrape') +local scraper = require('cp.scraper') local utils = require('cp.utils') ---@class cp.PlatformItem @@ -35,68 +35,33 @@ end ---@param platform string Platform identifier (e.g. "codeforces", "atcoder") ---@return cp.ContestItem[] local function get_contests_for_platform(platform) - local contests = {} - cache.load() local cached_contests = cache.get_contest_list(platform) if cached_contests then return cached_contests end - if not utils.setup_python_env() then - return contests - end - + -- No cache: start background scraping, return empty for now local constants = require('cp.constants') local platform_display_name = constants.PLATFORM_DISPLAY_NAMES[platform] or platform - logger.log( - ('Scraping %s for contests, this may take a few seconds...'):format(platform_display_name), - vim.log.levels.INFO, - true - ) + logger.log(('Loading %s contests...'):format(platform_display_name), vim.log.levels.INFO, true) - local plugin_path = utils.get_plugin_path() - local cmd = { - 'uv', - 'run', - '--directory', - plugin_path, - '-m', - 'scrapers.' .. platform, - 'contests', - } - local result = vim - .system(cmd, { - cwd = plugin_path, - text = true, - timeout = 30000, - }) - :wait() + scraper.scrape_contest_list(platform, function(result) + if result.success then + logger.log( + ('Loaded %d contests for %s'):format(#(result.contests or {}), platform_display_name), + vim.log.levels.INFO, + true + ) + else + logger.log( + ('Failed to load contests for %s: %s'):format(platform, result.error or 'unknown error'), + vim.log.levels.ERROR + ) + end + end) - if result.code ~= 0 then - logger.log( - ('Failed to get contests for %s: %s'):format(platform, result.stderr or 'unknown error'), - vim.log.levels.ERROR - ) - return contests - end - - local ok, data = pcall(vim.json.decode, result.stdout) - if not ok or not data.success then - logger.log(('Failed to parse contest data for %s'):format(platform), vim.log.levels.ERROR) - return contests - end - - for _, contest in ipairs(data.contests or {}) do - table.insert(contests, { - id = contest.id, - name = contest.name, - display_name = contest.display_name, - }) - end - - cache.set_contest_list(platform, contests) - return contests + return {} end ---Get list of problems for a specific contest @@ -130,20 +95,16 @@ local function get_problems_for_contest(platform, contest_id) true ) - local metadata_result = scrape.scrape_contest_metadata(platform, contest_id) - if not metadata_result.success then + local cached_data = cache.get_contest_data(platform, contest_id) + if not cached_data or not cached_data.problems then logger.log( - ('Failed to get problems for %s %s: %s'):format( - platform, - contest_id, - metadata_result.error or 'unknown error' - ), - vim.log.levels.ERROR + 'No cached contest data found. Run :CP first to scrape contest metadata.', + vim.log.levels.WARN ) return problems end - for _, problem in ipairs(metadata_result.problems or {}) do + for _, problem in ipairs(cached_data.problems) do table.insert(problems, { id = problem.id, name = problem.name, diff --git a/lua/cp/scrape.lua b/lua/cp/scrape.lua deleted file mode 100644 index f7a48c8..0000000 --- a/lua/cp/scrape.lua +++ /dev/null @@ -1,359 +0,0 @@ ----@class ScraperTestCase ----@field input string ----@field expected string - ----@class ScraperResult ----@field success boolean ----@field problem_id string ----@field url? string ----@field tests? ScraperTestCase[] ----@field timeout_ms? number ----@field memory_mb? number ----@field error? string - -local M = {} -local cache = require('cp.cache') -local problem = require('cp.problem') -local utils = require('cp.utils') - -local function check_internet_connectivity() - local result = vim.system({ 'ping', '-c', '5', '-W', '3', '8.8.8.8' }, { text = true }):wait() - return result.code == 0 -end - ----@param platform string ----@param contest_id string ----@return {success: boolean, problems?: table[], error?: string} -function M.scrape_contest_metadata(platform, contest_id) - vim.validate({ - platform = { platform, 'string' }, - contest_id = { contest_id, 'string' }, - }) - - cache.load() - - local cached_data = cache.get_contest_data(platform, contest_id) - if cached_data then - return { - success = true, - problems = cached_data.problems, - } - end - - if not check_internet_connectivity() then - return { - success = false, - error = 'No internet connection available', - } - end - - if not utils.setup_python_env() then - return { - success = false, - error = 'Python environment setup failed', - } - end - - local plugin_path = utils.get_plugin_path() - - local args = { - 'uv', - 'run', - '--directory', - plugin_path, - '-m', - 'scrapers.' .. platform, - 'metadata', - contest_id, - } - - local result = vim - .system(args, { - cwd = plugin_path, - text = true, - timeout = 30000, - }) - :wait() - - if result.code ~= 0 then - return { - success = false, - error = 'Failed to run metadata scraper: ' .. (result.stderr or 'Unknown error'), - } - end - - local ok, data = pcall(vim.json.decode, result.stdout) - if not ok then - return { - success = false, - error = 'Failed to parse metadata scraper output: ' .. tostring(data), - } - end - - if not data.success then - return data - end - - local problems_list = data.problems or {} - - cache.set_contest_data(platform, contest_id, problems_list) - return { - success = true, - problems = problems_list, - } -end - ----@param ctx ProblemContext ----@return {success: boolean, problem_id: string, test_count?: number, test_cases?: ScraperTestCase[], timeout_ms?: number, memory_mb?: number, url?: string, error?: string} -function M.scrape_problem(ctx) - vim.validate({ - ctx = { ctx, 'table' }, - }) - - vim.fn.mkdir('io', 'p') - - if vim.fn.filereadable(ctx.input_file) == 1 and vim.fn.filereadable(ctx.expected_file) == 1 then - local base_name = vim.fn.fnamemodify(ctx.input_file, ':r') - local test_cases = {} - local i = 1 - - while true do - local input_file = base_name .. '.' .. i .. '.cpin' - local expected_file = base_name .. '.' .. i .. '.cpout' - - if vim.fn.filereadable(input_file) == 1 and vim.fn.filereadable(expected_file) == 1 then - local input_content = table.concat(vim.fn.readfile(input_file), '\n') - local expected_content = table.concat(vim.fn.readfile(expected_file), '\n') - - table.insert(test_cases, { - index = i, - input = input_content, - output = expected_content, - }) - i = i + 1 - else - break - end - end - - return { - success = true, - problem_id = ctx.problem_name, - test_count = #test_cases, - test_cases = test_cases, - } - end - - if not check_internet_connectivity() then - return { - success = false, - problem_id = ctx.problem_name, - error = 'No internet connection available', - } - end - - if not utils.setup_python_env() then - return { - success = false, - problem_id = ctx.problem_name, - error = 'Python environment setup failed', - } - end - - local plugin_path = utils.get_plugin_path() - - local args = { - 'uv', - 'run', - '--directory', - plugin_path, - '-m', - 'scrapers.' .. ctx.contest, - 'tests', - ctx.contest_id, - ctx.problem_id, - } - - local result = vim - .system(args, { - cwd = plugin_path, - text = true, - timeout = 30000, - }) - :wait() - - if result.code ~= 0 then - return { - success = false, - problem_id = ctx.problem_name, - error = 'Failed to run tests scraper: ' .. (result.stderr or 'Unknown error'), - } - end - - local ok, data = pcall(vim.json.decode, result.stdout) - if not ok then - return { - success = false, - problem_id = ctx.problem_name, - error = 'Failed to parse tests scraper output: ' .. tostring(data), - } - end - - if not data.success then - return data - end - - if data.tests and #data.tests > 0 then - local base_name = vim.fn.fnamemodify(ctx.input_file, ':r') - - for i, test_case in ipairs(data.tests) do - local input_file = base_name .. '.' .. i .. '.cpin' - local expected_file = base_name .. '.' .. i .. '.cpout' - - local input_content = test_case.input:gsub('\r', '') - local expected_content = test_case.expected:gsub('\r', '') - - vim.fn.writefile(vim.split(input_content, '\n', true), input_file) - vim.fn.writefile(vim.split(expected_content, '\n', true), expected_file) - end - - local cached_test_cases = {} - for i, test_case in ipairs(data.tests) do - table.insert(cached_test_cases, { - index = i, - input = test_case.input, - expected = test_case.expected, - }) - end - - cache.set_test_cases( - ctx.contest, - ctx.contest_id, - ctx.problem_id, - cached_test_cases, - data.timeout_ms, - data.memory_mb - ) - end - - return { - success = true, - problem_id = ctx.problem_name, - test_count = data.tests and #data.tests or 0, - test_cases = data.tests, - timeout_ms = data.timeout_ms, - memory_mb = data.memory_mb, - url = data.url, - } -end - ----@param platform string ----@param contest_id string ----@param problems table[] ----@param config table ----@return table[] -function M.scrape_problems_parallel(platform, contest_id, problems, config) - vim.validate({ - platform = { platform, 'string' }, - contest_id = { contest_id, 'string' }, - problems = { problems, 'table' }, - config = { config, 'table' }, - }) - - if not check_internet_connectivity() then - return {} - end - - if not utils.setup_python_env() then - return {} - end - - local plugin_path = utils.get_plugin_path() - local jobs = {} - - for _, prob in ipairs(problems) do - local args = { - 'uv', - 'run', - '--directory', - plugin_path, - '-m', - 'scrapers.' .. platform, - 'tests', - contest_id, - prob.id, - } - - local job = vim.system(args, { - cwd = plugin_path, - text = true, - timeout = 30000, - }) - - jobs[prob.id] = { - job = job, - problem = prob, - } - end - - local results = {} - for problem_id, job_data in pairs(jobs) do - local result = job_data.job:wait() - local scrape_result = { - success = false, - problem_id = problem_id, - error = 'Unknown error', - } - - if result.code == 0 then - local ok, data = pcall(vim.json.decode, result.stdout) - if ok and data.success then - scrape_result = data - - if data.tests and #data.tests > 0 then - local ctx = problem.create_context(platform, contest_id, problem_id, config) - local base_name = vim.fn.fnamemodify(ctx.input_file, ':r') - - for i, test_case in ipairs(data.tests) do - local input_file = base_name .. '.' .. i .. '.cpin' - local expected_file = base_name .. '.' .. i .. '.cpout' - - local input_content = test_case.input:gsub('\r', '') - local expected_content = test_case.expected:gsub('\r', '') - - vim.fn.writefile(vim.split(input_content, '\n', true), input_file) - vim.fn.writefile(vim.split(expected_content, '\n', true), expected_file) - end - - local cached_test_cases = {} - for i, test_case in ipairs(data.tests) do - table.insert(cached_test_cases, { - index = i, - input = test_case.input, - expected = test_case.expected, - }) - end - - cache.set_test_cases( - platform, - contest_id, - problem_id, - cached_test_cases, - data.timeout_ms, - data.memory_mb - ) - end - else - scrape_result.error = ok and data.error or 'Failed to parse scraper output' - end - else - scrape_result.error = 'Scraper execution failed: ' .. (result.stderr or 'Unknown error') - end - - results[problem_id] = scrape_result - end - - return results -end - -return M diff --git a/lua/cp/scraper.lua b/lua/cp/scraper.lua new file mode 100644 index 0000000..e8d708f --- /dev/null +++ b/lua/cp/scraper.lua @@ -0,0 +1,152 @@ +local M = {} +local cache = require('cp.cache') +local utils = require('cp.utils') + +local function run_scraper(platform, subcommand, args, callback) + if not utils.setup_python_env() then + callback({ success = false, error = 'Python environment setup failed' }) + return + end + + local plugin_path = utils.get_plugin_path() + local cmd = { + 'uv', + 'run', + '--directory', + plugin_path, + '-m', + 'scrapers.' .. platform, + subcommand, + } + + for _, arg in ipairs(args or {}) do + table.insert(cmd, arg) + end + + vim.system(cmd, { + cwd = plugin_path, + text = true, + timeout = 30000, + }, function(result) + if result.code ~= 0 then + callback({ + success = false, + error = 'Scraper failed: ' .. (result.stderr or 'Unknown error'), + }) + return + end + + local ok, data = pcall(vim.json.decode, result.stdout) + if not ok then + callback({ + success = false, + error = 'Failed to parse scraper output: ' .. tostring(data), + }) + return + end + + callback(data) + end) +end + +function M.scrape_contest_metadata(platform, contest_id, callback) + cache.load() + + local cached = cache.get_contest_data(platform, contest_id) + if cached then + callback({ success = true, problems = cached.problems }) + return + end + + run_scraper(platform, 'metadata', { contest_id }, function(result) + if result.success and result.problems then + cache.set_contest_data(platform, contest_id, result.problems) + end + callback(result) + end) +end + +function M.scrape_contest_list(platform, callback) + cache.load() + + local cached = cache.get_contest_list(platform) + if cached then + callback({ success = true, contests = cached }) + return + end + + run_scraper(platform, 'contests', {}, function(result) + if result.success and result.contests then + cache.set_contest_list(platform, result.contests) + end + callback(result) + end) +end + +function M.scrape_problem_tests(platform, contest_id, problem_id, callback) + run_scraper(platform, 'tests', { contest_id, problem_id }, function(result) + if result.success and result.tests then + -- Write test files + vim.schedule(function() + local mkdir_ok = pcall(vim.fn.mkdir, 'io', 'p') + if mkdir_ok then + local config = require('cp.config') + local base_name = config.default_filename(contest_id, problem_id) + local logger = require('cp.log') + + logger.log( + ('writing %d test files for %s (base: %s)'):format(#result.tests, problem_id, base_name) + ) + + for i, test_case in ipairs(result.tests) do + local input_file = 'io/' .. base_name .. '.' .. i .. '.cpin' + local expected_file = 'io/' .. base_name .. '.' .. i .. '.cpout' + + local input_content = test_case.input:gsub('\r', '') + local expected_content = test_case.expected:gsub('\r', '') + + local input_ok = + pcall(vim.fn.writefile, vim.split(input_content, '\n', true), input_file) + local expected_ok = + pcall(vim.fn.writefile, vim.split(expected_content, '\n', true), expected_file) + + if input_ok and expected_ok then + logger.log(('wrote test files: %s, %s'):format(input_file, expected_file)) + else + logger.log( + ('failed to write test files for %s.%d'):format(base_name, i), + vim.log.levels.WARN + ) + end + end + else + local logger = require('cp.log') + logger.log('failed to create io/ directory', vim.log.levels.ERROR) + end + end) + + -- Cache test cases + local cached_tests = {} + for i, test_case in ipairs(result.tests) do + table.insert(cached_tests, { + index = i, + input = test_case.input, + expected = test_case.expected, + }) + end + + cache.set_test_cases( + platform, + contest_id, + problem_id, + cached_tests, + result.timeout_ms, + result.memory_mb + ) + end + + callback(result) + end) +end + +return M diff --git a/lua/cp/setup.lua b/lua/cp/setup.lua new file mode 100644 index 0000000..b38249b --- /dev/null +++ b/lua/cp/setup.lua @@ -0,0 +1,273 @@ +local M = {} + +local cache = require('cp.cache') +local config_module = require('cp.config') +local logger = require('cp.log') +local problem = require('cp.problem') +local scraper = require('cp.scraper') +local state = require('cp.state') + +local constants = require('cp.constants') +local platforms = constants.PLATFORMS + +function M.set_platform(platform) + if not vim.tbl_contains(platforms, platform) then + logger.log( + ('unknown platform: %s. supported: %s'):format(platform, table.concat(platforms, ', ')), + vim.log.levels.ERROR + ) + return false + end + + if state.get_platform() == platform then + logger.log(('platform already set to %s'):format(platform)) + else + state.set_platform(platform) + logger.log(('platform set to %s'):format(platform)) + end + + return true +end + +function M.setup_contest(platform, contest_id, problem_id, language) + if not state.get_platform() then + logger.log('no platform set', vim.log.levels.ERROR) + return + end + + local config = config_module.get_config() + + if not vim.tbl_contains(config.scrapers, platform) then + logger.log('scraping disabled for ' .. platform, vim.log.levels.WARN) + return + end + + logger.log(('setting up contest %s %s'):format(platform, contest_id)) + + scraper.scrape_contest_metadata(platform, contest_id, function(result) + if not result.success then + logger.log( + 'failed to load contest metadata: ' .. (result.error or 'unknown error'), + vim.log.levels.ERROR + ) + return + end + + local problems = result.problems + if not problems or #problems == 0 then + logger.log('no problems found in contest', vim.log.levels.ERROR) + return + end + + logger.log(('found %d problems'):format(#problems)) + + -- Set up specified problem or first problem + state.set_contest_id(contest_id) + local target_problem = problem_id or problems[1].id + + -- Validate problem exists if specified + if problem_id then + local problem_exists = false + for _, prob in ipairs(problems) do + if prob.id == problem_id then + problem_exists = true + break + end + end + if not problem_exists then + logger.log( + ('invalid problem %s for contest %s'):format(problem_id, contest_id), + vim.log.levels.ERROR + ) + return + end + end + + M.setup_problem(contest_id, target_problem, language) + + -- Scrape remaining problems in background + M.scrape_remaining_problems(platform, contest_id, problems) + end) +end + +function M.setup_problem(contest_id, problem_id, language) + if not state.get_platform() then + logger.log('no platform set. run :CP first', vim.log.levels.ERROR) + return + end + + local config = config_module.get_config() + local platform = state.get_platform() or '' + + logger.log(('setting up problem: %s%s'):format(contest_id, problem_id or '')) + + local ctx = problem.create_context(platform, contest_id, problem_id, config, language) + + -- Load test cases for current problem + local cached_tests = cache.get_test_cases(platform, contest_id, problem_id) + if cached_tests then + state.set_test_cases(cached_tests) + logger.log(('using cached test cases (%d)'):format(#cached_tests)) + elseif vim.tbl_contains(config.scrapers, platform) then + logger.log('loading test cases...') + + scraper.scrape_problem_tests(platform, contest_id, problem_id, function(result) + vim.schedule(function() + if result.success then + logger.log(('loaded %d test cases for %s'):format(#(result.tests or {}), problem_id)) + if state.get_problem_id() == problem_id then + state.set_test_cases(result.tests) + end + else + logger.log( + 'failed to load tests: ' .. (result.error or 'unknown error'), + vim.log.levels.ERROR + ) + if state.get_problem_id() == problem_id then + state.set_test_cases({}) + end + end + end) + end) + else + logger.log(('scraping disabled for %s'):format(platform)) + state.set_test_cases({}) + end + + -- Update state immediately (safe in fast event) + state.set_contest_id(contest_id) + state.set_problem_id(problem_id) + state.set_run_panel_active(false) + + -- Schedule vim commands (required for fast event context) + vim.schedule(function() + local ok, err = pcall(function() + vim.cmd.only({ mods = { silent = true } }) + + vim.cmd.e(ctx.source_file) + local source_buf = vim.api.nvim_get_current_buf() + + if vim.api.nvim_buf_get_lines(source_buf, 0, -1, true)[1] == '' then + local has_luasnip, luasnip = pcall(require, 'luasnip') + if has_luasnip then + local filetype = vim.api.nvim_get_option_value('filetype', { buf = source_buf }) + local language_name = constants.filetype_to_language[filetype] + local canonical_language = constants.canonical_filetypes[language_name] or language_name + local prefixed_trigger = ('cp.nvim/%s.%s'):format(platform, canonical_language) + + vim.api.nvim_buf_set_lines(0, 0, -1, false, { prefixed_trigger }) + vim.api.nvim_win_set_cursor(0, { 1, #prefixed_trigger }) + vim.cmd.startinsert({ bang = true }) + + vim.schedule(function() + if luasnip.expandable() then + luasnip.expand() + else + vim.api.nvim_buf_set_lines(0, 0, 1, false, { '' }) + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + end + vim.cmd.stopinsert() + end) + else + vim.api.nvim_input(('i%s'):format(platform)) + end + end + + if config.hooks and config.hooks.setup_code then + config.hooks.setup_code(ctx) + end + + cache.set_file_state(vim.fn.expand('%:p'), platform, contest_id, problem_id, language) + + logger.log(('switched to problem %s'):format(ctx.problem_name)) + end) + + if not ok then + logger.log(('setup error: %s'):format(err), vim.log.levels.ERROR) + end + end) +end + +function M.scrape_remaining_problems(platform, contest_id, problems) + cache.load() + local config = config_module.get_config() + local missing_problems = {} + + for _, prob in ipairs(problems) do + local cached_tests = cache.get_test_cases(platform, contest_id, prob.id) + if not cached_tests then + table.insert(missing_problems, prob) + end + end + + if #missing_problems == 0 then + logger.log('all problems already cached') + return + end + + logger.log(('scraping %d uncached problems in background...'):format(#missing_problems)) + + for _, prob in ipairs(missing_problems) do + logger.log(('starting background scrape for problem %s'):format(prob.id)) + scraper.scrape_problem_tests(platform, contest_id, prob.id, function(result) + if result.success then + logger.log( + ('background: scraped problem %s - %d test cases'):format(prob.id, #(result.tests or {})) + ) + else + logger.log( + ('background: failed to scrape problem %s: %s'):format( + prob.id, + result.error or 'unknown error' + ), + vim.log.levels.WARN + ) + end + end) + end +end + +function M.navigate_problem(direction, language) + local platform = state.get_platform() + 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 contest context', vim.log.levels.ERROR) + return + end + + cache.load() + local contest_data = cache.get_contest_data(platform, contest_id) + if not contest_data or not contest_data.problems then + logger.log('no contest data available', vim.log.levels.ERROR) + return + end + + local problems = contest_data.problems + local current_index = nil + for i, problem in ipairs(problems) do + if problem.id == current_problem_id then + current_index = i + break + end + end + + if not current_index then + logger.log('current problem not found in contest', vim.log.levels.ERROR) + return + end + + local new_index = current_index + direction + if new_index < 1 or new_index > #problems then + logger.log('no more problems in that direction', vim.log.levels.WARN) + return + end + + local new_problem = problems[new_index] + M.setup_problem(contest_id, new_problem.id, language) + + state.set_problem_id(new_problem.id) +end + +return M diff --git a/lua/cp/setup/contest.lua b/lua/cp/setup/contest.lua deleted file mode 100644 index 7649330..0000000 --- a/lua/cp/setup/contest.lua +++ /dev/null @@ -1,43 +0,0 @@ -local M = {} - -local logger = require('cp.log') -local scrape = require('cp.scrape') -local state = require('cp.state') - -function M.scrape_missing_problems(contest_id, missing_problems, config) - vim.fn.mkdir('io', 'p') - - logger.log(('scraping %d uncached problems...'):format(#missing_problems)) - - local results = scrape.scrape_problems_parallel( - state.get_platform() or '', - contest_id, - missing_problems, - config - ) - - local success_count = 0 - local failed_problems = {} - for problem_id, result in pairs(results) do - if result.success then - success_count = success_count + 1 - else - table.insert(failed_problems, problem_id) - end - end - - if #failed_problems > 0 then - logger.log( - ('scraping complete: %d/%d successful, failed: %s'):format( - success_count, - #missing_problems, - table.concat(failed_problems, ', ') - ), - vim.log.levels.WARN - ) - else - logger.log(('scraping complete: %d/%d successful'):format(success_count, #missing_problems)) - end -end - -return M diff --git a/lua/cp/setup/init.lua b/lua/cp/setup/init.lua deleted file mode 100644 index f654e5c..0000000 --- a/lua/cp/setup/init.lua +++ /dev/null @@ -1,260 +0,0 @@ -local M = {} - -local cache = require('cp.cache') -local config_module = require('cp.config') -local logger = require('cp.log') -local problem = require('cp.problem') -local scrape = require('cp.scrape') -local state = require('cp.state') - -local constants = require('cp.constants') -local platforms = constants.PLATFORMS - -function M.set_platform(platform) - if not vim.tbl_contains(platforms, platform) then - logger.log( - ('unknown platform. Available: [%s]'):format(table.concat(platforms, ', ')), - vim.log.levels.ERROR - ) - return false - end - - state.set_platform(platform) - vim.system({ 'mkdir', '-p', 'build', 'io' }):wait() - return true -end - -function M.setup_problem(contest_id, problem_id, language) - if not state.get_platform() then - logger.log('no platform set. run :CP first', vim.log.levels.ERROR) - return - end - - local config = config_module.get_config() - local problem_name = contest_id .. (problem_id or '') - logger.log(('setting up problem: %s'):format(problem_name)) - - local ctx = - problem.create_context(state.get_platform() or '', contest_id, problem_id, config, language) - - if vim.tbl_contains(config.scrapers, state.get_platform() or '') then - cache.load() - local existing_contest_data = cache.get_contest_data(state.get_platform() or '', contest_id) - - if not existing_contest_data then - local metadata_result = scrape.scrape_contest_metadata(state.get_platform() or '', contest_id) - if not metadata_result.success then - logger.log( - 'failed to load contest metadata: ' .. (metadata_result.error or 'unknown error'), - vim.log.levels.WARN - ) - end - end - end - - local cached_test_cases = cache.get_test_cases(state.get_platform() or '', contest_id, problem_id) - if cached_test_cases then - state.set_test_cases(cached_test_cases) - logger.log(('using cached test cases (%d)'):format(#cached_test_cases)) - elseif vim.tbl_contains(config.scrapers, state.get_platform() or '') then - local platform_display_name = constants.PLATFORM_DISPLAY_NAMES[state.get_platform() or ''] - or (state.get_platform() or '') - logger.log( - ('Scraping %s %s %s for test cases, this may take a few seconds...'):format( - platform_display_name, - contest_id, - problem_id - ), - vim.log.levels.INFO, - true - ) - - local scrape_result = scrape.scrape_problem(ctx) - - if not scrape_result.success then - logger.log( - 'scraping failed: ' .. (scrape_result.error or 'unknown error'), - vim.log.levels.ERROR - ) - return - end - - local test_count = scrape_result.test_count or 0 - logger.log(('scraped %d test case(s) for %s'):format(test_count, scrape_result.problem_id)) - state.set_test_cases(scrape_result.test_cases) - - if scrape_result.test_cases then - cache.set_test_cases( - state.get_platform() or '', - contest_id, - problem_id, - scrape_result.test_cases - ) - end - else - logger.log(('scraping disabled for %s'):format(state.get_platform() or '')) - state.set_test_cases(nil) - end - - vim.cmd('silent only') - state.set_run_panel_active(false) - - state.set_contest_id(contest_id) - state.set_problem_id(problem_id) - - vim.cmd.e(ctx.source_file) - local source_buf = vim.api.nvim_get_current_buf() - - if vim.api.nvim_buf_get_lines(source_buf, 0, -1, true)[1] == '' then - local has_luasnip, luasnip = pcall(require, 'luasnip') - if has_luasnip then - local filetype = vim.api.nvim_get_option_value('filetype', { buf = source_buf }) - local language_name = constants.filetype_to_language[filetype] - local canonical_language = constants.canonical_filetypes[language_name] or language_name - local prefixed_trigger = ('cp.nvim/%s.%s'):format(state.get_platform(), canonical_language) - - vim.api.nvim_buf_set_lines(0, 0, -1, false, { prefixed_trigger }) - vim.api.nvim_win_set_cursor(0, { 1, #prefixed_trigger }) - vim.cmd.startinsert({ bang = true }) - - vim.schedule(function() - if luasnip.expandable() then - luasnip.expand() - else - vim.api.nvim_buf_set_lines(0, 0, 1, false, { '' }) - vim.api.nvim_win_set_cursor(0, { 1, 0 }) - end - vim.cmd.stopinsert() - end) - else - vim.api.nvim_input(('i%s'):format(state.get_platform())) - end - end - - if config.hooks and config.hooks.setup_code then - config.hooks.setup_code(ctx) - end - - cache.set_file_state( - vim.fn.expand('%:p'), - state.get_platform() or '', - contest_id, - problem_id, - language - ) - - logger.log(('switched to problem %s'):format(ctx.problem_name)) -end - -function M.setup_contest(contest_id, language) - if not state.get_platform() then - logger.log('no platform set', vim.log.levels.ERROR) - return false - end - - local config = config_module.get_config() - - if not vim.tbl_contains(config.scrapers, state.get_platform() or '') then - logger.log('scraping disabled for ' .. (state.get_platform() or ''), vim.log.levels.WARN) - return false - end - - logger.log(('setting up contest %s %s'):format(state.get_platform() or '', contest_id)) - - local metadata_result = scrape.scrape_contest_metadata(state.get_platform() or '', contest_id) - if not metadata_result.success then - logger.log( - 'failed to load contest metadata: ' .. (metadata_result.error or 'unknown error'), - vim.log.levels.ERROR - ) - return false - end - - local problems = metadata_result.problems - if not problems or #problems == 0 then - logger.log('no problems found in contest', vim.log.levels.ERROR) - return false - end - - logger.log(('found %d problems, checking cache...'):format(#problems)) - - cache.load() - local missing_problems = {} - for _, prob in ipairs(problems) do - local cached_tests = cache.get_test_cases(state.get_platform() or '', contest_id, prob.id) - if not cached_tests then - table.insert(missing_problems, prob) - end - end - - if #missing_problems > 0 then - local contest_scraper = require('cp.setup.contest') - contest_scraper.scrape_missing_problems(contest_id, missing_problems, config) - else - logger.log('all problems already cached') - end - - state.set_contest_id(contest_id) - M.setup_problem(contest_id, problems[1].id, language) - - return true -end - -function M.navigate_problem(delta, language) - if not state.get_platform() or not state.get_contest_id() then - logger.log('no contest set. run :CP first', vim.log.levels.ERROR) - return - end - - local navigation = require('cp.setup.navigation') - navigation.navigate_problem(delta, language) -end - -function M.handle_full_setup(cmd) - state.set_contest_id(cmd.contest) - local problem_ids = {} - local has_metadata = false - local config = config_module.get_config() - - if vim.tbl_contains(config.scrapers, cmd.platform) then - local metadata_result = scrape.scrape_contest_metadata(cmd.platform, cmd.contest) - if not metadata_result.success then - logger.log( - 'failed to load contest metadata: ' .. (metadata_result.error or 'unknown error'), - vim.log.levels.ERROR - ) - return - end - - logger.log( - ('loaded %d problems for %s %s'):format(#metadata_result.problems, cmd.platform, cmd.contest), - vim.log.levels.INFO, - true - ) - problem_ids = vim.tbl_map(function(prob) - return prob.id - end, metadata_result.problems) - has_metadata = true - else - cache.load() - local contest_data = cache.get_contest_data(cmd.platform or '', cmd.contest) - if contest_data and contest_data.problems then - problem_ids = vim.tbl_map(function(prob) - return prob.id - end, contest_data.problems) - has_metadata = true - end - end - - if has_metadata and not vim.tbl_contains(problem_ids, cmd.problem) then - logger.log( - ("Invalid problem '%s' for contest %s %s"):format(cmd.problem, cmd.platform, cmd.contest), - vim.log.levels.ERROR - ) - return - end - - M.setup_problem(cmd.contest, cmd.problem, cmd.language) -end - -return M diff --git a/lua/cp/setup/navigation.lua b/lua/cp/setup/navigation.lua deleted file mode 100644 index bab857b..0000000 --- a/lua/cp/setup/navigation.lua +++ /dev/null @@ -1,64 +0,0 @@ -local M = {} - -local cache = require('cp.cache') -local logger = require('cp.log') -local state = require('cp.state') - -local function get_current_problem() - local filename = vim.fn.expand('%:t:r') - if filename == '' then - logger.log('no file open', vim.log.levels.ERROR) - return nil - end - return filename -end - -function M.navigate_problem(delta, language) - cache.load() - local contest_data = - cache.get_contest_data(state.get_platform() or '', state.get_contest_id() or '') - if not contest_data or not contest_data.problems then - logger.log( - 'no contest metadata found. set up a problem first to cache contest data', - vim.log.levels.ERROR - ) - return - end - - local problems = contest_data.problems - local current_problem_id = state.get_problem_id() - - if not current_problem_id then - logger.log('no current problem set', vim.log.levels.ERROR) - return - end - - local current_index = nil - for i, prob in ipairs(problems) do - if prob.id == current_problem_id then - current_index = i - break - end - end - - if not current_index then - logger.log('current problem not found in contest', vim.log.levels.ERROR) - return - end - - local new_index = current_index + delta - - if new_index < 1 or new_index > #problems then - local msg = delta > 0 and 'at last problem' or 'at first problem' - logger.log(msg, vim.log.levels.WARN) - return - end - - local new_problem = problems[new_index] - local setup = require('cp.setup') - setup.setup_problem(state.get_contest_id() or '', new_problem.id, language) -end - -M.get_current_problem = get_current_problem - -return M diff --git a/lua/cp/ui/panel.lua b/lua/cp/ui/panel.lua index 5f3345d..35a4c9e 100644 --- a/lua/cp/ui/panel.lua +++ b/lua/cp/ui/panel.lua @@ -11,8 +11,7 @@ local current_diff_layout = nil local current_mode = nil local function get_current_problem() - local setup_nav = require('cp.setup.navigation') - return setup_nav.get_current_problem() + return state.get_problem_id() end function M.toggle_run_panel(is_debug) @@ -28,12 +27,15 @@ function M.toggle_run_panel(is_debug) state.saved_session = nil end + print('run panel was active, returning') + state.set_run_panel_active(false) logger.log('test panel closed') return end if not state.get_platform() then + print('no panel active, returning') logger.log( 'No contest configured. Use :CP to set up first.', vim.log.levels.ERROR @@ -42,19 +44,29 @@ function M.toggle_run_panel(is_debug) end local problem_id = get_current_problem() + print(problem_id) if not problem_id then + logger.log('no current problem set', vim.log.levels.ERROR) return end - local config = config_module.get_config() - local ctx = problem.create_context( - state.get_platform() or '', - state.get_contest_id() or '', - state.get_problem_id(), - config + local platform = state.get_platform() + local contest_id = state.get_contest_id() + + logger.log( + ('run panel: platform=%s, contest=%s, problem=%s'):format( + platform or 'nil', + contest_id or 'nil', + problem_id or 'nil' + ) ) + + local config = config_module.get_config() + local ctx = problem.create_context(platform or '', contest_id or '', problem_id, config) local run = require('cp.runner.run') + logger.log(('run panel: checking test cases for %s'):format(ctx.input_file)) + if not run.load_test_cases(ctx, state) then logger.log('no test cases found', vim.log.levels.WARN) return diff --git a/spec/picker_spec.lua b/spec/picker_spec.lua index 6fd5a81..e7ca9b5 100644 --- a/spec/picker_spec.lua +++ b/spec/picker_spec.lua @@ -168,18 +168,11 @@ describe('cp.picker', function() it('returns empty list when scraping fails', function() local cache = require('cp.cache') - local scrape = require('cp.scrape') cache.load = function() end cache.get_contest_data = function(_, _) return nil end - scrape.scrape_contest_metadata = function(_, _) - return { - success = false, - error = 'test error', - } - end picker = spec_helper.fresh_require('cp.pickers', { 'cp.pickers.init' })