From 282d7013270c230bb518da7d8368cccfbc07880c Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Tue, 27 Jan 2026 16:10:00 -0500 Subject: [PATCH 1/6] fix: minor log msg tweak --- lua/cp/setup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/cp/setup.lua b/lua/cp/setup.lua index 56519e4..fce1a0c 100644 --- a/lua/cp/setup.lua +++ b/lua/cp/setup.lua @@ -82,7 +82,7 @@ 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 - logger.log(('Fetching problem test data... (%d/%d)'):format(cached_len, #problems)) + 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 From 0b21d02f243ddf63bb84ee4ee33f42d8ae0c457b Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Tue, 27 Jan 2026 16:42:16 -0500 Subject: [PATCH 2/6] fix(runner): save buffer before compile --- lua/cp/runner/execute.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lua/cp/runner/execute.lua b/lua/cp/runner/execute.lua index a60ffa3..5c89c8a 100644 --- a/lua/cp/runner/execute.lua +++ b/lua/cp/runner/execute.lua @@ -177,6 +177,16 @@ function M.compile_problem(debug, on_complete) local language = state.get_language() or config.platforms[platform].default_language local eff = config.runtime.effective[platform][language] + local source_file = state.get_source_file() + if source_file then + local buf = vim.fn.bufnr(source_file) + if buf ~= -1 and vim.api.nvim_buf_is_loaded(buf) and vim.bo[buf].modified then + vim.api.nvim_buf_call(buf, function() + vim.cmd.write({ mods = { silent = true, noautocmd = true } }) + end) + end + end + local compile_config = (debug and eff.commands.debug) or eff.commands.build if not compile_config then From 9af359eb0117f227e012ef839a7d17f47cd0e517 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Tue, 27 Jan 2026 16:47:42 -0500 Subject: [PATCH 3/6] feat(layout): cleanup mode labels --- lua/cp/ui/layouts.lua | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lua/cp/ui/layouts.lua b/lua/cp/ui/layouts.lua index 4e737d3..6fe7391 100644 --- a/lua/cp/ui/layouts.lua +++ b/lua/cp/ui/layouts.lua @@ -3,6 +3,12 @@ local M = {} local helpers = require('cp.helpers') local utils = require('cp.utils') +local MODE_LABELS = { + none = 'none', + vim = 'vim', + git = 'git', +} + local function create_none_diff_layout(parent_win, expected_content, actual_content) local expected_buf = utils.create_buffer_with_options() local actual_buf = utils.create_buffer_with_options() @@ -21,8 +27,9 @@ local function create_none_diff_layout(parent_win, expected_content, actual_cont vim.api.nvim_set_option_value('filetype', 'cp', { buf = expected_buf }) vim.api.nvim_set_option_value('filetype', 'cp', { buf = actual_buf }) - vim.api.nvim_set_option_value('winbar', 'Expected', { win = expected_win }) - vim.api.nvim_set_option_value('winbar', 'Actual', { win = actual_win }) + local label = MODE_LABELS.none + vim.api.nvim_set_option_value('winbar', ('expected [%s]'):format(label), { win = expected_win }) + vim.api.nvim_set_option_value('winbar', ('actual [%s]'):format(label), { win = actual_win }) local expected_lines = vim.split(expected_content, '\n', { plain = true, trimempty = true }) local actual_lines = vim.split(actual_content, '\n', { plain = true }) @@ -33,6 +40,7 @@ local function create_none_diff_layout(parent_win, expected_content, actual_cont return { buffers = { expected_buf, actual_buf }, windows = { expected_win, actual_win }, + mode = 'none', cleanup = function() pcall(vim.api.nvim_win_close, expected_win, true) pcall(vim.api.nvim_win_close, actual_win, true) @@ -60,8 +68,9 @@ local function create_vim_diff_layout(parent_win, expected_content, actual_conte vim.api.nvim_set_option_value('filetype', 'cp', { buf = expected_buf }) vim.api.nvim_set_option_value('filetype', 'cp', { buf = actual_buf }) - vim.api.nvim_set_option_value('winbar', 'Expected', { win = expected_win }) - vim.api.nvim_set_option_value('winbar', 'Actual', { win = actual_win }) + local label = MODE_LABELS.vim + vim.api.nvim_set_option_value('winbar', ('Expected (%s)'):format(label), { win = expected_win }) + vim.api.nvim_set_option_value('winbar', ('Actual (%s)'):format(label), { win = actual_win }) local expected_lines = vim.split(expected_content, '\n', { plain = true, trimempty = true }) local actual_lines = vim.split(actual_content, '\n', { plain = true }) @@ -83,6 +92,7 @@ local function create_vim_diff_layout(parent_win, expected_content, actual_conte return { buffers = { expected_buf, actual_buf }, windows = { expected_win, actual_win }, + mode = 'vim', cleanup = function() pcall(vim.api.nvim_win_close, expected_win, true) pcall(vim.api.nvim_win_close, actual_win, true) @@ -103,7 +113,8 @@ local function create_git_diff_layout(parent_win, expected_content, actual_conte vim.api.nvim_win_set_buf(diff_win, diff_buf) vim.api.nvim_set_option_value('filetype', 'cp', { buf = diff_buf }) - vim.api.nvim_set_option_value('winbar', 'Expected vs Actual', { win = diff_win }) + local label = MODE_LABELS.git + vim.api.nvim_set_option_value('winbar', ('Diff (%s)'):format(label), { win = diff_win }) local diff_backend = require('cp.ui.diff') local backend = diff_backend.get_best_backend('git') @@ -121,6 +132,7 @@ local function create_git_diff_layout(parent_win, expected_content, actual_conte return { buffers = { diff_buf }, windows = { diff_win }, + mode = 'git', cleanup = function() pcall(vim.api.nvim_win_close, diff_win, true) pcall(vim.api.nvim_buf_delete, diff_buf, { force = true }) @@ -143,6 +155,7 @@ local function create_single_layout(parent_win, content) return { buffers = { buf }, windows = { win }, + mode = 'single', cleanup = function() pcall(vim.api.nvim_win_close, win, true) pcall(vim.api.nvim_buf_delete, buf, { force = true }) From ee38da50745c0ae7029139a08ee979c88db2b36e Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Tue, 27 Jan 2026 16:47:50 -0500 Subject: [PATCH 4/6] feat(layout): change formatting --- lua/cp/ui/layouts.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/cp/ui/layouts.lua b/lua/cp/ui/layouts.lua index 6fe7391..365ec78 100644 --- a/lua/cp/ui/layouts.lua +++ b/lua/cp/ui/layouts.lua @@ -69,8 +69,8 @@ local function create_vim_diff_layout(parent_win, expected_content, actual_conte vim.api.nvim_set_option_value('filetype', 'cp', { buf = expected_buf }) vim.api.nvim_set_option_value('filetype', 'cp', { buf = actual_buf }) local label = MODE_LABELS.vim - vim.api.nvim_set_option_value('winbar', ('Expected (%s)'):format(label), { win = expected_win }) - vim.api.nvim_set_option_value('winbar', ('Actual (%s)'):format(label), { win = actual_win }) + vim.api.nvim_set_option_value('winbar', ('expected [%s]'):format(label), { win = expected_win }) + vim.api.nvim_set_option_value('winbar', ('actual [%s]'):format(label), { win = actual_win }) local expected_lines = vim.split(expected_content, '\n', { plain = true, trimempty = true }) local actual_lines = vim.split(actual_content, '\n', { plain = true }) From 3348ac3e51017f4ff9ff3704c245000b1269b311 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Tue, 27 Jan 2026 16:48:04 -0500 Subject: [PATCH 5/6] feat: improve formatting --- lua/cp/ui/layouts.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/cp/ui/layouts.lua b/lua/cp/ui/layouts.lua index 365ec78..387f0ec 100644 --- a/lua/cp/ui/layouts.lua +++ b/lua/cp/ui/layouts.lua @@ -114,7 +114,7 @@ local function create_git_diff_layout(parent_win, expected_content, actual_conte vim.api.nvim_set_option_value('filetype', 'cp', { buf = diff_buf }) local label = MODE_LABELS.git - vim.api.nvim_set_option_value('winbar', ('Diff (%s)'):format(label), { win = diff_win }) + vim.api.nvim_set_option_value('winbar', ('diff [%s]'):format(label), { win = diff_win }) local diff_backend = require('cp.ui.diff') local backend = diff_backend.get_best_backend('git') From d89a40b21f0058496db1a8e80ce744c13643e9fc Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Tue, 27 Jan 2026 17:18:52 -0500 Subject: [PATCH 6/6] feat: update git formatting --- doc/cp.nvim.txt | 21 ++++++++++++------- lua/cp/config.lua | 49 ++++++++++++++++++++++++++++++++++--------- lua/cp/ui/layouts.lua | 47 +++++++++++++++++++++++++---------------- lua/cp/ui/views.lua | 6 +++--- 4 files changed, 84 insertions(+), 39 deletions(-) diff --git a/doc/cp.nvim.txt b/doc/cp.nvim.txt index 570beb5..866346f 100644 --- a/doc/cp.nvim.txt +++ b/doc/cp.nvim.txt @@ -258,7 +258,7 @@ Here's an example configuration with lazy.nvim: prev_test_key = '', -- or nil to disable }, panel = { - diff_mode = 'vim', + diff_modes = { 'side-by-side', 'git', 'vim' }, max_output_lines = 50, }, diff = { @@ -378,8 +378,10 @@ run CSES problems with Rust using the single schema: *cp.PanelConfig* Fields: ~ - {diff_mode} (string, default: "none") Diff backend: "none", - "vim", or "git". + {diff_modes} (string[], default: {'side-by-side', 'git', 'vim'}) + List of diff modes to cycle through with 't' key. + First element is the default mode. + Valid modes: 'side-by-side', 'git', 'vim'. {max_output_lines} (number, default: 50) Maximum lines of test output. *cp.DiffConfig* @@ -851,17 +853,20 @@ PANEL KEYMAPS *cp-panel-keys* Navigate to next test case Navigate to previous test case -t Cycle through diff modes: none → git → vim +t Cycle through configured diff modes (see |cp.PanelConfig|) q Exit panel and restore layout Exit interactive terminal and restore layout Diff Modes ~ -Three diff backends are available: +Three diff modes are available: - none Nothing - vim Built-in vim diff (default, always available) - git Character-level git word-diff (requires git, more precise) + side-by-side Expected and actual output shown side-by-side (default) + vim Built-in vim diff (always available) + git Character-level git word-diff (requires git, more precise) + +Configure which modes to cycle through via |cp.PanelConfig|.diff_modes. +The first element is used as the default mode. The git backend shows character-level changes with [-removed-] and {+added+} markers. diff --git a/lua/cp/config.lua b/lua/cp/config.lua index 78f321f..ca5b027 100644 --- a/lua/cp/config.lua +++ b/lua/cp/config.lua @@ -18,7 +18,7 @@ ---@field overrides? table ---@class PanelConfig ----@field diff_mode "none"|"vim"|"git" +---@field diff_modes string[] ---@field max_output_lines integer ---@class DiffGitConfig @@ -173,7 +173,7 @@ M.defaults = { add_test_key = 'ga', save_and_exit_key = 'q', }, - panel = { diff_mode = 'none', max_output_lines = 50 }, + panel = { diff_modes = { 'side-by-side', 'git', 'vim' }, max_output_lines = 50 }, diff = { git = { args = { 'diff', '--no-index', '--word-diff=plain', '--word-diff-regex=.', '--no-prefix' }, @@ -313,15 +313,26 @@ function M.setup(user_config) setup_io_output = { cfg.hooks.setup_io_output, { 'function', 'nil' }, true }, }) + local layouts = require('cp.ui.layouts') + local valid_modes_str = table.concat(vim.tbl_keys(layouts.DIFF_MODES), ',') + if type(cfg.ui.panel.diff_modes) == 'table' then + local invalid = {} + for _, mode in ipairs(cfg.ui.panel.diff_modes) do + if not layouts.DIFF_MODES[mode] then + table.insert(invalid, mode) + end + end + if #invalid > 0 then + error( + ('invalid diff modes [%s] - must be one of: {%s}'):format( + table.concat(invalid, ','), + valid_modes_str + ) + ) + end + end vim.validate({ ansi = { cfg.ui.ansi, 'boolean' }, - diff_mode = { - cfg.ui.panel.diff_mode, - function(v) - return vim.tbl_contains({ 'none', 'vim', 'git' }, v) - end, - "diff_mode must be 'none', 'vim', or 'git'", - }, max_output_lines = { cfg.ui.panel.max_output_lines, function(v) @@ -383,6 +394,13 @@ function M.setup(user_config) end, 'nil or non-empty string', }, + picker = { + cfg.ui.picker, + function(v) + return v == nil or v == 'telescope' or v == 'fzf-lua' + end, + "nil, 'telescope', or 'fzf-lua'", + }, }) for id, lang in pairs(cfg.languages) do @@ -443,7 +461,18 @@ function M.get_language_for_platform(platform_id, language_id) } end - local effective = cfg.runtime.effective[platform_id][language_id] + local platform_effective = cfg.runtime.effective[platform_id] + if not platform_effective then + return { + valid = false, + error = string.format( + 'No runtime config for platform %s (plugin not initialized)', + platform_id + ), + } + end + + local effective = platform_effective[language_id] if not effective then return { valid = false, diff --git a/lua/cp/ui/layouts.lua b/lua/cp/ui/layouts.lua index 387f0ec..9b40f49 100644 --- a/lua/cp/ui/layouts.lua +++ b/lua/cp/ui/layouts.lua @@ -3,13 +3,13 @@ local M = {} local helpers = require('cp.helpers') local utils = require('cp.utils') -local MODE_LABELS = { - none = 'none', +M.DIFF_MODES = { + ['side-by-side'] = 'side-by-side', vim = 'vim', git = 'git', } -local function create_none_diff_layout(parent_win, expected_content, actual_content) +local function create_side_by_side_layout(parent_win, expected_content, actual_content) local expected_buf = utils.create_buffer_with_options() local actual_buf = utils.create_buffer_with_options() helpers.clearcol(expected_buf) @@ -27,9 +27,13 @@ local function create_none_diff_layout(parent_win, expected_content, actual_cont vim.api.nvim_set_option_value('filetype', 'cp', { buf = expected_buf }) vim.api.nvim_set_option_value('filetype', 'cp', { buf = actual_buf }) - local label = MODE_LABELS.none - vim.api.nvim_set_option_value('winbar', ('expected [%s]'):format(label), { win = expected_win }) - vim.api.nvim_set_option_value('winbar', ('actual [%s]'):format(label), { win = actual_win }) + local label = M.DIFF_MODES['side-by-side'] + vim.api.nvim_set_option_value( + 'winbar', + ('expected (diff: %s)'):format(label), + { win = expected_win } + ) + vim.api.nvim_set_option_value('winbar', ('actual (diff: %s)'):format(label), { win = actual_win }) local expected_lines = vim.split(expected_content, '\n', { plain = true, trimempty = true }) local actual_lines = vim.split(actual_content, '\n', { plain = true }) @@ -40,7 +44,7 @@ local function create_none_diff_layout(parent_win, expected_content, actual_cont return { buffers = { expected_buf, actual_buf }, windows = { expected_win, actual_win }, - mode = 'none', + mode = 'side-by-side', cleanup = function() pcall(vim.api.nvim_win_close, expected_win, true) pcall(vim.api.nvim_win_close, actual_win, true) @@ -68,9 +72,13 @@ local function create_vim_diff_layout(parent_win, expected_content, actual_conte vim.api.nvim_set_option_value('filetype', 'cp', { buf = expected_buf }) vim.api.nvim_set_option_value('filetype', 'cp', { buf = actual_buf }) - local label = MODE_LABELS.vim - vim.api.nvim_set_option_value('winbar', ('expected [%s]'):format(label), { win = expected_win }) - vim.api.nvim_set_option_value('winbar', ('actual [%s]'):format(label), { win = actual_win }) + local label = M.DIFF_MODES.vim + vim.api.nvim_set_option_value( + 'winbar', + ('expected (diff: %s)'):format(label), + { win = expected_win } + ) + vim.api.nvim_set_option_value('winbar', ('actual (diff: %s)'):format(label), { win = actual_win }) local expected_lines = vim.split(expected_content, '\n', { plain = true, trimempty = true }) local actual_lines = vim.split(actual_content, '\n', { plain = true }) @@ -113,8 +121,8 @@ local function create_git_diff_layout(parent_win, expected_content, actual_conte vim.api.nvim_win_set_buf(diff_win, diff_buf) vim.api.nvim_set_option_value('filetype', 'cp', { buf = diff_buf }) - local label = MODE_LABELS.git - vim.api.nvim_set_option_value('winbar', ('diff [%s]'):format(label), { win = diff_win }) + local label = M.DIFF_MODES.git + vim.api.nvim_set_option_value('winbar', ('diff: %s'):format(label), { win = diff_win }) local diff_backend = require('cp.ui.diff') local backend = diff_backend.get_best_backend('git') @@ -166,12 +174,14 @@ end function M.create_diff_layout(mode, parent_win, expected_content, actual_content) if mode == 'single' then return create_single_layout(parent_win, actual_content) - elseif mode == 'none' then - return create_none_diff_layout(parent_win, expected_content, actual_content) + elseif mode == 'side-by-side' then + return create_side_by_side_layout(parent_win, expected_content, actual_content) elseif mode == 'git' then return create_git_diff_layout(parent_win, expected_content, actual_content) - else + elseif mode == 'vim' then return create_vim_diff_layout(parent_win, expected_content, actual_content) + else + return create_side_by_side_layout(parent_win, expected_content, actual_content) end end @@ -204,12 +214,13 @@ function M.update_diff_panes( actual_content = actual_content end - local desired_mode = is_compilation_failure and 'single' or config.ui.panel.diff_mode + local default_mode = config.ui.panel.diff_modes[1] + local desired_mode = is_compilation_failure and 'single' or (current_mode or default_mode) local highlight = require('cp.ui.highlight') local diff_namespace = highlight.create_namespace() local ansi_namespace = vim.api.nvim_create_namespace('cp_ansi_highlights') - if current_diff_layout and current_mode ~= desired_mode then + if current_diff_layout and current_diff_layout.mode ~= desired_mode then local saved_pos = vim.api.nvim_win_get_cursor(0) current_diff_layout.cleanup() current_diff_layout = nil @@ -264,7 +275,7 @@ function M.update_diff_panes( ansi_namespace ) end - elseif desired_mode == 'none' then + elseif desired_mode == 'side-by-side' then local expected_lines = vim.split(expected_content, '\n', { plain = true, trimempty = true }) local actual_lines = vim.split(actual_content, '\n', { plain = true }) utils.update_buffer_content(current_diff_layout.buffers[1], expected_lines, {}) diff --git a/lua/cp/ui/views.lua b/lua/cp/ui/views.lua index 4e06bd3..4b03b68 100644 --- a/lua/cp/ui/views.lua +++ b/lua/cp/ui/views.lua @@ -900,15 +900,15 @@ function M.toggle_panel(panel_opts) M.toggle_panel() end, { buffer = buf, silent = true }) vim.keymap.set('n', 't', function() - local modes = { 'none', 'git', 'vim' } + local modes = config.ui.panel.diff_modes local current_idx = 1 for i, mode in ipairs(modes) do - if config.ui.panel.diff_mode == mode then + if current_mode == mode then current_idx = i break end end - config.ui.panel.diff_mode = modes[(current_idx % #modes) + 1] + current_mode = modes[(current_idx % #modes) + 1] refresh_panel() end, { buffer = buf, silent = true }) vim.keymap.set('n', '', function()