diff --git a/lua/git/hunks.lua b/lua/git/hunks.lua index 6869dba..6249261 100644 --- a/lua/git/hunks.lua +++ b/lua/git/hunks.lua @@ -446,12 +446,9 @@ function M.nav(direction) end ---@param h ow.Git.Hunks.Hunk ----@param state ow.Git.Hunks.BufState ----@return string -local function build_patch(h, state) +---@return string[] +local function hunk_body(h) local lines = { - "--- a/" .. state.rel, - "+++ b/" .. state.rel, string.format( "@@ -%d,%d +%d,%d @@", h.old_start, @@ -466,6 +463,15 @@ local function build_patch(h, state) for _, l in ipairs(h.new_lines) do table.insert(lines, "+" .. l) end + return lines +end + +---@param h ow.Git.Hunks.Hunk +---@param state ow.Git.Hunks.BufState +---@return string +local function build_patch(h, state) + local lines = { "--- a/" .. state.rel, "+++ b/" .. state.rel } + vim.list_extend(lines, hunk_body(h)) return table.concat(lines, "\n") .. "\n" end @@ -543,16 +549,22 @@ function M.select_hunk(buf) vim.api.nvim_win_set_cursor(0, { last, 0 }) end +local preview_win ---@type integer? + ---@param buf? integer function M.preview_hunk(buf) - local _, state, h = cursor_hunk(buf) + if preview_win and vim.api.nvim_win_is_valid(preview_win) then + vim.api.nvim_set_current_win(preview_win) + return + end + local target, state, h = cursor_hunk(buf) if not state then return end if not h then return end - local lines = util.split_lines(build_patch(h, state)) + local lines = hunk_body(h) local pbuf = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_lines(pbuf, 0, -1, false, lines) vim.bo[pbuf].filetype = "diff" @@ -573,19 +585,33 @@ function M.preview_hunk(buf) height = height, style = "minimal", }) - vim.api.nvim_create_autocmd({ "CursorMoved", "InsertEnter", "BufLeave" }, { - once = true, - callback = function() - if vim.api.nvim_win_is_valid(win) then - vim.api.nvim_win_close(win, true) - end - end, - }) - vim.keymap.set("n", "q", function() + preview_win = win + + local function close() if vim.api.nvim_win_is_valid(win) then vim.api.nvim_win_close(win, true) end - end, { buffer = pbuf, nowait = true }) + end + local group = + vim.api.nvim_create_augroup("ow.git.hunks.preview", { clear = true }) + vim.api.nvim_create_autocmd( + { "CursorMoved", "CursorMovedI", "InsertEnter" }, + { group = group, buffer = target, callback = close } + ) + vim.api.nvim_create_autocmd("WinLeave", { + group = group, + buffer = pbuf, + callback = close, + }) + vim.api.nvim_create_autocmd("WinClosed", { + group = group, + pattern = tostring(win), + callback = function() + preview_win = nil + pcall(vim.api.nvim_del_augroup_by_name, "ow.git.hunks.preview") + end, + }) + vim.keymap.set("n", "q", close, { buffer = pbuf, nowait = true }) end repo.on("change", function(r, change) diff --git a/test/git/hunks_test.lua b/test/git/hunks_test.lua index a43a45f..3bd631a 100644 --- a/test/git/hunks_test.lua +++ b/test/git/hunks_test.lua @@ -52,6 +52,15 @@ local function detailed_marks(buf, ns_name) return vim.api.nvim_buf_get_extmarks(buf, ns, 0, -1, { details = true }) end +---@return integer? +local function find_float() + for _, w in ipairs(vim.api.nvim_tabpage_list_wins(0)) do + if vim.api.nvim_win_get_config(w).relative ~= "" then + return w + end + end +end + t.test("pure add: hunk shape and add signs", function() local _, buf, state = setup("a\nd\n", "a\nb\nc\nd\n") t.eq(#state.hunks, 1, "one hunk for a pure addition") @@ -296,6 +305,52 @@ t.test("git_hunk_signs falls back to the default for unset kinds", function() }) end) +t.test("preview_hunk shows the hunk body without file headers", function() + local _, buf = setup("a\nb\nc\n", "a\nB\nc\n") + vim.api.nvim_set_current_buf(buf) + vim.api.nvim_win_set_cursor(0, { 2, 0 }) + hunks.preview_hunk(buf) + local float = assert(find_float(), "preview float should open") + t.defer(function() + pcall(vim.api.nvim_win_close, float, true) + end) + local lines = vim.api.nvim_buf_get_lines( + vim.api.nvim_win_get_buf(float), + 0, + -1, + false + ) + t.truthy( + vim.startswith(lines[1] or "", "@@ "), + "first line is the @@ header" + ) + for _, l in ipairs(lines) do + t.falsy(vim.startswith(l, "--- "), "no --- file header line") + t.falsy(vim.startswith(l, "+++ "), "no +++ file header line") + end +end) + +t.test("preview_hunk re-invocation focuses the open float", function() + local _, buf = setup("a\nb\nc\n", "a\nB\nc\n") + vim.api.nvim_set_current_buf(buf) + vim.api.nvim_win_set_cursor(0, { 2, 0 }) + hunks.preview_hunk(buf) + local float = assert(find_float(), "preview float should open") + t.defer(function() + pcall(vim.api.nvim_win_close, float, true) + end) + t.truthy( + vim.api.nvim_get_current_win() ~= float, + "the float opens unfocused" + ) + hunks.preview_hunk(buf) + t.eq( + vim.api.nvim_get_current_win(), + float, + "re-invoking focuses the existing float" + ) +end) + t.test("nav jumps to next and previous hunks with wrap", function() local _, buf = setup("a\nb\nc\nd\ne\n", "A\nb\nc\nd\nE\n") vim.api.nvim_set_current_buf(buf)