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. * Never saves the buffer.
* Quick jumping between blocks of changed lines ("hunks"). * Quick jumping between blocks of changed lines ("hunks").
* Stage/undo/preview individual hunks. * Stage/undo/preview individual hunks.
* Previews highlight intra-line changes.
* Stage partial hunks. * Stage partial hunks.
* Provides a hunk text object. * Provides a hunk text object.
* Diffs against index (default) or any commit. * 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 GitGutterChangeLineNr CursorLineNr
highlight default link GitGutterDeleteLineNr CursorLineNr highlight default link GitGutterDeleteLineNr CursorLineNr
highlight default link GitGutterChangeDeleteLineNr CursorLineNr highlight default link GitGutterChangeDeleteLineNr CursorLineNr
" Highlights used intra line.
highlight GitGutterAddIntraLine gui=reverse
highlight GitGutterDeleteIntraLine gui=reverse
endfunction endfunction
function! gitgutter#highlight#define_signs() abort 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, [])
call nvim_buf_set_lines(winbufnr(s:winid), 0, -1, v:false, a:body) 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) 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]) call nvim_win_set_cursor(s:winid, [1,0])
endif endif
if exists('*popup_create') if exists('*popup_create')
call popup_settext(s:winid, a:body) call popup_settext(s:winid, a:body)
" TODO add intra line highlights
endif endif
else else
@@ -472,6 +481,13 @@ function! s:populate_hunk_preview_window(header, body)
%delete _ %delete _
call setline(1, a:body) call setline(1, a:body)
setlocal nomodified 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 1
endif endif
endfunction endfunction

View File

@@ -60,6 +60,9 @@ function SetUp()
execute ':cd' s:test_repo execute ':cd' s:test_repo
edit! fixture.txt edit! fixture.txt
call gitgutter#sign#reset() call gitgutter#sign#reset()
" FIXME why won't vim autoload the file?
execute 'source' '../../autoload/gitgutter/diff_highlight.vim'
endfunction endfunction
function TearDown() function TearDown()
@@ -910,3 +913,62 @@ function Test_quickfix()
call s:assert_list_of_dicts(expected, getqflist()) call s:assert_list_of_dicts(expected, getqflist())
endfunction 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