refactor(highlights)!: rework highlights

- Added GitSigns*Inline and GitSigns*LnInline to be used with word diff
    - GitSigns*LnInline is used for word diff with config.word_diff
    - GitSigns*Inline is used for word diff in hunk previews

- Added GitSigns*VirtLn and GitSigns*VirtLnInline to be used with
  `config.show_deleted`.

- Define GitSigns highlights conditionally as opposed to only defining
  them if certain features are enabled.
This commit is contained in:
Lewis Russell 2021-10-28 15:09:53 +01:00 committed by Lewis Russell
parent 4861666b58
commit b4548e4c60
14 changed files with 222 additions and 237 deletions

View File

@ -415,18 +415,16 @@ signs *gitsigns-config-signs*
• `show_count` to enable showing count of hunk, e.g. number of deleted • `show_count` to enable showing count of hunk, e.g. number of deleted
lines. lines.
Note if `hl`, `numhl` or `linehl` use a `GitSigns*` highlight and it is Note if a highlight is not defined, it will be automatically derived by
not defined, it will be automatically derived by searching for other searching for other defined highlights in the following order:
defined highlights in the following order:
• `GitGutter*` • `GitGutter*`
• `Signify*` • `Signify*`
• `Diff*Gutter` • `Diff*Gutter`
• `diff*` • `diff*`
• `Diff*` • `Diff*`
For example if `signs.add.hl = GitSignsAdd` and `GitSignsAdd` is not For example if `GitSignsAdd` is not defined but `GitGutterAdd` is defined,
defined but `GitGutterAdd` is defined, then `GitSignsAdd` will be linked then `GitSignsAdd` will be linked to `GitGutterAdd`.
to `GitGutterAdd`.
keymaps *gitsigns-config-keymaps* keymaps *gitsigns-config-keymaps*
DEPRECATED DEPRECATED
@ -744,9 +742,18 @@ word_diff *gitsigns-config-word_diff*
Requires `config.diff_opts.internal = true` . Requires `config.diff_opts.internal = true` .
Uses the highlights: Uses the highlights:
• GitSignsAddLn • For word diff in previews:
• GitSignsChangeLn • `GitSignsAddInline`
• GitSignsDeleteLn • `GitSignsChangeInline`
• `GitSignsDeleteInline`
• For word diff in buffer:
• `GitSignsAddLnInline`
• `GitSignsChangeLnInline`
• `GitSignsDeleteLnInline`
• For word diff in virtual lines (e.g. show_deleted):
• `GitSignsAddVirtLnInline`
• `GitSignsChangeVirtLnInline`
• `GitSignsDeleteVirtLnInline`
debug_mode *gitsigns-config-debug_mode* debug_mode *gitsigns-config-debug_mode*
Type: `boolean`, Default: `false` Type: `boolean`, Default: `false`

View File

@ -21,6 +21,7 @@ local Hunk_Public = gs_hunks.Hunk_Public
local api = vim.api local api = vim.api
local current_buf = api.nvim_get_current_buf local current_buf = api.nvim_get_current_buf
local add_highlight = api.nvim_buf_add_highlight
local NavHunkOpts = {} local NavHunkOpts = {}
@ -437,17 +438,21 @@ end
local function highlight_hunk_lines(bufnr, offset, hunk) local function highlight_hunk_lines(bufnr, offset, hunk)
for i = 1, #hunk.removed.lines do for i = 1, #hunk.removed.lines do
api.nvim_buf_add_highlight(bufnr, -1, 'DiffRemoved', offset + i - 1, 0, -1) add_highlight(bufnr, -1, 'GitSignsDeleteLn', offset + i - 1, 0, -1)
end end
for i = 1, #hunk.added.lines do for i = 1, #hunk.added.lines do
api.nvim_buf_add_highlight(bufnr, -1, 'DiffAdded', #hunk.removed.lines + offset + i - 1, 0, -1) add_highlight(bufnr, -1, 'GitSignsAddLn', #hunk.removed.lines + offset + i - 1, 0, -1)
end end
if config.diff_opts.internal then if config.diff_opts.internal then
local regions = require('gitsigns.diff_int').run_word_diff(hunk.removed.lines, hunk.added.lines) local removed_regions, added_regions = require('gitsigns.diff_int').run_word_diff(hunk.removed.lines, hunk.added.lines)
for _, region in ipairs(regions) do for _, region in ipairs(removed_regions) do
local line, scol, ecol = region[1], region[3], region[4] local line, scol, ecol = region[1], region[3], region[4]
api.nvim_buf_add_highlight(bufnr, -1, 'TermCursor', line + offset - 1, scol, ecol) add_highlight(bufnr, -1, 'GitSignsDeleteInline', line + offset - 1, scol, ecol)
end
for _, region in ipairs(added_regions) do
local line, scol, ecol = region[1], region[3], region[4]
add_highlight(bufnr, -1, 'GitSignsAddInline', line + offset - 1, scol, ecol)
end end
end end
end end
@ -499,7 +504,7 @@ M.preview_hunk = noautocmd(function()
local _, bufnr = popup.create(lines, config.preview_config) local _, bufnr = popup.create(lines, config.preview_config)
api.nvim_buf_add_highlight(bufnr, -1, 'Title', 0, 0, -1) add_highlight(bufnr, -1, 'Title', 0, 0, -1)
api.nvim_buf_set_var(cbuf, '_gitsigns_preview_open', true) api.nvim_buf_set_var(cbuf, '_gitsigns_preview_open', true)
vim.cmd([[autocmd CursorMoved,CursorMovedI <buffer> ++once silent! unlet b:_gitsigns_preview_open]]) vim.cmd([[autocmd CursorMoved,CursorMovedI <buffer> ++once silent! unlet b:_gitsigns_preview_open]])
@ -637,7 +642,7 @@ M.blame_line = void(function(opts)
local highlights = {} local highlights = {}
local function add_highlight(hlgroup, start, length) local function add_hl(hlgroup, start, length)
highlights[#highlights + 1] = { hlgroup, #lines - 1, start or 0, length or -1 } highlights[#highlights + 1] = { hlgroup, #lines - 1, start or 0, length or -1 }
end end
@ -660,9 +665,9 @@ M.blame_line = void(function(opts)
local p2 = #result.author local p2 = #result.author
local p3 = #date local p3 = #date
add_highlight('Directory', 0, p1) add_hl('Directory', 0, p1)
add_highlight('MoreMsg', p1 + 1, p2) add_hl('MoreMsg', p1 + 1, p2)
add_highlight('Label', p1 + p2 + 2, p3 + 2) add_hl('Label', p1 + p2 + 2, p3 + 2)
vim.list_extend(lines, commit_message) vim.list_extend(lines, commit_message)
@ -671,7 +676,7 @@ M.blame_line = void(function(opts)
end end
else else
lines[#lines + 1] = result.author lines[#lines + 1] = result.author
add_highlight('ErrorMsg') add_hl('ErrorMsg')
if full then if full then
scheduler() scheduler()
hunk, ihunk = get_cursor_hunk(bufnr, bcache.hunks) hunk, ihunk = get_cursor_hunk(bufnr, bcache.hunks)
@ -682,7 +687,7 @@ M.blame_line = void(function(opts)
if hunk then if hunk then
lines[#lines + 1] = '' lines[#lines + 1] = ''
lines[#lines + 1] = ('Hunk %d of %d'):format(ihunk, nhunk) lines[#lines + 1] = ('Hunk %d of %d'):format(ihunk, nhunk)
add_highlight('Title') add_hl('Title')
vim.list_extend(lines, gs_hunks.patch_lines(hunk)) vim.list_extend(lines, gs_hunks.patch_lines(hunk))
end end
@ -691,7 +696,7 @@ M.blame_line = void(function(opts)
for _, h in ipairs(highlights) do for _, h in ipairs(highlights) do
local hlgroup, line, start, length = h[1], h[2], h[3], h[4] local hlgroup, line, start, length = h[1], h[2], h[3], h[4]
api.nvim_buf_add_highlight(pbufnr, -1, hlgroup, line, start, start + length) add_highlight(pbufnr, -1, hlgroup, line, start, start + length)
end end
if hunk then if hunk then

View File

@ -131,18 +131,16 @@ M.schema = {
`show_count` to enable showing count of hunk, e.g. number of deleted `show_count` to enable showing count of hunk, e.g. number of deleted
lines. lines.
Note if `hl`, `numhl` or `linehl` use a `GitSigns*` highlight and it is Note if a highlight is not defined, it will be automatically derived by
not defined, it will be automatically derived by searching for other searching for other defined highlights in the following order:
defined highlights in the following order:
`GitGutter*` `GitGutter*`
`Signify*` `Signify*`
`Diff*Gutter` `Diff*Gutter`
`diff*` `diff*`
`Diff*` `Diff*`
For example if `signs.add.hl = GitSignsAdd` and `GitSignsAdd` is not For example if `GitSignsAdd` is not defined but `GitGutterAdd` is defined,
defined but `GitGutterAdd` is defined, then `GitSignsAdd` will be linked then `GitSignsAdd` will be linked to `GitGutterAdd`.
to `GitGutterAdd`.
]], ]],
}, },
@ -591,9 +589,18 @@ M.schema = {
Requires `config.diff_opts.internal = true` . Requires `config.diff_opts.internal = true` .
Uses the highlights: Uses the highlights:
GitSignsAddLn For word diff in previews:
GitSignsChangeLn `GitSignsAddInline`
GitSignsDeleteLn `GitSignsChangeInline`
`GitSignsDeleteInline`
For word diff in buffer:
`GitSignsAddLnInline`
`GitSignsChangeLnInline`
`GitSignsDeleteLnInline`
For word diff in virtual lines (e.g. show_deleted):
`GitSignsAddVirtLnInline`
`GitSignsChangeVirtLnInline`
`GitSignsDeleteVirtLnInline`
]], ]],
}, },

View File

@ -50,11 +50,12 @@ local Region = {}
local gaps_between_regions = 5 local gaps_between_regions = 5
function M.run_word_diff(removed, added) function M.run_word_diff(removed, added)
if #removed ~= #added then local adds = {}
return {} local rems = {}
end
local ret = {} if #removed ~= #added then
return rems, adds
end
for i = 1, #removed do for i = 1, #removed do
@ -93,14 +94,11 @@ function M.run_word_diff(removed, added)
end end
for _, h in ipairs(hunks) do for _, h in ipairs(hunks) do
local rem = { i, h.type, h.removed.start, h.removed.start + h.removed.count } adds[#adds + 1] = { i + #removed, h.type, h.added.start, h.added.start + h.added.count }
local add = { i + #removed, h.type, h.added.start, h.added.start + h.added.count } rems[#rems + 1] = { i, h.type, h.removed.start, h.removed.start + h.removed.count }
ret[#ret + 1] = rem
ret[#ret + 1] = add
end end
end end
return ret return rems, adds
end end
return M return M

View File

@ -7,71 +7,65 @@ local M = {}
local GitSignHl = {}
local hls = { local hls = {
GitSignsAdd = { 'GitGutterAdd', 'SignifySignAdd', 'DiffAddedGutter', 'diffAdded', 'DiffAdd' }, { GitSignsAdd = { 'GitGutterAdd', 'SignifySignAdd', 'DiffAddedGutter', 'diffAdded', 'DiffAdd' } },
GitSignsChange = { 'GitGutterChange', 'SignifySignChange', 'DiffModifiedGutter', 'diffChanged', 'DiffChange' }, { GitSignsChange = { 'GitGutterChange', 'SignifySignChange', 'DiffModifiedGutter', 'diffChanged', 'DiffChange' } },
GitSignsDelete = { 'GitGutterDelete', 'SignifySignDelete', 'DiffRemovedGutter', 'diffRemoved', 'DiffDelete' }, { GitSignsDelete = { 'GitGutterDelete', 'SignifySignDelete', 'DiffRemovedGutter', 'diffRemoved', 'DiffDelete' } },
GitSignsAddNr = { 'GitGutterAddLineNr', 'GitSignsAdd', 'SignifySignAdd', 'DiffAddedGutter', 'diffAdded', 'DiffAdd' }, { GitSignsAddNr = { 'GitGutterAddLineNr', 'GitSignsAdd' } },
GitSignsChangeNr = { 'GitGutterChangeLineNr', 'GitSignsChange', 'SignifySignChange', 'DiffModifiedGutter', 'diffChanged', 'DiffChange' }, { GitSignsChangeNr = { 'GitGutterChangeLineNr', 'GitSignsChange' } },
GitSignsDeleteNr = { 'GitGutterDeleteLineNr', 'GitSignsDelete', 'SignifySignDelete', 'DiffRemovedGutter', 'diffRemoved', 'DiffDelete' }, { GitSignsDeleteNr = { 'GitGutterDeleteLineNr', 'GitSignsDelete' } },
GitSignsAddLn = { 'GitGutterAddLine', 'SignifyLineAdd', 'DiffAdd' }, { GitSignsAddLn = { 'GitGutterAddLine', 'SignifyLineAdd', 'DiffAdd' } },
GitSignsChangeLn = { 'GitGutterChangeLine', 'SignifyLineChange', 'DiffChange' }, { GitSignsChangeLn = { 'GitGutterChangeLine', 'SignifyLineChange', 'DiffChange' } },
GitSignsDeleteLn = { 'GitGutterDeleteLine', 'SignifyLineDelete', 'DiffDelete' }, { GitSignsDeleteLn = { 'GitGutterDeleteLine', 'SignifyLineDelete', 'DiffDelete' } },
GitSignsCurrentLineBlame = { 'NonText' }, { GitSignsCurrentLineBlame = { 'NonText' } },
{ GitSignsAddInline = { 'TermCursor' } },
{ GitSignsDeleteInline = { 'TermCursor' } },
{ GitSignsChangeInline = { 'TermCursor' } },
{ GitSignsAddLnInline = { 'GitSignsAddInline' } },
{ GitSignsChangeLnInline = { 'GitSignsChangeInline' } },
{ GitSignsDeleteLnInline = { 'GitSignsDeleteInline' } },
{ GitSignsAddLnVirtLn = { 'GitSignsAddLn' } },
{ GitSignsChangeVirtLn = { 'GitSignsChangeLn' } },
{ GitSignsDeleteVirtLn = { 'GitSignsDeleteLn' } },
{ GitSignsAddLnVirtLnInLine = { 'GitSignsAddLnInline' } },
{ GitSignsChangeVirtLnInLine = { 'GitSignsChangeLnInline' } },
{ GitSignsDeleteVirtLnInLine = { 'GitSignsDeleteLnInline' } },
} }
local function is_hl_set(hl_name) local function is_hl_set(hl_name)
local exists, hl = pcall(api.nvim_get_hl_by_name, hl_name, true) local exists, hl = pcall(api.nvim_get_hl_by_name, hl_name, true)
local color = hl.foreground or hl.background local color = hl.foreground or hl.background or hl.reverse
return exists and color ~= nil return exists and color ~= nil
end end
local function isGitSignHl(hl)
return hls[hl] ~= nil
end
M.setup_highlights = function()
for _, hlg in ipairs(hls) do
for hl, candidates in pairs(hlg) do
if is_hl_set(hl) then
M.setup_highlight = function(hl_name) dprintf('Highlight %s is already defined', hl)
if not isGitSignHl(hl_name) then else
return for _, d in ipairs(candidates) do
end if is_hl_set(d) then
dprintf('Deriving %s from %s', hl, d)
if is_hl_set(hl_name) then vim.cmd(('highlight default link %s %s'):format(hl, d))
break
dprintf('Highlight %s is already defined', hl_name) end
return end
end end
for _, d in ipairs(hls[hl_name]) do
if is_hl_set(d) then
dprintf('Deriving %s from %s', hl_name, d)
vim.cmd(('highlight default link %s %s'):format(hl_name, d))
return
end end
end end
dprintf('Unable to setup highlight %s', hl_name)
end end
return M return M

View File

@ -22,8 +22,7 @@ local util = require('gitsigns.util')
local gs_hunks = require("gitsigns.hunks") local gs_hunks = require("gitsigns.hunks")
local Hunk = gs_hunks.Hunk local Hunk = gs_hunks.Hunk
local setup_highlight = require('gitsigns.highlight').setup_highlight local setup_highlights = require('gitsigns.highlight').setup_highlights
local config = require('gitsigns.config').config local config = require('gitsigns.config').config
local api = vim.api local api = vim.api
@ -184,7 +183,8 @@ M.apply_word_diff = function(bufnr, row)
for _, hunk in ipairs(cache[bufnr].hunks) do for _, hunk in ipairs(cache[bufnr].hunks) do
if lnum >= hunk.start and lnum <= hunk.vend then if lnum >= hunk.start and lnum <= hunk.vend then
local size = (#hunk.added.lines + #hunk.removed.lines) / 2 local size = (#hunk.added.lines + #hunk.removed.lines) / 2
local regions = require('gitsigns.diff_int').run_word_diff(hunk.removed.lines, hunk.added.lines) local removed_regions, added_regions = require('gitsigns.diff_int').run_word_diff(hunk.removed.lines, hunk.added.lines)
local regions = vim.list_extend(removed_regions or {}, added_regions or {})
for _, region in ipairs(regions) do for _, region in ipairs(regions) do
local line = region[1] local line = region[1]
if lnum == hunk.start + line - size - 1 then if lnum == hunk.start + line - size - 1 then
@ -199,10 +199,11 @@ M.apply_word_diff = function(bufnr, row)
end end
api.nvim_buf_set_extmark(bufnr, ns, row, scol - 1, { api.nvim_buf_set_extmark(bufnr, ns, row, scol - 1, {
end_col = ecol - 1, end_col = ecol - 1,
hl_group = rtype == 'add' and 'GitSignsAddLn' or hl_group = rtype == 'add' and 'GitSignsAddLnInline' or
rtype == 'change' and 'GitSignsChangeLn' or rtype == 'change' and 'GitSignsChangeLnInline' or
'GitSignsDeleteLn', 'GitSignsDeleteLnInline',
ephemeral = true, ephemeral = true,
priority = 1000,
}) })
end end
end end
@ -249,19 +250,19 @@ local function show_deleted(bufnr)
if rline > 1 then if rline > 1 then
break break
end end
vline[#vline + 1] = { line:sub(last_ecol, scol - 1), config.signs.delete.linehl } vline[#vline + 1] = { line:sub(last_ecol, scol - 1), 'GitsignsDeleteVirtLn' }
vline[#vline + 1] = { line:sub(scol, ecol - 1), 'TermCursor' } vline[#vline + 1] = { line:sub(scol, ecol - 1), 'GitsignsDeleteVirtLnInline' }
last_ecol = ecol last_ecol = ecol
end end
end end
if #line > 0 then if #line > 0 then
vline[#vline + 1] = { line:sub(last_ecol, -1), config.signs.delete.linehl } vline[#vline + 1] = { line:sub(last_ecol, -1), 'GitsignsDeleteVirtLn' }
end end
local padding = string.rep(' ', VIRT_LINE_LEN - #line) local padding = string.rep(' ', VIRT_LINE_LEN - #line)
vline[#vline + 1] = { padding, config.signs.delete.linehl } vline[#vline + 1] = { padding, 'GitsignsDeleteVirtLn' }
virt_lines[i] = vline virt_lines[i] = vline
end end
@ -340,16 +341,6 @@ M.setup_signs_and_highlights = function(redefine)
for t, sign_name in pairs(signs.sign_map) do for t, sign_name in pairs(signs.sign_map) do
local cs = config.signs[t] local cs = config.signs[t]
setup_highlight(cs.hl)
if config.numhl then
setup_highlight(cs.numhl)
end
if config.linehl or config.word_diff then
setup_highlight(cs.linehl)
end
signs.define(sign_name, { signs.define(sign_name, {
texthl = cs.hl, texthl = cs.hl,
text = config.signcolumn and cs.text or nil, text = config.signcolumn and cs.text or nil,
@ -358,9 +349,8 @@ M.setup_signs_and_highlights = function(redefine)
}, redefine) }, redefine)
end end
if config.current_line_blame then
setup_highlight('GitSignsCurrentLineBlame') setup_highlights()
end
end end
return M return M

View File

@ -21,6 +21,7 @@ local Hunk_Public = gs_hunks.Hunk_Public
local api = vim.api local api = vim.api
local current_buf = api.nvim_get_current_buf local current_buf = api.nvim_get_current_buf
local add_highlight = api.nvim_buf_add_highlight
local record NavHunkOpts local record NavHunkOpts
forwards: boolean forwards: boolean
@ -437,17 +438,21 @@ end
local function highlight_hunk_lines(bufnr: integer, offset: integer, hunk: Hunk) local function highlight_hunk_lines(bufnr: integer, offset: integer, hunk: Hunk)
for i = 1, #hunk.removed.lines do for i = 1, #hunk.removed.lines do
api.nvim_buf_add_highlight(bufnr, -1, 'DiffRemoved', offset+i-1, 0, -1) add_highlight(bufnr, -1, 'GitSignsDeleteLn', offset+i-1, 0, -1)
end end
for i = 1, #hunk.added.lines do for i = 1, #hunk.added.lines do
api.nvim_buf_add_highlight(bufnr, -1, 'DiffAdded', #hunk.removed.lines + offset+i-1, 0, -1) add_highlight(bufnr, -1, 'GitSignsAddLn', #hunk.removed.lines + offset+i-1, 0, -1)
end end
if config.diff_opts.internal then if config.diff_opts.internal then
local regions = require('gitsigns.diff_int').run_word_diff(hunk.removed.lines, hunk.added.lines) local removed_regions, added_regions = require('gitsigns.diff_int').run_word_diff(hunk.removed.lines, hunk.added.lines)
for _, region in ipairs(regions) do for _, region in ipairs(removed_regions) do
local line, scol, ecol = region[1], region[3], region[4] local line, scol, ecol = region[1], region[3], region[4]
api.nvim_buf_add_highlight(bufnr, -1, 'TermCursor', line+offset-1, scol, ecol) add_highlight(bufnr, -1, 'GitSignsDeleteInline', line+offset-1, scol, ecol)
end
for _, region in ipairs(added_regions) do
local line, scol, ecol = region[1], region[3], region[4]
add_highlight(bufnr, -1, 'GitSignsAddInline', line+offset-1, scol, ecol)
end end
end end
end end
@ -499,7 +504,7 @@ M.preview_hunk = noautocmd(function()
local _, bufnr = popup.create(lines, config.preview_config) local _, bufnr = popup.create(lines, config.preview_config)
api.nvim_buf_add_highlight(bufnr, -1, 'Title', 0, 0, -1) add_highlight(bufnr, -1, 'Title', 0, 0, -1)
api.nvim_buf_set_var(cbuf, '_gitsigns_preview_open', true) api.nvim_buf_set_var(cbuf, '_gitsigns_preview_open', true)
vim.cmd[[autocmd CursorMoved,CursorMovedI <buffer> ++once silent! unlet b:_gitsigns_preview_open]] vim.cmd[[autocmd CursorMoved,CursorMovedI <buffer> ++once silent! unlet b:_gitsigns_preview_open]]
@ -637,7 +642,7 @@ M.blame_line = void(function(opts: boolean | BlameOpts)
local highlights: {{string, integer, integer, integer}} = {} local highlights: {{string, integer, integer, integer}} = {}
local function add_highlight(hlgroup: string, start: integer, length: integer) local function add_hl(hlgroup: string, start: integer, length: integer)
highlights[#highlights+1] = {hlgroup, #lines-1, start or 0, length or -1} highlights[#highlights+1] = {hlgroup, #lines-1, start or 0, length or -1}
end end
@ -660,9 +665,9 @@ M.blame_line = void(function(opts: boolean | BlameOpts)
local p2 = #result.author local p2 = #result.author
local p3 = #date local p3 = #date
add_highlight('Directory', 0 , p1) add_hl('Directory', 0 , p1)
add_highlight('MoreMsg' , p1+1 , p2) add_hl('MoreMsg' , p1+1 , p2)
add_highlight('Label' , p1+p2+2, p3+2) add_hl('Label' , p1+p2+2, p3+2)
vim.list_extend(lines, commit_message) vim.list_extend(lines, commit_message)
@ -671,7 +676,7 @@ M.blame_line = void(function(opts: boolean | BlameOpts)
end end
else else
lines[#lines+1] = result.author lines[#lines+1] = result.author
add_highlight('ErrorMsg') add_hl('ErrorMsg')
if full then if full then
scheduler() scheduler()
hunk, ihunk = get_cursor_hunk(bufnr, bcache.hunks) hunk, ihunk = get_cursor_hunk(bufnr, bcache.hunks)
@ -682,7 +687,7 @@ M.blame_line = void(function(opts: boolean | BlameOpts)
if hunk then if hunk then
lines[#lines+1] = '' lines[#lines+1] = ''
lines[#lines+1] = ('Hunk %d of %d'):format(ihunk, nhunk) lines[#lines+1] = ('Hunk %d of %d'):format(ihunk, nhunk)
add_highlight('Title') add_hl('Title')
vim.list_extend(lines, gs_hunks.patch_lines(hunk)) vim.list_extend(lines, gs_hunks.patch_lines(hunk))
end end
@ -691,7 +696,7 @@ M.blame_line = void(function(opts: boolean | BlameOpts)
for _, h in ipairs(highlights) do for _, h in ipairs(highlights) do
local hlgroup, line, start, length = h[1], h[2], h[3], h[4] local hlgroup, line, start, length = h[1], h[2], h[3], h[4]
api.nvim_buf_add_highlight(pbufnr, -1, hlgroup, line, start, start+length) add_highlight(pbufnr, -1, hlgroup, line, start, start+length)
end end
if hunk then if hunk then

View File

@ -131,18 +131,16 @@ M.schema = {
`show_count` to enable showing count of hunk, e.g. number of deleted `show_count` to enable showing count of hunk, e.g. number of deleted
lines. lines.
Note if `hl`, `numhl` or `linehl` use a `GitSigns*` highlight and it is Note if a highlight is not defined, it will be automatically derived by
not defined, it will be automatically derived by searching for other searching for other defined highlights in the following order:
defined highlights in the following order:
`GitGutter*` `GitGutter*`
`Signify*` `Signify*`
`Diff*Gutter` `Diff*Gutter`
`diff*` `diff*`
`Diff*` `Diff*`
For example if `signs.add.hl = GitSignsAdd` and `GitSignsAdd` is not For example if `GitSignsAdd` is not defined but `GitGutterAdd` is defined,
defined but `GitGutterAdd` is defined, then `GitSignsAdd` will be linked then `GitSignsAdd` will be linked to `GitGutterAdd`.
to `GitGutterAdd`.
]] ]]
}, },
@ -591,9 +589,18 @@ M.schema = {
Requires `config.diff_opts.internal = true` . Requires `config.diff_opts.internal = true` .
Uses the highlights: Uses the highlights:
GitSignsAddLn For word diff in previews:
GitSignsChangeLn `GitSignsAddInline`
GitSignsDeleteLn `GitSignsChangeInline`
`GitSignsDeleteInline`
For word diff in buffer:
`GitSignsAddLnInline`
`GitSignsChangeLnInline`
`GitSignsDeleteLnInline`
For word diff in virtual lines (e.g. show_deleted):
`GitSignsAddVirtLnInline`
`GitSignsChangeVirtLnInline`
`GitSignsDeleteVirtLnInline`
]] ]]
}, },

View File

@ -49,12 +49,13 @@ local type Region = {integer, string, integer, integer}
local gaps_between_regions = 5 local gaps_between_regions = 5
function M.run_word_diff(removed: {string}, added: {string}): {Region} function M.run_word_diff(removed: {string}, added: {string}): {Region}, {Region}
if #removed ~= #added then local adds: {Region} = {}
return {} local rems: {Region} = {}
end
local ret: {Region} = {} if #removed ~= #added then
return rems, adds
end
for i = 1, #removed do for i = 1, #removed do
-- pair lines by position -- pair lines by position
@ -93,14 +94,11 @@ function M.run_word_diff(removed: {string}, added: {string}): {Region}
end end
for _, h in ipairs(hunks) do for _, h in ipairs(hunks) do
local rem = {i , h.type, h.removed.start, h.removed.start + h.removed.count} adds[#adds+1] = {i+#removed, h.type, h.added.start , h.added.start + h.added.count}
local add = {i+#removed, h.type, h.added.start , h.added.start + h.added.count} rems[#rems+1] = {i , h.type, h.removed.start, h.removed.start + h.removed.count}
ret[#ret+1] = rem
ret[#ret+1] = add
end end
end end
return ret return rems, adds
end end
return M return M

View File

@ -4,74 +4,68 @@ local api = vim.api
local dprintf = require("gitsigns.debug").dprintf local dprintf = require("gitsigns.debug").dprintf
local record M local record M
setup_highlight: function(string) setup_highlights: function()
end end
local enum GitSignHl -- Use array of dict so we can iterate deterministically
'GitSignsAdd' local hls: {{string:{string}}} = {
'GitSignsChange' {GitSignsAdd = {'GitGutterAdd' , 'SignifySignAdd' , 'DiffAddedGutter' , 'diffAdded' , 'DiffAdd' }},
'GitSignsDelete' {GitSignsChange = {'GitGutterChange', 'SignifySignChange', 'DiffModifiedGutter', 'diffChanged', 'DiffChange'}},
{GitSignsDelete = {'GitGutterDelete', 'SignifySignDelete', 'DiffRemovedGutter' , 'diffRemoved', 'DiffDelete'}},
'GitSignsAddNr' {GitSignsAddNr = {'GitGutterAddLineNr' , 'GitSignsAdd' }},
'GitSignsChangeNr' {GitSignsChangeNr = {'GitGutterChangeLineNr', 'GitSignsChange'}},
'GitSignsDeleteNr' {GitSignsDeleteNr = {'GitGutterDeleteLineNr', 'GitSignsDelete'}},
'GitSignsAddLn' {GitSignsAddLn = {'GitGutterAddLine' , 'SignifyLineAdd' , 'DiffAdd' }},
'GitSignsChangeLn' {GitSignsChangeLn = {'GitGutterChangeLine', 'SignifyLineChange', 'DiffChange'}},
'GitSignsDeleteLn' {GitSignsDeleteLn = {'GitGutterDeleteLine', 'SignifyLineDelete', 'DiffDelete'}},
'GitSignsCurrentLineBlame' {GitSignsCurrentLineBlame = {'NonText'}},
end
local hls: {GitSignHl:{string}} = { {GitSignsAddInline = {'TermCursor'}},
GitSignsAdd = {'GitGutterAdd' , 'SignifySignAdd' , 'DiffAddedGutter' , 'diffAdded' , 'DiffAdd' }, {GitSignsDeleteInline = {'TermCursor'}},
GitSignsChange = {'GitGutterChange', 'SignifySignChange', 'DiffModifiedGutter', 'diffChanged', 'DiffChange'}, {GitSignsChangeInline = {'TermCursor'}},
GitSignsDelete = {'GitGutterDelete', 'SignifySignDelete', 'DiffRemovedGutter' , 'diffRemoved', 'DiffDelete'},
GitSignsAddNr = {'GitGutterAddLineNr' , 'GitSignsAdd' , 'SignifySignAdd' , 'DiffAddedGutter' , 'diffAdded' , 'DiffAdd' }, {GitSignsAddLnInline = {'GitSignsAddInline'}},
GitSignsChangeNr = {'GitGutterChangeLineNr', 'GitSignsChange', 'SignifySignChange', 'DiffModifiedGutter', 'diffChanged', 'DiffChange'}, {GitSignsChangeLnInline = {'GitSignsChangeInline'}},
GitSignsDeleteNr = {'GitGutterDeleteLineNr', 'GitSignsDelete', 'SignifySignDelete', 'DiffRemovedGutter' , 'diffRemoved', 'DiffDelete'}, {GitSignsDeleteLnInline = {'GitSignsDeleteInline'}},
GitSignsAddLn = {'GitGutterAddLine' , 'SignifyLineAdd' , 'DiffAdd' }, {GitSignsAddLnVirtLn = {'GitSignsAddLn' }},
GitSignsChangeLn = {'GitGutterChangeLine', 'SignifyLineChange', 'DiffChange'}, {GitSignsChangeVirtLn = {'GitSignsChangeLn'}},
GitSignsDeleteLn = {'GitGutterDeleteLine', 'SignifyLineDelete', 'DiffDelete'}, {GitSignsDeleteVirtLn = {'GitSignsDeleteLn'}},
GitSignsCurrentLineBlame = {'NonText'}, {GitSignsAddLnVirtLnInLine = {'GitSignsAddLnInline' }},
{GitSignsChangeVirtLnInLine = {'GitSignsChangeLnInline'}},
{GitSignsDeleteVirtLnInLine = {'GitSignsDeleteLnInline'}},
} }
local function is_hl_set(hl_name: string): boolean local function is_hl_set(hl_name: string): boolean
-- TODO: this only works with `set termguicolors` -- TODO: this only works with `set termguicolors`
local exists, hl = pcall(api.nvim_get_hl_by_name, hl_name, true) local exists, hl = pcall(api.nvim_get_hl_by_name, hl_name, true)
local color = hl.foreground or hl.background local color = hl.foreground or hl.background or hl.reverse
return exists and color ~= nil return exists and color ~= nil
end end
local function isGitSignHl(hl: string): boolean
return hls[hl as GitSignHl] ~= nil
end
-- Setup a GitSign* highlight by deriving it from other potentially present -- Setup a GitSign* highlight by deriving it from other potentially present
-- highlights. -- highlights.
M.setup_highlight = function(hl_name: string) M.setup_highlights = function()
if not isGitSignHl(hl_name) then for _, hlg in ipairs(hls) do
return for hl, candidates in pairs(hlg) do
end if is_hl_set(hl) then
-- Already defined
if is_hl_set(hl_name) then dprintf('Highlight %s is already defined', hl)
-- Already defined else
dprintf('Highlight %s is already defined', hl_name) for _, d in ipairs(candidates) do
return if is_hl_set(d) then
end dprintf('Deriving %s from %s', hl, d)
vim.cmd(('highlight default link %s %s'):format(hl, d))
for _, d in ipairs(hls[hl_name as GitSignHl]) do break
if is_hl_set(d) then end
dprintf('Deriving %s from %s', hl_name, d) end
vim.cmd(('highlight default link %s %s'):format(hl_name, d)) end
return
end end
end end
dprintf('Unable to setup highlight %s', hl_name)
end end
return M return M

View File

@ -22,9 +22,8 @@ local util = require('gitsigns.util')
local gs_hunks = require("gitsigns.hunks") local gs_hunks = require("gitsigns.hunks")
local Hunk = gs_hunks.Hunk local Hunk = gs_hunks.Hunk
local setup_highlight = require('gitsigns.highlight').setup_highlight local setup_highlights = require('gitsigns.highlight').setup_highlights
local config = require('gitsigns.config').config
local config = require('gitsigns.config').config
local api = vim.api local api = vim.api
@ -184,7 +183,8 @@ M.apply_word_diff = function(bufnr: integer, row: integer)
for _, hunk in ipairs(cache[bufnr].hunks) do for _, hunk in ipairs(cache[bufnr].hunks) do
if lnum >= hunk.start and lnum <= hunk.vend then if lnum >= hunk.start and lnum <= hunk.vend then
local size = (#hunk.added.lines +#hunk.removed.lines) / 2 local size = (#hunk.added.lines +#hunk.removed.lines) / 2
local regions = require('gitsigns.diff_int').run_word_diff(hunk.removed.lines, hunk.added.lines) local removed_regions, added_regions = require('gitsigns.diff_int').run_word_diff(hunk.removed.lines, hunk.added.lines)
local regions = vim.list_extend(removed_regions or {}, added_regions or {})
for _, region in ipairs(regions) do for _, region in ipairs(regions) do
local line = region[1] local line = region[1]
if lnum == hunk.start + line - size - 1 then if lnum == hunk.start + line - size - 1 then
@ -199,10 +199,11 @@ M.apply_word_diff = function(bufnr: integer, row: integer)
end end
api.nvim_buf_set_extmark(bufnr, ns, row, scol-1, { api.nvim_buf_set_extmark(bufnr, ns, row, scol-1, {
end_col = ecol-1, end_col = ecol-1,
hl_group = rtype == 'add' and 'GitSignsAddLn' hl_group = rtype == 'add' and 'GitSignsAddLnInline'
or rtype == 'change' and 'GitSignsChangeLn' or rtype == 'change' and 'GitSignsChangeLnInline'
or 'GitSignsDeleteLn', or 'GitSignsDeleteLnInline',
ephemeral = true ephemeral = true,
priority = 1000
}) })
end end
end end
@ -249,19 +250,19 @@ local function show_deleted(bufnr: integer)
if rline > 1 then if rline > 1 then
break break
end end
vline[#vline+1] = { line:sub(last_ecol, scol-1), config.signs.delete.linehl } vline[#vline+1] = { line:sub(last_ecol, scol-1), 'GitsignsDeleteVirtLn'}
vline[#vline+1] = { line:sub(scol, ecol-1), 'TermCursor'} vline[#vline+1] = { line:sub(scol, ecol-1), 'GitsignsDeleteVirtLnInline'}
last_ecol = ecol last_ecol = ecol
end end
end end
if #line > 0 then if #line > 0 then
vline[#vline+1] = { line:sub(last_ecol, -1), config.signs.delete.linehl } vline[#vline+1] = { line:sub(last_ecol, -1), 'GitsignsDeleteVirtLn'}
end end
-- Add extra padding so the entire line is highlighted -- Add extra padding so the entire line is highlighted
local padding = string.rep(' ', VIRT_LINE_LEN-#line) local padding = string.rep(' ', VIRT_LINE_LEN-#line)
vline[#vline+1] = { padding, config.signs.delete.linehl } vline[#vline+1] = { padding, 'GitsignsDeleteVirtLn'}
virt_lines[i] = vline virt_lines[i] = vline
end end
@ -340,16 +341,6 @@ M.setup_signs_and_highlights = function(redefine: boolean)
for t, sign_name in pairs(signs.sign_map) do for t, sign_name in pairs(signs.sign_map) do
local cs = config.signs[t] local cs = config.signs[t]
setup_highlight(cs.hl)
if config.numhl then
setup_highlight(cs.numhl)
end
if config.linehl or config.word_diff then
setup_highlight(cs.linehl)
end
signs.define(sign_name, { signs.define(sign_name, {
texthl = cs.hl, texthl = cs.hl,
text = config.signcolumn and cs.text or nil, text = config.signcolumn and cs.text or nil,
@ -358,9 +349,8 @@ M.setup_signs_and_highlights = function(redefine: boolean)
}, redefine) }, redefine)
end end
if config.current_line_blame then
setup_highlight('GitSignsCurrentLineBlame') setup_highlights()
end
end end
return M return M

View File

@ -33,11 +33,10 @@ describe('gitdir_watcher', function()
it('can follow moved files', function() it('can follow moved files', function()
setup_test_repo() setup_test_repo()
setup_gitsigns(test_config) setup_gitsigns(test_config)
command('Gitsigns clear_debug')
edit(test_file) edit(test_file)
match_debug_messages { match_debug_messages {
"run_job: git --no-pager --version",
'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
'attach(1): Attaching (trigger=BufRead)', 'attach(1): Attaching (trigger=BufRead)',
p"run_job: git .* config user.name", p"run_job: git .* config user.name",
"run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD", "run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD",

View File

@ -107,12 +107,11 @@ describe('gitsigns', function()
it('can open files not in a git repo', function() it('can open files not in a git repo', function()
setup_gitsigns(config) setup_gitsigns(config)
command('Gitsigns clear_debug')
local tmpfile = os.tmpname() local tmpfile = os.tmpname()
edit(tmpfile) edit(tmpfile)
match_debug_messages { match_debug_messages {
'run_job: git --no-pager --version',
'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
'attach(1): Attaching (trigger=BufRead)', 'attach(1): Attaching (trigger=BufRead)',
p'run_job: git .* config user.name', p'run_job: git .* config user.name',
'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD', 'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
@ -158,11 +157,10 @@ describe('gitsigns', function()
end) end)
it('does not attach inside .git', function() it('does not attach inside .git', function()
command("Gitsigns clear_debug")
edit(scratch..'/.git/index') edit(scratch..'/.git/index')
match_debug_messages { match_debug_messages {
'run_job: git --no-pager --version',
'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
'attach(1): Attaching (trigger=BufRead)', 'attach(1): Attaching (trigger=BufRead)',
'new: In git dir', 'new: In git dir',
'attach(1): Empty git obj' 'attach(1): Empty git obj'
@ -170,6 +168,7 @@ describe('gitsigns', function()
end) end)
it('doesn\'t attach to ignored files', function() it('doesn\'t attach to ignored files', function()
command("Gitsigns clear_debug")
write_to_file(scratch..'/.gitignore', {'dummy_ignored.txt'}) write_to_file(scratch..'/.gitignore', {'dummy_ignored.txt'})
local ignored_file = scratch.."/dummy_ignored.txt" local ignored_file = scratch.."/dummy_ignored.txt"
@ -178,8 +177,6 @@ describe('gitsigns', function()
edit(ignored_file) edit(ignored_file)
match_debug_messages { match_debug_messages {
'run_job: git --no-pager --version',
'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
'attach(1): Attaching (trigger=BufRead)', 'attach(1): Attaching (trigger=BufRead)',
p'run_job: git .* config user.name', p'run_job: git .* config user.name',
'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD', 'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
@ -191,11 +188,10 @@ describe('gitsigns', function()
end) end)
it('doesn\'t attach to non-existent files', function() it('doesn\'t attach to non-existent files', function()
command("Gitsigns clear_debug")
edit(newfile) edit(newfile)
match_debug_messages { match_debug_messages {
'run_job: git --no-pager --version',
'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
'attach(1): Attaching (trigger=BufNewFile)', 'attach(1): Attaching (trigger=BufNewFile)',
p'run_job: git .* config user.name', p'run_job: git .* config user.name',
'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD', 'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
@ -207,11 +203,10 @@ describe('gitsigns', function()
end) end)
it('doesn\'t attach to non-existent files with non-existent sub-dirs', function() it('doesn\'t attach to non-existent files with non-existent sub-dirs', function()
command("Gitsigns clear_debug")
edit(scratch..'/does/not/exist') edit(scratch..'/does/not/exist')
match_debug_messages { match_debug_messages {
'run_job: git --no-pager --version',
'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
'attach(1): Attaching (trigger=BufNewFile)', 'attach(1): Attaching (trigger=BufNewFile)',
'attach(1): Not a path', 'attach(1): Not a path',
} }
@ -221,10 +216,9 @@ describe('gitsigns', function()
end) end)
it('can run copen', function() it('can run copen', function()
command("Gitsigns clear_debug")
command("copen") command("copen")
match_debug_messages { match_debug_messages {
'run_job: git --no-pager --version',
'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
'attach(2): Attaching (trigger=BufRead)', 'attach(2): Attaching (trigger=BufRead)',
'attach(2): Non-normal buffer', 'attach(2): Non-normal buffer',
} }
@ -340,11 +334,10 @@ describe('gitsigns', function()
return false return false
end end
]]) ]])
command("Gitsigns clear_debug")
edit(test_file) edit(test_file)
match_debug_messages { match_debug_messages {
'run_job: git --no-pager --version',
'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
'attach(1): Attaching (trigger=BufRead)', 'attach(1): Attaching (trigger=BufRead)',
p'run_job: git .* config user.name', p'run_job: git .* config user.name',
'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD', 'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
@ -455,10 +448,9 @@ describe('gitsigns', function()
it('attaches to newly created files', function() it('attaches to newly created files', function()
setup_gitsigns(config) setup_gitsigns(config)
command('Gitsigns clear_debug')
edit(newfile) edit(newfile)
match_debug_messages{ match_debug_messages{
'run_job: git --no-pager --version',
'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
'attach(1): Attaching (trigger=BufNewFile)', 'attach(1): Attaching (trigger=BufNewFile)',
'run_job: git --no-pager config user.name', 'run_job: git --no-pager config user.name',
'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD', 'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
@ -656,6 +648,7 @@ describe('gitsigns', function()
write_to_file(scratch..'/t3.txt', {'hello lewis'}) write_to_file(scratch..'/t3.txt', {'hello lewis'})
setup_gitsigns(config) setup_gitsigns(config)
command('Gitsigns clear_debug')
helpers.exc_exec("vimgrep ben "..scratch..'/*') helpers.exc_exec("vimgrep ben "..scratch..'/*')
@ -664,8 +657,6 @@ describe('gitsigns', function()
}}} }}}
eq({ eq({
'run_job: git --no-pager --version',
'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
'attach(2): attaching is disabled', 'attach(2): attaching is disabled',
'attach(3): attaching is disabled', 'attach(3): attaching is disabled',
'attach(4): attaching is disabled', 'attach(4): attaching is disabled',

View File

@ -384,7 +384,7 @@ global record vim
in_fast_event: function(): boolean in_fast_event: function(): boolean
list_extend: function({any}, {any}, integer, integer) list_extend: function<T>({T}, {T}, integer, integer): {T}
list_slice: function<T>({T}, integer, integer): {T} list_slice: function<T>({T}, integer, integer): {T}
record keymap record keymap