diff --git a/lua/gitsigns/hunks.lua b/lua/gitsigns/hunks.lua index 096785fbf..3935e5fe9 100644 --- a/lua/gitsigns/hunks.lua +++ b/lua/gitsigns/hunks.lua @@ -225,6 +225,88 @@ function M.calc_signs(hunk, next, min_lnum, max_lnum, untracked) return signs end +--- Calculate signs needed to be applied from a hunk for a specified line range. +--- @param hunk Gitsigns.Hunk.Hunk +--- @param next_hunk Gitsigns.Hunk.Hunk? +--- @param untracked boolean +--- @return Gitsigns.Sign[] +function M.calc_sign_ranges(hunk, next_hunk, untracked) + assert( + not untracked or hunk.type == 'add', + string.format('Invalid hunk with untracked=%s hunk="%s"', untracked, hunk.head) + ) + + local start = hunk.added.start + local added = hunk.added.count + local removed = hunk.removed.count + local end_lnum = change_end(hunk) + + if hunk.type == 'delete' and start == 0 then + -- topdelete signs get placed one row lower + return { { + type = 'topdelete', + count = removed, + lnum = 1, + } } + elseif hunk.type == 'delete' then + return { + { + type = 'delete', + count = removed, + lnum = start, + end_lnum = start, + }, + } + elseif untracked or hunk.type == 'add' then + return { + { + type = untracked and 'untracked' or 'add', + count = added, + lnum = start, + end_lnum = end_lnum, + }, + } + end + + --- @type Gitsigns.Sign[] + local signs = {} + + -- changedelete + if removed > added then + if end_lnum > start then + signs[#signs + 1] = { + type = 'change', + lnum = start, + end_lnum = end_lnum - 1, + } + end + + signs[#signs + 1] = { + type = 'changedelete', + count = removed, + lnum = end_lnum, + } + else -- change + signs[#signs + 1] = { + type = 'change', + lnum = start, + end_lnum = end_lnum, + } + + -- Added lines of a 'change' hunk + if added > removed then + signs[#signs + 1] = { + type = 'add', + count = added - removed, + lnum = end_lnum, + end_lnum = hunk.vend, + } + end + end + + return signs +end + --- @param relpath string --- @param hunks Gitsigns.Hunk.Hunk[] --- @param mode_bits string diff --git a/lua/gitsigns/manager.lua b/lua/gitsigns/manager.lua index d9b4adebb..24ae99952 100644 --- a/lua/gitsigns/manager.lua +++ b/lua/gitsigns/manager.lua @@ -42,22 +42,16 @@ local function apply_win_signs0(bufnr, signs, hunks, top, bot, clear, untracked) for i, hunk in ipairs(hunks or {}) do --- @type Gitsigns.Hunk.Hunk? - local next = hunks[i + 1] + local next_hunk = hunks[i + 1] -- To stop the sign column width changing too much, if there are signs to be -- added but none of them are visible in the window, then make sure to add at -- least one sign. Only do this on the first call after an update when we all -- the signs have been cleared. - if clear and i == 1 then - signs:add( - bufnr, - gs_hunks.calc_signs(hunk, next, hunk.added.start, hunk.added.start, untracked) - ) + if (clear and i == 1) or (top <= hunk.vend and bot >= hunk.added.start) then + signs:add(bufnr, gs_hunks.calc_sign_ranges(hunk, next_hunk, untracked)) end - if top <= hunk.vend and bot >= hunk.added.start then - signs:add(bufnr, gs_hunks.calc_signs(hunk, next, top, bot, untracked)) - end if hunk.added.start > bot then break end diff --git a/lua/gitsigns/signs.lua b/lua/gitsigns/signs.lua index 9a7bb844e..08a658319 100644 --- a/lua/gitsigns/signs.lua +++ b/lua/gitsigns/signs.lua @@ -6,6 +6,7 @@ local dprint = require('gitsigns.debug.log').dprint --- @field type Gitsigns.SignType --- @field count? integer --- @field lnum integer +--- @field end_lnum? integer --- @class Gitsigns.Signs --- @field hls table diff --git a/lua/gitsigns/signs/extmarks.lua b/lua/gitsigns/signs/extmarks.lua index f029c616c..e4124880c 100644 --- a/lua/gitsigns/signs/extmarks.lua +++ b/lua/gitsigns/signs/extmarks.lua @@ -7,6 +7,8 @@ local M = {} local group_base = 'gitsigns_extmark_signs_' +local use_invalidate_signs = vim.fn.has('nvim-0.10') > 0 + --- @param cfg Gitsigns.SignConfig --- @param hls table --- @param name string @@ -24,6 +26,9 @@ end --- @param last_orig? integer --- @param last_new? integer function M:on_lines(buf, _, last_orig, last_new) + if use_invalidate_signs then + return + end -- Remove extmarks on line deletions to mimic -- the behaviour of vim signs. if last_orig > last_new then @@ -65,11 +70,15 @@ function M:add(bufnr, signs) local ok, err = pcall(api.nvim_buf_set_extmark, bufnr, self.ns, s.lnum - 1, -1, { id = s.lnum, + end_row = s.end_lnum and s.end_lnum - 1 or nil, sign_text = config.signcolumn and text or '', priority = config.sign_priority, sign_hl_group = hls.hl, number_hl_group = config.numhl and hls.numhl or nil, line_hl_group = config.linehl and hls.linehl or nil, + + undo_restore = not use_invalidate_signs, + invalidate = use_invalidate_signs, }) if not ok and config.debug_mode then