From 4e880a2d840b19b69a7c41810033f8f820f86605 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sat, 20 Sep 2025 01:37:39 -0400 Subject: [PATCH] feat(panel): restore cursor --- lua/cp/execute.lua | 17 ++++-------- lua/cp/init.lua | 66 ++++++++++++++++++++++++++++++++++++++++++---- lua/cp/test.lua | 30 +++++++++++++++++++++ 3 files changed, 96 insertions(+), 17 deletions(-) diff --git a/lua/cp/execute.lua b/lua/cp/execute.lua index d336ad8..945011f 100644 --- a/lua/cp/execute.lua +++ b/lua/cp/execute.lua @@ -92,10 +92,7 @@ function M.compile_generic(language_config, substitutions) if result.code == 0 then logger.log(('compilation successful (%.1fms)'):format(compile_time)) else - logger.log( - ('compilation failed (%.1fms): %s'):format(compile_time, result.stderr), - vim.log.levels.WARN - ) + logger.log(('compilation failed (%.1fms)'):format(compile_time)) end return result @@ -202,7 +199,7 @@ end ---@param ctx ProblemContext ---@param contest_config ContestConfig ---@param is_debug? boolean ----@return boolean success +---@return {success: boolean, stderr: string?} function M.compile_problem(ctx, contest_config, is_debug) vim.validate({ ctx = { ctx, 'table' }, @@ -214,7 +211,7 @@ function M.compile_problem(ctx, contest_config, is_debug) if not language_config then logger.log('No configuration for language: ' .. language, vim.log.levels.ERROR) - return false + return { success = false, stderr = 'No configuration for language: ' .. language } end local substitutions = { @@ -229,16 +226,12 @@ function M.compile_problem(ctx, contest_config, is_debug) language_config.compile = compile_cmd local compile_result = M.compile_generic(language_config, substitutions) if compile_result.code ~= 0 then - logger.log( - 'compilation failed: ' .. (compile_result.stderr or 'unknown error'), - vim.log.levels.ERROR - ) - return false + return { success = false, stderr = compile_result.stderr or 'unknown error' } end logger.log(('compilation successful (%s)'):format(is_debug and 'debug mode' or 'test mode')) end - return true + return { success = true, stderr = nil } end function M.run_problem(ctx, contest_config, is_debug) diff --git a/lua/cp/init.lua b/lua/cp/init.lua index b24180c..aed8234 100644 --- a/lua/cp/init.lua +++ b/lua/cp/init.lua @@ -26,6 +26,8 @@ local state = { test_cases = nil, test_states = {}, run_panel_active = false, + saved_cursor_pos = nil, + saved_source_win = nil, } local current_diff_layout = nil @@ -179,6 +181,16 @@ local function toggle_run_panel(is_debug) vim.fn.delete(state.saved_session) state.saved_session = nil end + + if state.saved_source_win and vim.api.nvim_win_is_valid(state.saved_source_win) then + vim.api.nvim_set_current_win(state.saved_source_win) + if state.saved_cursor_pos then + pcall(vim.api.nvim_win_set_cursor, 0, state.saved_cursor_pos) + end + end + + state.saved_cursor_pos = nil + state.saved_source_win = nil state.run_panel_active = false logger.log('test panel closed') return @@ -192,6 +204,9 @@ local function toggle_run_panel(is_debug) return end + state.saved_cursor_pos = vim.api.nvim_win_get_cursor(0) + state.saved_source_win = vim.api.nvim_get_current_win() + local problem_id = get_current_problem() if not problem_id then return @@ -326,8 +341,31 @@ local function toggle_run_panel(is_debug) } end + local function create_single_layout(parent_win, content) + local buf = create_buffer_with_options() + local lines = vim.split(content, '\n', { plain = true, trimempty = true }) + update_buffer_content(buf, lines, {}) + + vim.api.nvim_set_current_win(parent_win) + vim.cmd.split() + local win = vim.api.nvim_get_current_win() + vim.api.nvim_win_set_buf(win, buf) + vim.api.nvim_set_option_value('filetype', 'cptest', { buf = buf }) + + return { + buffers = { buf }, + windows = { win }, + cleanup = function() + pcall(vim.api.nvim_win_close, win, true) + pcall(vim.api.nvim_buf_delete, buf, { force = true }) + end, + } + end + local function create_diff_layout(mode, parent_win, expected_content, actual_content) - if mode == 'git' then + if mode == 'single' then + return create_single_layout(parent_win, actual_content) + elseif mode == 'git' then return create_git_diff_layout(parent_win, expected_content, actual_content) else return create_vim_diff_layout(parent_win, expected_content, actual_content) @@ -344,14 +382,18 @@ local function toggle_run_panel(is_debug) local expected_content = current_test.expected or '' local actual_content = current_test.actual or '(not run yet)' - local should_show_diff = current_test.status == 'fail' and current_test.actual + local is_compilation_failure = current_test.error + and current_test.error:match('Compilation failed') + local should_show_diff = current_test.status == 'fail' + and current_test.actual + and not is_compilation_failure if not should_show_diff then expected_content = expected_content actual_content = actual_content end - local desired_mode = config.run_panel.diff_mode + local desired_mode = is_compilation_failure and 'single' or config.run_panel.diff_mode if current_diff_layout and current_mode ~= desired_mode then local saved_pos = vim.api.nvim_win_get_cursor(0) @@ -380,7 +422,10 @@ local function toggle_run_panel(is_debug) setup_keybindings_for_buffer(buf) end else - if desired_mode == 'git' then + if desired_mode == 'single' then + local lines = vim.split(actual_content, '\n', { plain = true, trimempty = true }) + update_buffer_content(current_diff_layout.buffers[1], lines, {}) + elseif desired_mode == 'git' then local diff_backend = require('cp.diff') local backend = diff_backend.get_best_backend('git') local diff_result = backend.render(expected_content, actual_content) @@ -410,6 +455,8 @@ local function toggle_run_panel(is_debug) vim.api.nvim_win_call(current_diff_layout.windows[2], function() vim.cmd.diffthis() end) + vim.api.nvim_set_option_value('foldcolumn', '0', { win = current_diff_layout.windows[1] }) + vim.api.nvim_set_option_value('foldcolumn', '0', { win = current_diff_layout.windows[2] }) else vim.api.nvim_set_option_value('diff', false, { win = current_diff_layout.windows[1] }) vim.api.nvim_set_option_value('diff', false, { win = current_diff_layout.windows[2] }) @@ -456,6 +503,12 @@ local function toggle_run_panel(is_debug) config.run_panel.diff_mode = config.run_panel.diff_mode == 'vim' and 'git' or 'vim' refresh_run_panel() end, { buffer = buf, silent = true }) + vim.keymap.set('n', config.run_panel.next_test_key, function() + navigate_test_case(1) + end, { buffer = buf, silent = true }) + vim.keymap.set('n', config.run_panel.prev_test_key, function() + navigate_test_case(-1) + end, { buffer = buf, silent = true }) end vim.keymap.set('n', config.run_panel.next_test_key, function() @@ -477,8 +530,11 @@ local function toggle_run_panel(is_debug) local execute_module = require('cp.execute') local contest_config = config.contests[state.platform] - if execute_module.compile_problem(ctx, contest_config, is_debug) then + local compile_result = execute_module.compile_problem(ctx, contest_config, is_debug) + if compile_result.success then test_module.run_all_test_cases(ctx, contest_config, config) + else + test_module.handle_compilation_failure(compile_result.stderr) end refresh_run_panel() diff --git a/lua/cp/test.lua b/lua/cp/test.lua index e894d4a..753b38b 100644 --- a/lua/cp/test.lua +++ b/lua/cp/test.lua @@ -192,7 +192,12 @@ local function run_single_test_case(ctx, contest_config, cp_config, test_case) status = 'fail', actual = '', error = 'Compilation failed: ' .. (compile_result.stderr or 'Unknown error'), + stderr = compile_result.stderr or '', time_ms = 0, + code = compile_result.code, + ok = false, + signal = nil, + timed_out = false, } end end @@ -217,6 +222,15 @@ local function run_single_test_case(ctx, contest_config, cp_config, test_case) local execution_time = (vim.uv.hrtime() - start_time) / 1000000 local actual_output = (result.stdout or ''):gsub('\n$', '') + local stderr_output = (result.stderr or ''):gsub('\n$', '') + + if stderr_output ~= '' then + if actual_output ~= '' then + actual_output = actual_output .. '\n\n--- stderr ---\n' .. stderr_output + else + actual_output = '--- stderr ---\n' .. stderr_output + end + end local max_lines = cp_config.run_panel.max_output_lines local output_lines = vim.split(actual_output, '\n') @@ -251,6 +265,7 @@ local function run_single_test_case(ctx, contest_config, cp_config, test_case) status = status, actual = actual_output, error = result.code ~= 0 and result.stderr or nil, + stderr = result.stderr or '', time_ms = execution_time, code = result.code, ok = ok, @@ -303,6 +318,7 @@ function M.run_test_case(ctx, contest_config, cp_config, index) test_case.status = result.status test_case.actual = result.actual test_case.error = result.error + test_case.stderr = result.stderr test_case.time_ms = result.time_ms test_case.code = result.code test_case.ok = result.ok @@ -329,4 +345,18 @@ function M.get_run_panel_state() return run_panel_state end +function M.handle_compilation_failure(compilation_stderr) + for i, test_case in ipairs(run_panel_state.test_cases) do + test_case.status = 'fail' + test_case.actual = compilation_stderr or 'Compilation failed' + test_case.error = 'Compilation failed' + test_case.stderr = '' + test_case.time_ms = 0 + test_case.code = 1 + test_case.ok = false + test_case.signal = nil + test_case.timed_out = false + end +end + return M