Hunk previews highlight intra-line changes.

Closes #577.
This commit is contained in:
Andy Stewart
2019-09-18 11:55:01 +01:00
parent b71ab64dc1
commit fdecc23368
5 changed files with 183 additions and 0 deletions

View File

@@ -12,6 +12,7 @@ Features:
* Never saves the buffer.
* Quick jumping between blocks of changed lines ("hunks").
* Stage/undo/preview individual hunks.
* Previews highlight intra-line changes.
* Stage partial hunks.
* Provides a hunk text object.
* Diffs against index (default) or any commit.

View File

@@ -0,0 +1,100 @@
" Calculates the changed portions of lines. Based closely on diff-highlight
" (included with git) - please note its caveats.
"
" https://github.com/git/git/blob/master/contrib/diff-highlight/DiffHighlight.pm
" Returns a list of intra-line changed regions.
" Each element is a list:
"
" [
" line number (1-based),
" type ('+' or '-'),
" start column (1-based, inclusive),
" stop column (1-based, inclusive),
" ]
"
" Args:
" hunk_body - list of lines
function! gitgutter#diff_highlight#process(hunk_body)
" Check whether we have the same number of lines added as removed.
let [removed, added] = [0, 0]
for line in a:hunk_body
if line[0] == '-'
let removed += 1
elseif line[0] == '+'
let added += 1
endif
endfor
if removed != added
return []
endif
let regions = []
for i in range(removed)
" pair lines by position
let rline = a:hunk_body[i]
let aline = a:hunk_body[i + removed]
let prefix = s:common_prefix(rline, aline)
let [rsuffix, asuffix] = s:common_suffix(rline, aline, prefix+1)
if (prefix != 0 || rsuffix != 0) && prefix+1 < rsuffix
call add(regions, [i+1, '-', prefix+1+1, rsuffix+1-1])
endif
if (prefix != 0 || asuffix != 0) && prefix+1 < asuffix
call add(regions, [i+1+removed, '+', prefix+1+1, asuffix+1-1])
endif
endfor
return regions
endfunction
" Returns 0-based index of last character of common prefix
" Does not treat leading +/- as different.
"
" a, b - strings
"
function! s:common_prefix(a, b)
let len = min([len(a:a), len(a:b)])
" ignore initial +/-
for i in range(1, len - 1)
if a:a[i:i] != a:b[i:i]
return i - 1
endif
endfor
return i
endfunction
if $VIM_GITGUTTER_TEST
function! gitgutter#diff_highlight#common_prefix(a, b)
return s:common_prefix(a:a, a:b)
endfunction
endif
" Returns 0-based indices of start of common suffix
"
" a, b - strings
" start - 0-based index to start from
function! s:common_suffix(a, b, start)
let [sa, sb] = [len(a:a), len(a:b)]
while sa >= a:start && sb >= a:start
if a:a[sa] ==# a:b[sb]
let sa -= 1
let sb -= 1
else
break
endif
endwhile
return [sa+1, sb+1]
endfunction
if $VIM_GITGUTTER_TEST
function! gitgutter#diff_highlight#common_suffix(a, b, start)
return s:common_suffix(a:a, a:b, a:start)
endfunction
endif

View File

@@ -102,6 +102,10 @@ function! gitgutter#highlight#define_highlights() abort
highlight default link GitGutterChangeLineNr CursorLineNr
highlight default link GitGutterDeleteLineNr CursorLineNr
highlight default link GitGutterChangeDeleteLineNr CursorLineNr
" Highlights used intra line.
highlight GitGutterAddIntraLine gui=reverse
highlight GitGutterDeleteIntraLine gui=reverse
endfunction
function! gitgutter#highlight#define_signs() abort

View File

@@ -458,11 +458,20 @@ function! s:populate_hunk_preview_window(header, body)
call nvim_buf_set_lines(winbufnr(s:winid), 0, -1, v:false, [])
call nvim_buf_set_lines(winbufnr(s:winid), 0, -1, v:false, a:body)
call nvim_buf_set_option(winbufnr(s:winid), 'modified', v:false)
let ns_id = nvim_create_namespace('GitGutter')
call nvim_buf_clear_namespace(winbufnr(s:winid), ns_id, 0, -1)
for region in gitgutter#diff_highlight#process(a:body)
let group = region[1] == '+' ? 'GitGutterAddIntraLine' : 'GitGutterDeleteIntraLine'
call nvim_buf_add_highlight(winbufnr(s:winid), ns_id, group, region[0]-1, region[2]-1, region[3])
endfor
call nvim_win_set_cursor(s:winid, [1,0])
endif
if exists('*popup_create')
call popup_settext(s:winid, a:body)
" TODO add intra line highlights
endif
else
@@ -472,6 +481,13 @@ function! s:populate_hunk_preview_window(header, body)
%delete _
call setline(1, a:body)
setlocal nomodified
call clearmatches()
for region in gitgutter#diff_highlight#process(a:body)
let group = region[1] == '+' ? 'GitGutterAddIntraLine' : 'GitGutterDeleteIntraLine'
call matchaddpos(group, [[region[0], region[2], region[3]-region[2]+1]])
endfor
1
endif
endfunction

View File

@@ -60,6 +60,9 @@ function SetUp()
execute ':cd' s:test_repo
edit! fixture.txt
call gitgutter#sign#reset()
" FIXME why won't vim autoload the file?
execute 'source' '../../autoload/gitgutter/diff_highlight.vim'
endfunction
function TearDown()
@@ -910,3 +913,62 @@ function Test_quickfix()
call s:assert_list_of_dicts(expected, getqflist())
endfunction
function Test_common_prefix()
" nothing in common
call assert_equal(0, gitgutter#diff_highlight#common_prefix('-abcde', '+pqrst'))
" something in common
call assert_equal(3, gitgutter#diff_highlight#common_prefix('-abcde', '+abcpq'))
" everything in common
call assert_equal(5, gitgutter#diff_highlight#common_prefix('-abcde', '+abcde'))
" different lengths
call assert_equal(2, gitgutter#diff_highlight#common_prefix('-abcde', '+abx'))
call assert_equal(2, gitgutter#diff_highlight#common_prefix('-abx', '+abcde'))
endfunction
function Test_common_suffix()
" nothing in common
call assert_equal([6,6], gitgutter#diff_highlight#common_suffix('-abcde', '+pqrst', 0))
" something in common
call assert_equal([3,3], gitgutter#diff_highlight#common_suffix('-abcde', '+pqcde', 0))
" everything in common
call assert_equal([5,5], gitgutter#diff_highlight#common_suffix('-abcde', '+abcde', 5))
" different lengths
call assert_equal([4,2], gitgutter#diff_highlight#common_suffix('-abcde', '+xde', 0))
call assert_equal([2,4], gitgutter#diff_highlight#common_suffix('-xde', '+abcde', 0))
endfunction
function Test_diff_highlight()
" Ignores mismatched number of added and removed lines.
call assert_equal([], gitgutter#diff_highlight#process(['-foo']))
call assert_equal([], gitgutter#diff_highlight#process(['+foo']))
call assert_equal([], gitgutter#diff_highlight#process(['-foo','-bar','+baz']))
" change in middle
let hunk = ['-foo bar baz', '+foo (bar) baz']
let expected = [[1, '-', 6, 8], [2, '+', 6, 10]]
call assert_equal(expected, gitgutter#diff_highlight#process(hunk))
" change at start
let hunk = ['-foo bar baz', '+(foo) bar baz']
let expected = [[1, '-', 2, 4], [2, '+', 2, 6]]
call assert_equal(expected, gitgutter#diff_highlight#process(hunk))
" change at end
let hunk = ['-foo bar baz', '+foo bar (baz)']
let expected = [[1, '-', 10, 12], [2, '+', 10, 14]]
call assert_equal(expected, gitgutter#diff_highlight#process(hunk))
" removed in middle
let hunk = ['-foo bar baz', '+foo baz']
let expected = [[1, '-', 8, 11]]
call assert_equal(expected, gitgutter#diff_highlight#process(hunk))
" added in middle
let hunk = ['-foo baz', '+foo bar baz']
let expected = [[2, '+', 8, 11]]
call assert_equal(expected, gitgutter#diff_highlight#process(hunk))
endfunction