diff --git a/doc/cp.txt b/doc/cp.txt index f743f51..4e8f3ac 100644 --- a/doc/cp.txt +++ b/doc/cp.txt @@ -50,8 +50,8 @@ Setup Commands ~ Action Commands ~ :CP test [--debug] Toggle test panel for individual test case - debugging. Shows per-test results with three-pane - layout for easy Expected/Actual comparison. + debugging. Shows per-test results with redesigned + layout for efficient comparison. Use --debug flag to compile with debug flags Requires contest setup first. @@ -115,6 +115,21 @@ Optional configuration with lazy.nvim: > vim.diagnostic.enable(false) end, }, + test_panel = { + diff_mode = "vim", -- "vim" or "git" + toggle_key = "t", -- key to toggle test panel + status_format = "compact", -- "compact" or "verbose" + }, + diff = { + git = { + command = "git", + args = {"diff", "--no-index", "--word-diff=plain", + "--word-diff-regex=.", "--no-prefix"}, + }, + vim = { + enable_diffthis = true, + }, + }, snippets = { ... }, -- LuaSnip snippets filename = function(contest, contest_id, problem_id, config, language) ... end, } @@ -131,6 +146,8 @@ Optional configuration with lazy.nvim: > during operation. • {scrapers} (`table`) Per-platform scraper control. Default enables all platforms. + • {test_panel} (`TestPanelConfig`) Test panel behavior configuration. + • {diff} (`DiffConfig`) Diff backend configuration. • {filename}? (`function`) Custom filename generation function. `function(contest, contest_id, problem_id, config, language)` Should return full filename with extension. @@ -157,6 +174,20 @@ Optional configuration with lazy.nvim: > • {extension} (`string`) File extension (e.g. "cc", "py"). • {executable}? (`string`) Executable name for interpreted languages. +*cp.TestPanelConfig* + + Fields: ~ + • {diff_mode} (`string`, default: `"vim"`) Diff backend: "vim" or "git". + Git provides character-level precision, vim uses built-in diff. + • {toggle_key} (`string`, default: `"t"`) Key to toggle test panel. + • {status_format} (`string`, default: `"compact"`) Status display format. + +*cp.DiffConfig* + + Fields: ~ + • {git} (`DiffGitConfig`) Git diff backend configuration. + • {vim} (`DiffVimConfig`) Vim diff backend configuration. + *cp.Hooks* Fields: ~ @@ -256,8 +287,9 @@ Example: Quick setup for single Codeforces problem > TEST PANEL *cp-test* -The test panel provides individual test case debugging with a three-pane -layout showing test list, expected output, and actual output side-by-side. +The test panel provides individual test case debugging with a streamlined +layout optimized for modern screens. Shows test status with competitive +programming terminology and efficient space usage. Activation ~ *:CP-test* @@ -270,29 +302,46 @@ Activation ~ Interface ~ -The test panel uses a three-pane layout for easy comparison: > +The test panel uses a redesigned two-pane layout for efficient comparison: +(note that the diff is indeed highlighted, not the weird amalgamation of +characters below) > - ┌─────────────────────────────────────────────────────────────┐ - │ 1. [ok:true ] [code:0] [time:12ms] │ - │> 2. [ok:false] [code:0] [time:45ms] │ - │ │ - │ Input: │ - │ 5 3 │ - │ │ - └─────────────────────────────────────────────────────────────┘ - ┌─ Expected ──────────────────┐ ┌───── Actual ────────────────┐ - │ 8 │ │ 7 │ - │ │ │ │ - │ │ │ │ - │ │ │ │ - └─────────────────────────────┘ └─────────────────────────────┘ + ┌─ Tests ─────────────────────┐ ┌─ Expected vs Actual ───────────────────────┐ + │ AC 1. 12ms │ │ 45ms │ Exit: 0 │ + │ WA > 2. 45ms │ ├────────────────────────────────────────────┤ + │ 5 3 │ │ │ + │ │ │ 4[-2-]{+3+} │ + │ AC 3. 9ms │ │ 100 │ + │ RTE 4. 0ms │ │ hello w[-o-]r{+o+}ld │ + │ │ │ │ + └─────────────────────────────┘ └────────────────────────────────────────────┘ < +Status Indicators ~ + +Test cases use competitive programming terminology: + + AC Accepted (passed) + WA Wrong Answer (output mismatch) + TLE Time Limit Exceeded (timeout) + RTE Runtime Error (non-zero exit) + Keymaps ~ *cp-test-keys* Navigate to next test case Navigate to previous test case q Exit test panel (restore layout) +t Toggle test panel (configurable via test_panel.toggle_key) + +Diff Modes ~ + +Two diff backends are available: + + vim Built-in vim diff (default, always available) + git Character-level git word-diff (requires git, more precise) + +The git backend shows character-level changes with [-removed-] and {+added+} +markers for precise difference analysis. Execution Details ~ diff --git a/lua/cp/diff.lua b/lua/cp/diff.lua index 03928fa..97c1b76 100644 --- a/lua/cp/diff.lua +++ b/lua/cp/diff.lua @@ -48,42 +48,16 @@ local git_backend = { vim.fn.delete(tmp_actual) if result.code == 0 then - -- No differences, return actual content as-is return { content = vim.split(actual, '\n', { plain = true, trimempty = true }), highlights = {} } else - -- Parse git diff output - local lines = vim.split(result.stdout or '', '\n', { plain = true }) - local content_lines = {} - local highlights = {} - - -- Skip git diff header lines (start with @@, +++, ---, etc.) - local content_started = false - for _, line in ipairs(lines) do - if content_started or (not line:match('^@@') and not line:match('^%+%+%+') and not line:match('^%-%-%-') and not line:match('^index')) then - content_started = true - if line:match('^[^%-+]') or line:match('^%+') then - -- Skip lines starting with - (removed lines) for the actual pane - -- Only show lines that are unchanged or added - if not line:match('^%-') then - local clean_line = line:gsub('^%+', '') -- Remove + prefix - table.insert(content_lines, clean_line) - - -- Parse highlights will be handled in highlight.lua - table.insert(highlights, { - line = #content_lines, - content = clean_line - }) - end - end - end - end - + local highlight_module = require('cp.highlight') return { - content = content_lines, - highlights = highlights + content = {}, + highlights = {}, + raw_diff = result.stdout or '' } end end @@ -122,18 +96,12 @@ end function M.get_best_backend(preferred_backend) if preferred_backend and backends[preferred_backend] then if preferred_backend == 'git' and not M.is_git_available() then - -- Fall back to vim if git is not available return backends.vim end return backends[preferred_backend] end - -- Default to git if available, otherwise vim - if M.is_git_available() then - return backends.git - else - return backends.vim - end + return backends.vim end ---Render diff using specified backend diff --git a/lua/cp/init.lua b/lua/cp/init.lua index 607b8ad..c16f185 100644 --- a/lua/cp/init.lua +++ b/lua/cp/init.lua @@ -225,6 +225,9 @@ local function toggle_test_panel(is_debug) actual_buf = actual_buf, } + local highlight = require('cp.highlight') + local diff_namespace = highlight.create_namespace() + local function render_test_tabs() local test_render = require('cp.test_render') test_render.setup_highlights() @@ -250,6 +253,15 @@ local function toggle_test_panel(is_debug) local expected_lines = vim.split(expected_text, '\n', { plain = true, trimempty = true }) update_buffer_content(test_buffers.expected_buf, expected_lines) + + local diff_backend = require('cp.diff') + local backend = diff_backend.get_best_backend(config.test_panel.diff_mode) + + if backend.name == 'vim' and current_test.status == 'fail' then + vim.api.nvim_set_option_value('diff', true, { win = test_windows.expected_win }) + else + vim.api.nvim_set_option_value('diff', false, { win = test_windows.expected_win }) + end end local function update_actual_pane() @@ -270,24 +282,38 @@ local function toggle_test_panel(is_debug) actual_lines = { '(not run yet)' } end - update_buffer_content(test_buffers.actual_buf, actual_lines) - local test_render = require('cp.test_render') local status_bar_text = test_render.render_status_bar(current_test) if status_bar_text ~= '' then vim.api.nvim_set_option_value('winbar', status_bar_text, { win = test_windows.actual_win }) end - vim.api.nvim_set_option_value('diff', enable_diff, { win = test_windows.expected_win }) - vim.api.nvim_set_option_value('diff', enable_diff, { win = test_windows.actual_win }) - if enable_diff then - vim.api.nvim_win_call(test_windows.expected_win, function() - vim.cmd.diffthis() - end) - vim.api.nvim_win_call(test_windows.actual_win, function() - vim.cmd.diffthis() - end) + local diff_backend = require('cp.diff') + local backend = diff_backend.get_best_backend(config.test_panel.diff_mode) + + if backend.name == 'git' then + local diff_result = backend.render(current_test.expected, current_test.actual) + if diff_result.raw_diff and diff_result.raw_diff ~= '' then + highlight.parse_and_apply_diff(test_buffers.actual_buf, diff_result.raw_diff, diff_namespace) + else + update_buffer_content(test_buffers.actual_buf, actual_lines) + end + else + update_buffer_content(test_buffers.actual_buf, actual_lines) + vim.api.nvim_set_option_value('diff', true, { win = test_windows.expected_win }) + vim.api.nvim_set_option_value('diff', true, { win = test_windows.actual_win }) + vim.api.nvim_win_call(test_windows.expected_win, function() + vim.cmd.diffthis() + end) + vim.api.nvim_win_call(test_windows.actual_win, function() + vim.cmd.diffthis() + end) + end + else + update_buffer_content(test_buffers.actual_buf, actual_lines) + vim.api.nvim_set_option_value('diff', false, { win = test_windows.expected_win }) + vim.api.nvim_set_option_value('diff', false, { win = test_windows.actual_win }) end end