fix(commands): add diff buffer UX improvements
Problem: diffs:// buffers could trigger spurious LSP diagnostics, opening multiple diffs from fugitive created redundant splits, and there was no quick way to close diff windows. Solution: disable diagnostics on diff buffers, reuse existing diffs:// windows in the tabpage instead of creating new splits, and add a buffer-local q keymap to close diff windows.
This commit is contained in:
parent
52013d007d
commit
c72efec77d
2 changed files with 183 additions and 6 deletions
|
|
@ -3,6 +3,27 @@ local M = {}
|
||||||
local git = require('diffs.git')
|
local git = require('diffs.git')
|
||||||
local dbg = require('diffs.log').dbg
|
local dbg = require('diffs.log').dbg
|
||||||
|
|
||||||
|
---@return integer?
|
||||||
|
function M.find_diffs_window()
|
||||||
|
local tabpage = vim.api.nvim_get_current_tabpage()
|
||||||
|
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tabpage)) do
|
||||||
|
if vim.api.nvim_win_is_valid(win) then
|
||||||
|
local buf = vim.api.nvim_win_get_buf(win)
|
||||||
|
local name = vim.api.nvim_buf_get_name(buf)
|
||||||
|
if name:match('^diffs://') then
|
||||||
|
return win
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param bufnr integer
|
||||||
|
function M.setup_diff_buf(bufnr)
|
||||||
|
vim.diagnostic.enable(false, { bufnr = bufnr })
|
||||||
|
vim.keymap.set('n', 'q', '<cmd>close<CR>', { buffer = bufnr })
|
||||||
|
end
|
||||||
|
|
||||||
---@param diff_lines string[]
|
---@param diff_lines string[]
|
||||||
---@param hunk_position { hunk_header: string, offset: integer }
|
---@param hunk_position { hunk_header: string, offset: integer }
|
||||||
---@return integer?
|
---@return integer?
|
||||||
|
|
@ -96,9 +117,16 @@ function M.gdiff(revision, vertical)
|
||||||
vim.api.nvim_buf_set_var(diff_buf, 'diffs_repo_root', repo_root)
|
vim.api.nvim_buf_set_var(diff_buf, 'diffs_repo_root', repo_root)
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.cmd(vertical and 'vsplit' or 'split')
|
local existing_win = M.find_diffs_window()
|
||||||
vim.api.nvim_win_set_buf(0, diff_buf)
|
if existing_win then
|
||||||
|
vim.api.nvim_set_current_win(existing_win)
|
||||||
|
vim.api.nvim_win_set_buf(existing_win, diff_buf)
|
||||||
|
else
|
||||||
|
vim.cmd(vertical and 'vsplit' or 'split')
|
||||||
|
vim.api.nvim_win_set_buf(0, diff_buf)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.setup_diff_buf(diff_buf)
|
||||||
dbg('opened diff buffer %d for %s against %s', diff_buf, rel_path, revision)
|
dbg('opened diff buffer %d for %s against %s', diff_buf, rel_path, revision)
|
||||||
|
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
|
|
@ -190,8 +218,14 @@ function M.gdiff_file(filepath, opts)
|
||||||
vim.api.nvim_buf_set_var(diff_buf, 'diffs_old_filepath', old_rel_path)
|
vim.api.nvim_buf_set_var(diff_buf, 'diffs_old_filepath', old_rel_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.cmd(opts.vertical and 'vsplit' or 'split')
|
local existing_win = M.find_diffs_window()
|
||||||
vim.api.nvim_win_set_buf(0, diff_buf)
|
if existing_win then
|
||||||
|
vim.api.nvim_set_current_win(existing_win)
|
||||||
|
vim.api.nvim_win_set_buf(existing_win, diff_buf)
|
||||||
|
else
|
||||||
|
vim.cmd(opts.vertical and 'vsplit' or 'split')
|
||||||
|
vim.api.nvim_win_set_buf(0, diff_buf)
|
||||||
|
end
|
||||||
|
|
||||||
if opts.hunk_position then
|
if opts.hunk_position then
|
||||||
local target_line = M.find_hunk_line(diff_lines, opts.hunk_position)
|
local target_line = M.find_hunk_line(diff_lines, opts.hunk_position)
|
||||||
|
|
@ -201,6 +235,7 @@ function M.gdiff_file(filepath, opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
M.setup_diff_buf(diff_buf)
|
||||||
dbg('opened diff buffer %d for %s (%s)', diff_buf, rel_path, diff_label)
|
dbg('opened diff buffer %d for %s (%s)', diff_buf, rel_path, diff_label)
|
||||||
|
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
|
|
@ -244,9 +279,16 @@ function M.gdiff_section(repo_root, opts)
|
||||||
vim.api.nvim_buf_set_name(diff_buf, 'diffs://' .. diff_label .. ':all')
|
vim.api.nvim_buf_set_name(diff_buf, 'diffs://' .. diff_label .. ':all')
|
||||||
vim.api.nvim_buf_set_var(diff_buf, 'diffs_repo_root', repo_root)
|
vim.api.nvim_buf_set_var(diff_buf, 'diffs_repo_root', repo_root)
|
||||||
|
|
||||||
vim.cmd(opts.vertical and 'vsplit' or 'split')
|
local existing_win = M.find_diffs_window()
|
||||||
vim.api.nvim_win_set_buf(0, diff_buf)
|
if existing_win then
|
||||||
|
vim.api.nvim_set_current_win(existing_win)
|
||||||
|
vim.api.nvim_win_set_buf(existing_win, diff_buf)
|
||||||
|
else
|
||||||
|
vim.cmd(opts.vertical and 'vsplit' or 'split')
|
||||||
|
vim.api.nvim_win_set_buf(0, diff_buf)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.setup_diff_buf(diff_buf)
|
||||||
dbg('opened section diff buffer %d (%s)', diff_buf, diff_label)
|
dbg('opened section diff buffer %d (%s)', diff_buf, diff_label)
|
||||||
|
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
|
|
|
||||||
135
spec/ux_spec.lua
Normal file
135
spec/ux_spec.lua
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
local commands = require('diffs.commands')
|
||||||
|
local helpers = require('spec.helpers')
|
||||||
|
|
||||||
|
local counter = 0
|
||||||
|
|
||||||
|
local function create_diffs_buffer(name)
|
||||||
|
counter = counter + 1
|
||||||
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {
|
||||||
|
'diff --git a/file.lua b/file.lua',
|
||||||
|
'--- a/file.lua',
|
||||||
|
'+++ b/file.lua',
|
||||||
|
'@@ -1,1 +1,2 @@',
|
||||||
|
' local x = 1',
|
||||||
|
'+local y = 2',
|
||||||
|
})
|
||||||
|
vim.api.nvim_set_option_value('buftype', 'nowrite', { buf = bufnr })
|
||||||
|
vim.api.nvim_set_option_value('bufhidden', 'wipe', { buf = bufnr })
|
||||||
|
vim.api.nvim_set_option_value('swapfile', false, { buf = bufnr })
|
||||||
|
vim.api.nvim_set_option_value('modifiable', false, { buf = bufnr })
|
||||||
|
vim.api.nvim_set_option_value('filetype', 'diff', { buf = bufnr })
|
||||||
|
vim.api.nvim_buf_set_name(bufnr, name or ('diffs://unstaged:file_' .. counter .. '.lua'))
|
||||||
|
return bufnr
|
||||||
|
end
|
||||||
|
|
||||||
|
describe('ux', function()
|
||||||
|
describe('diagnostics', function()
|
||||||
|
it('disables diagnostics on diff buffers', function()
|
||||||
|
local bufnr = create_diffs_buffer()
|
||||||
|
commands.setup_diff_buf(bufnr)
|
||||||
|
|
||||||
|
assert.is_false(vim.diagnostic.is_enabled({ bufnr = bufnr }))
|
||||||
|
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('does not affect other buffers', function()
|
||||||
|
local diff_buf = create_diffs_buffer()
|
||||||
|
local normal_buf = helpers.create_buffer({ 'hello' })
|
||||||
|
|
||||||
|
commands.setup_diff_buf(diff_buf)
|
||||||
|
|
||||||
|
assert.is_true(vim.diagnostic.is_enabled({ bufnr = normal_buf }))
|
||||||
|
vim.api.nvim_buf_delete(diff_buf, { force = true })
|
||||||
|
helpers.delete_buffer(normal_buf)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('q keymap', function()
|
||||||
|
it('sets q keymap on diff buffer', function()
|
||||||
|
local bufnr = create_diffs_buffer()
|
||||||
|
commands.setup_diff_buf(bufnr)
|
||||||
|
|
||||||
|
local keymaps = vim.api.nvim_buf_get_keymap(bufnr, 'n')
|
||||||
|
local has_q = false
|
||||||
|
for _, km in ipairs(keymaps) do
|
||||||
|
if km.lhs == 'q' then
|
||||||
|
has_q = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert.is_true(has_q)
|
||||||
|
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('q closes the window', function()
|
||||||
|
local bufnr = create_diffs_buffer()
|
||||||
|
commands.setup_diff_buf(bufnr)
|
||||||
|
|
||||||
|
vim.cmd('split')
|
||||||
|
local win = vim.api.nvim_get_current_win()
|
||||||
|
vim.api.nvim_win_set_buf(win, bufnr)
|
||||||
|
|
||||||
|
local win_count_before = #vim.api.nvim_tabpage_list_wins(0)
|
||||||
|
|
||||||
|
vim.api.nvim_buf_call(bufnr, function()
|
||||||
|
vim.cmd('normal q')
|
||||||
|
end)
|
||||||
|
|
||||||
|
local win_count_after = #vim.api.nvim_tabpage_list_wins(0)
|
||||||
|
assert.equals(win_count_before - 1, win_count_after)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('window reuse', function()
|
||||||
|
it('returns nil when no diffs window exists', function()
|
||||||
|
local win = commands.find_diffs_window()
|
||||||
|
assert.is_nil(win)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('finds existing diffs:// window', function()
|
||||||
|
local bufnr = create_diffs_buffer()
|
||||||
|
vim.cmd('split')
|
||||||
|
local expected_win = vim.api.nvim_get_current_win()
|
||||||
|
vim.api.nvim_win_set_buf(expected_win, bufnr)
|
||||||
|
|
||||||
|
local found = commands.find_diffs_window()
|
||||||
|
assert.equals(expected_win, found)
|
||||||
|
|
||||||
|
vim.api.nvim_win_close(expected_win, true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('ignores non-diffs buffers', function()
|
||||||
|
local normal_buf = helpers.create_buffer({ 'hello' })
|
||||||
|
vim.cmd('split')
|
||||||
|
local win = vim.api.nvim_get_current_win()
|
||||||
|
vim.api.nvim_win_set_buf(win, normal_buf)
|
||||||
|
|
||||||
|
local found = commands.find_diffs_window()
|
||||||
|
assert.is_nil(found)
|
||||||
|
|
||||||
|
vim.api.nvim_win_close(win, true)
|
||||||
|
helpers.delete_buffer(normal_buf)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('returns first diffs window when multiple exist', function()
|
||||||
|
local buf1 = create_diffs_buffer()
|
||||||
|
local buf2 = create_diffs_buffer()
|
||||||
|
|
||||||
|
vim.cmd('split')
|
||||||
|
local win1 = vim.api.nvim_get_current_win()
|
||||||
|
vim.api.nvim_win_set_buf(win1, buf1)
|
||||||
|
|
||||||
|
vim.cmd('split')
|
||||||
|
local win2 = vim.api.nvim_get_current_win()
|
||||||
|
vim.api.nvim_win_set_buf(win2, buf2)
|
||||||
|
|
||||||
|
local found = commands.find_diffs_window()
|
||||||
|
assert.is_not_nil(found)
|
||||||
|
assert.is_true(found == win1 or found == win2)
|
||||||
|
|
||||||
|
vim.api.nvim_win_close(win1, true)
|
||||||
|
vim.api.nvim_win_close(win2, true)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue