feat(diff): third, regular diff mode

This commit is contained in:
Barrett Ruth 2025-09-21 17:18:22 -04:00
parent 7d51fc2931
commit ff20efca71
6 changed files with 126 additions and 12 deletions

View file

@ -211,12 +211,12 @@ Here's an example configuration with lazy.nvim: >lua
ANSI escape codes are stripped for plain text display.
Requires vim.g.terminal_color_* to be configured for
proper color display.
{diff_mode} (string, default: "vim") Diff backend: "vim" or "git".
Git provides character-level precision, vim uses
built-in diff.
{diff_mode} (string, default: "git") Diff backend: "none", "vim", or "git".
"none" displays plain buffers without highlighting,
"vim" uses built-in diff, "git" provides character-level precision.
{next_test_key} (string, default: "<c-n>") Key to navigate to next test case.
{prev_test_key} (string, default: "<c-p>") Key to navigate to previous test case.
{toggle_diff_key} (string, default: "t") Key to toggle diff mode.
{toggle_diff_key} (string, default: "t") Key to cycle through diff modes.
{max_output_lines} (number, default: 50) Maximum lines of test output.
*cp.DiffConfig*
@ -545,7 +545,7 @@ RUN PANEL KEYMAPS *cp-test-keys*
run_panel.next_test_key)
<c-p> Navigate to previous test case (configurable via
run_panel.prev_test_key)
t Toggle diff mode between vim and git (configurable
t Cycle through diff modes: none → vim → git (configurable
via run_panel.toggle_diff_key)
q Exit test panel and restore layout

View file

@ -31,10 +31,10 @@
---@class RunPanelConfig
---@field ansi boolean Enable ANSI color parsing and highlighting
---@field diff_mode "vim"|"git" Diff backend to use
---@field diff_mode "none"|"vim"|"git" Diff backend to use
---@field next_test_key string Key to navigate to next test case
---@field prev_test_key string Key to navigate to previous test case
---@field toggle_diff_key string Key to toggle diff mode
---@field toggle_diff_key string Key to cycle through diff modes
---@field max_output_lines number Maximum lines of test output to display
---@class DiffGitConfig
@ -205,9 +205,9 @@ function M.setup(user_config)
diff_mode = {
config.run_panel.diff_mode,
function(value)
return vim.tbl_contains({ 'vim', 'git' }, value)
return vim.tbl_contains({ 'none', 'vim', 'git' }, value)
end,
"diff_mode must be 'vim' or 'git'",
"diff_mode must be 'none', 'vim', or 'git'",
},
next_test_key = {
config.run_panel.next_test_key,

View file

@ -288,6 +288,43 @@ local function toggle_run_panel(is_debug)
highlight.apply_highlights(bufnr, highlights, namespace or test_list_namespace)
end
local function create_none_diff_layout(parent_win, expected_content, actual_content)
local expected_buf = create_buffer_with_options()
local actual_buf = create_buffer_with_options()
vim.api.nvim_set_current_win(parent_win)
vim.cmd.split()
vim.cmd('resize ' .. math.floor(vim.o.lines * 0.35))
local actual_win = vim.api.nvim_get_current_win()
vim.api.nvim_win_set_buf(actual_win, actual_buf)
vim.cmd.vsplit()
local expected_win = vim.api.nvim_get_current_win()
vim.api.nvim_win_set_buf(expected_win, expected_buf)
vim.api.nvim_set_option_value('filetype', 'cptest', { buf = expected_buf })
vim.api.nvim_set_option_value('filetype', 'cptest', { 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 expected_lines = vim.split(expected_content, '\n', { plain = true, trimempty = true })
local actual_lines = vim.split(actual_content, '\n', { plain = true, trimempty = true })
update_buffer_content(expected_buf, expected_lines, {})
update_buffer_content(actual_buf, actual_lines, {})
return {
buffers = { expected_buf, actual_buf },
windows = { expected_win, actual_win },
cleanup = function()
pcall(vim.api.nvim_win_close, expected_win, true)
pcall(vim.api.nvim_win_close, actual_win, true)
pcall(vim.api.nvim_buf_delete, expected_buf, { force = true })
pcall(vim.api.nvim_buf_delete, actual_buf, { force = true })
end,
}
end
local function create_vim_diff_layout(parent_win, expected_content, actual_content)
local expected_buf = create_buffer_with_options()
local actual_buf = create_buffer_with_options()
@ -395,6 +432,8 @@ local function toggle_run_panel(is_debug)
local function 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 == 'git' then
return create_git_diff_layout(parent_win, expected_content, actual_content)
else
@ -481,6 +520,16 @@ local function toggle_run_panel(is_debug)
ansi_namespace
)
end
elseif desired_mode == 'none' then
local expected_lines = vim.split(expected_content, '\n', { plain = true, trimempty = true })
local actual_lines = vim.split(actual_content, '\n', { plain = true, trimempty = true })
update_buffer_content(current_diff_layout.buffers[1], expected_lines, {})
update_buffer_content(
current_diff_layout.buffers[2],
actual_lines,
actual_highlights,
ansi_namespace
)
else
local expected_lines = vim.split(expected_content, '\n', { plain = true, trimempty = true })
local actual_lines = vim.split(actual_content, '\n', { plain = true, trimempty = true })
@ -548,7 +597,16 @@ local function toggle_run_panel(is_debug)
toggle_run_panel()
end, { buffer = buf, silent = true })
vim.keymap.set('n', config.run_panel.toggle_diff_key, function()
config.run_panel.diff_mode = config.run_panel.diff_mode == 'vim' and 'git' or 'vim'
local modes = { 'none', 'vim', 'git' }
local current_idx = nil
for i, mode in ipairs(modes) do
if config.run_panel.diff_mode == mode then
current_idx = i
break
end
end
current_idx = current_idx or 1
config.run_panel.diff_mode = modes[(current_idx % #modes) + 1]
refresh_run_panel()
end, { buffer = buf, silent = true })
vim.keymap.set('n', config.run_panel.next_test_key, function()

View file

@ -22,6 +22,20 @@ local vim_backend = {
end,
}
---@type DiffBackend
local none_backend = {
name = 'none',
render = function(expected, actual)
local expected_lines = vim.split(expected, '\n', { plain = true, trimempty = true })
local actual_lines = vim.split(actual, '\n', { plain = true, trimempty = true })
return {
content = { expected = expected_lines, actual = actual_lines },
highlights = {},
}
end,
}
---@type DiffBackend
local git_backend = {
name = 'git',
@ -143,6 +157,7 @@ local git_backend = {
---@type table<string, DiffBackend>
local backends = {
none = none_backend,
vim = vim_backend,
git = git_backend,
}

View file

@ -70,6 +70,28 @@ describe('cp.config', function()
end)
end)
it('validates diff_mode values', function()
local valid_config = {
run_panel = {
diff_mode = 'none',
},
}
assert.has_no.errors(function()
config.setup(valid_config)
end)
local invalid_config = {
run_panel = {
diff_mode = 'invalid_mode',
},
}
assert.has_error(function()
config.setup(invalid_config)
end)
end)
it('validates hook functions', function()
local invalid_config = {
hooks = { before_run = 'not_a_function' },

View file

@ -12,10 +12,10 @@ describe('cp.diff', function()
end)
describe('get_available_backends', function()
it('returns vim and git backends', function()
it('returns none, vim and git backends', function()
local backends = diff.get_available_backends()
table.sort(backends)
assert.same({ 'git', 'vim' }, backends)
assert.same({ 'git', 'none', 'vim' }, backends)
end)
end)
@ -32,6 +32,12 @@ describe('cp.diff', function()
assert.equals('git', backend.name)
end)
it('returns none backend by name', function()
local backend = diff.get_backend('none')
assert.is_not_nil(backend)
assert.equals('none', backend.name)
end)
it('returns nil for invalid name', function()
local backend = diff.get_backend('invalid')
assert.is_nil(backend)
@ -95,6 +101,19 @@ describe('cp.diff', function()
end)
end)
describe('none backend', function()
it('returns both expected and actual content', function()
local backend = diff.get_backend('none')
local result = backend.render('expected\nline2', 'actual\nline2')
assert.same({
expected = { 'expected', 'line2' },
actual = { 'actual', 'line2' },
}, result.content)
assert.same({}, result.highlights)
end)
end)
describe('vim backend', function()
it('returns content as-is', function()
local backend = diff.get_backend('vim')