2019-11-16 10:28:42 -05:00
|
|
|
let s:winid = 0
|
2020-04-25 21:56:16 -04:00
|
|
|
let s:preview_bufnr = 0
|
|
|
|
let s:nomodeline = (v:version > 703 || (v:version == 703 && has('patch442'))) ? '<nomodeline>' : ''
|
2019-11-16 10:28:42 -05:00
|
|
|
|
2018-03-31 10:56:26 -04:00
|
|
|
function! gitgutter#hunk#set_hunks(bufnr, hunks) abort
|
|
|
|
call gitgutter#utility#setbufvar(a:bufnr, 'hunks', a:hunks)
|
|
|
|
call s:reset_summary(a:bufnr)
|
2016-11-22 03:36:31 -05:00
|
|
|
endfunction
|
|
|
|
|
2018-03-31 10:56:26 -04:00
|
|
|
function! gitgutter#hunk#hunks(bufnr) abort
|
|
|
|
return gitgutter#utility#getbufvar(a:bufnr, 'hunks', [])
|
2017-04-01 07:22:06 -04:00
|
|
|
endfunction
|
|
|
|
|
2018-03-31 10:56:26 -04:00
|
|
|
function! gitgutter#hunk#reset(bufnr) abort
|
|
|
|
call gitgutter#utility#setbufvar(a:bufnr, 'hunks', [])
|
|
|
|
call s:reset_summary(a:bufnr)
|
2016-11-22 03:36:31 -05:00
|
|
|
endfunction
|
|
|
|
|
2017-04-01 07:22:06 -04:00
|
|
|
|
2016-11-22 03:36:31 -05:00
|
|
|
function! gitgutter#hunk#summary(bufnr) abort
|
2017-04-01 07:22:06 -04:00
|
|
|
return gitgutter#utility#getbufvar(a:bufnr, 'summary', [0,0,0])
|
2016-11-22 03:36:31 -05:00
|
|
|
endfunction
|
|
|
|
|
2018-03-31 10:56:26 -04:00
|
|
|
function! s:reset_summary(bufnr) abort
|
|
|
|
call gitgutter#utility#setbufvar(a:bufnr, 'summary', [0,0,0])
|
2016-11-22 03:36:31 -05:00
|
|
|
endfunction
|
|
|
|
|
2018-03-31 10:56:26 -04:00
|
|
|
function! gitgutter#hunk#increment_lines_added(bufnr, count) abort
|
|
|
|
let summary = gitgutter#hunk#summary(a:bufnr)
|
2016-11-22 03:36:31 -05:00
|
|
|
let summary[0] += a:count
|
2018-03-31 10:56:26 -04:00
|
|
|
call gitgutter#utility#setbufvar(a:bufnr, 'summary', summary)
|
2016-11-22 03:36:31 -05:00
|
|
|
endfunction
|
|
|
|
|
2018-03-31 10:56:26 -04:00
|
|
|
function! gitgutter#hunk#increment_lines_modified(bufnr, count) abort
|
|
|
|
let summary = gitgutter#hunk#summary(a:bufnr)
|
2016-11-22 03:36:31 -05:00
|
|
|
let summary[1] += a:count
|
2018-03-31 10:56:26 -04:00
|
|
|
call gitgutter#utility#setbufvar(a:bufnr, 'summary', summary)
|
2016-11-22 03:36:31 -05:00
|
|
|
endfunction
|
|
|
|
|
2018-03-31 10:56:26 -04:00
|
|
|
function! gitgutter#hunk#increment_lines_removed(bufnr, count) abort
|
|
|
|
let summary = gitgutter#hunk#summary(a:bufnr)
|
2016-11-22 03:36:31 -05:00
|
|
|
let summary[2] += a:count
|
2018-03-31 10:56:26 -04:00
|
|
|
call gitgutter#utility#setbufvar(a:bufnr, 'summary', summary)
|
2016-11-22 03:36:31 -05:00
|
|
|
endfunction
|
|
|
|
|
2017-04-01 07:22:06 -04:00
|
|
|
|
2016-11-22 03:36:31 -05:00
|
|
|
function! gitgutter#hunk#next_hunk(count) abort
|
2018-03-31 10:56:26 -04:00
|
|
|
let bufnr = bufnr('')
|
2020-12-04 16:15:32 -05:00
|
|
|
if !gitgutter#utility#is_active(bufnr) | return | endif
|
|
|
|
|
|
|
|
let hunks = gitgutter#hunk#hunks(bufnr)
|
|
|
|
if empty(hunks)
|
|
|
|
call gitgutter#utility#warn('No hunks in file')
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
let current_line = line('.')
|
|
|
|
let hunk_count = 0
|
|
|
|
for hunk in hunks
|
|
|
|
if hunk[2] > current_line
|
|
|
|
let hunk_count += 1
|
|
|
|
if hunk_count == a:count
|
|
|
|
execute 'normal!' hunk[2] . 'Gzv'
|
|
|
|
if g:gitgutter_show_msg_on_hunk_jumping
|
|
|
|
redraw | echo printf('Hunk %d of %d', index(hunks, hunk) + 1, len(hunks))
|
2016-11-22 03:36:31 -05:00
|
|
|
endif
|
2021-06-23 05:57:12 -04:00
|
|
|
if gitgutter#hunk#is_preview_window_open()
|
2021-05-05 04:25:00 -04:00
|
|
|
call gitgutter#hunk#preview()
|
|
|
|
endif
|
2020-12-04 16:15:32 -05:00
|
|
|
return
|
2016-11-22 03:36:31 -05:00
|
|
|
endif
|
2020-12-04 16:15:32 -05:00
|
|
|
endif
|
|
|
|
endfor
|
|
|
|
call gitgutter#utility#warn('No more hunks')
|
2016-11-22 03:36:31 -05:00
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! gitgutter#hunk#prev_hunk(count) abort
|
2018-03-31 10:56:26 -04:00
|
|
|
let bufnr = bufnr('')
|
2020-12-04 16:15:32 -05:00
|
|
|
if !gitgutter#utility#is_active(bufnr) | return | endif
|
|
|
|
|
|
|
|
let hunks = gitgutter#hunk#hunks(bufnr)
|
|
|
|
if empty(hunks)
|
|
|
|
call gitgutter#utility#warn('No hunks in file')
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
let current_line = line('.')
|
|
|
|
let hunk_count = 0
|
|
|
|
for hunk in reverse(copy(hunks))
|
|
|
|
if hunk[2] < current_line
|
|
|
|
let hunk_count += 1
|
|
|
|
if hunk_count == a:count
|
|
|
|
let target = hunk[2] == 0 ? 1 : hunk[2]
|
|
|
|
execute 'normal!' target . 'Gzv'
|
|
|
|
if g:gitgutter_show_msg_on_hunk_jumping
|
|
|
|
redraw | echo printf('Hunk %d of %d', index(hunks, hunk) + 1, len(hunks))
|
2016-11-22 03:36:31 -05:00
|
|
|
endif
|
2021-06-23 05:57:12 -04:00
|
|
|
if gitgutter#hunk#is_preview_window_open()
|
2021-05-05 04:25:00 -04:00
|
|
|
call gitgutter#hunk#preview()
|
|
|
|
endif
|
2020-12-04 16:15:32 -05:00
|
|
|
return
|
2016-11-22 03:36:31 -05:00
|
|
|
endif
|
2020-12-04 16:15:32 -05:00
|
|
|
endif
|
|
|
|
endfor
|
|
|
|
call gitgutter#utility#warn('No previous hunks')
|
2016-11-22 03:36:31 -05:00
|
|
|
endfunction
|
|
|
|
|
|
|
|
" Returns the hunk the cursor is currently in or an empty list if the cursor
|
|
|
|
" isn't in a hunk.
|
2018-03-31 10:56:26 -04:00
|
|
|
function! s:current_hunk() abort
|
|
|
|
let bufnr = bufnr('')
|
2016-11-22 03:36:31 -05:00
|
|
|
let current_hunk = []
|
|
|
|
|
2018-03-31 10:56:26 -04:00
|
|
|
for hunk in gitgutter#hunk#hunks(bufnr)
|
2016-11-22 03:36:31 -05:00
|
|
|
if gitgutter#hunk#cursor_in_hunk(hunk)
|
|
|
|
let current_hunk = hunk
|
|
|
|
break
|
|
|
|
endif
|
|
|
|
endfor
|
|
|
|
|
|
|
|
return current_hunk
|
|
|
|
endfunction
|
|
|
|
|
2018-11-01 06:03:42 -04:00
|
|
|
" Returns truthy if the cursor is in two hunks (which can only happen if the
|
|
|
|
" cursor is on the first line and lines above have been deleted and lines
|
|
|
|
" immediately below have been deleted) or falsey otherwise.
|
|
|
|
function! s:cursor_in_two_hunks()
|
|
|
|
let hunks = gitgutter#hunk#hunks(bufnr(''))
|
|
|
|
|
|
|
|
if line('.') == 1 && len(hunks) > 1 && hunks[0][2:3] == [0, 0] && hunks[1][2:3] == [1, 0]
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
|
|
|
|
return 0
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
" A line can be in 0 or 1 hunks, with the following exception: when the first
|
|
|
|
" line(s) of a file has been deleted, and the new second line (and
|
|
|
|
" optionally below) has been deleted, the new first line is in two hunks.
|
2016-11-22 03:36:31 -05:00
|
|
|
function! gitgutter#hunk#cursor_in_hunk(hunk) abort
|
|
|
|
let current_line = line('.')
|
|
|
|
|
|
|
|
if current_line == 1 && a:hunk[2] == 0
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
|
|
|
|
if current_line >= a:hunk[2] && current_line < a:hunk[2] + (a:hunk[3] == 0 ? 1 : a:hunk[3])
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
|
|
|
|
return 0
|
|
|
|
endfunction
|
|
|
|
|
2019-03-08 06:04:56 -05:00
|
|
|
|
|
|
|
function! gitgutter#hunk#in_hunk(lnum)
|
|
|
|
" Hunks are sorted in the order they appear in the buffer.
|
|
|
|
for hunk in gitgutter#hunk#hunks(bufnr(''))
|
|
|
|
" if in a hunk on first line of buffer
|
|
|
|
if a:lnum == 1 && hunk[2] == 0
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
|
|
|
|
" if in a hunk generally
|
|
|
|
if a:lnum >= hunk[2] && a:lnum < hunk[2] + (hunk[3] == 0 ? 1 : hunk[3])
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
|
|
|
|
" if hunk starts after the given line
|
|
|
|
if a:lnum < hunk[2]
|
|
|
|
return 0
|
|
|
|
endif
|
|
|
|
endfor
|
|
|
|
|
|
|
|
return 0
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
2016-11-22 03:36:31 -05:00
|
|
|
function! gitgutter#hunk#text_object(inner) abort
|
2018-03-31 10:56:26 -04:00
|
|
|
let hunk = s:current_hunk()
|
2016-11-22 03:36:31 -05:00
|
|
|
|
|
|
|
if empty(hunk)
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
let [first_line, last_line] = [hunk[2], hunk[2] + hunk[3] - 1]
|
|
|
|
|
|
|
|
if ! a:inner
|
|
|
|
let lnum = last_line
|
|
|
|
let eof = line('$')
|
|
|
|
while lnum < eof && empty(getline(lnum + 1))
|
|
|
|
let lnum +=1
|
|
|
|
endwhile
|
|
|
|
let last_line = lnum
|
|
|
|
endif
|
|
|
|
|
|
|
|
execute 'normal! 'first_line.'GV'.last_line.'G'
|
|
|
|
endfunction
|
2018-03-31 10:56:26 -04:00
|
|
|
|
|
|
|
|
2019-08-22 11:36:17 -04:00
|
|
|
function! gitgutter#hunk#stage(...) abort
|
2020-04-25 21:56:16 -04:00
|
|
|
if !s:in_hunk_preview_window() && !gitgutter#utility#has_repo_path(bufnr('')) | return | endif
|
|
|
|
|
2019-08-22 11:36:17 -04:00
|
|
|
if a:0 && (a:1 != 1 || a:2 != line('$'))
|
|
|
|
call s:hunk_op(function('s:stage'), a:1, a:2)
|
|
|
|
else
|
|
|
|
call s:hunk_op(function('s:stage'))
|
|
|
|
endif
|
2019-11-16 10:28:42 -05:00
|
|
|
silent! call repeat#set("\<Plug>(GitGutterStageHunk)", -1)
|
2018-03-31 10:56:26 -04:00
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! gitgutter#hunk#undo() abort
|
2020-04-25 21:56:16 -04:00
|
|
|
if !gitgutter#utility#has_repo_path(bufnr('')) | return | endif
|
|
|
|
|
2018-03-31 10:56:26 -04:00
|
|
|
call s:hunk_op(function('s:undo'))
|
2019-11-16 10:28:42 -05:00
|
|
|
silent! call repeat#set("\<Plug>(GitGutterUndoHunk)", -1)
|
2018-03-31 10:56:26 -04:00
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! gitgutter#hunk#preview() abort
|
2020-04-25 21:56:16 -04:00
|
|
|
if !gitgutter#utility#has_repo_path(bufnr('')) | return | endif
|
|
|
|
|
2018-03-31 10:56:26 -04:00
|
|
|
call s:hunk_op(function('s:preview'))
|
2019-11-16 10:28:42 -05:00
|
|
|
silent! call repeat#set("\<Plug>(GitGutterPreviewHunk)", -1)
|
2018-03-31 10:56:26 -04:00
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
2019-08-22 11:36:17 -04:00
|
|
|
function! s:hunk_op(op, ...)
|
2018-03-31 10:56:26 -04:00
|
|
|
let bufnr = bufnr('')
|
|
|
|
|
2019-11-16 10:28:42 -05:00
|
|
|
if s:in_hunk_preview_window()
|
2019-08-22 11:36:17 -04:00
|
|
|
if string(a:op) =~ '_stage'
|
|
|
|
" combine hunk-body in preview window with updated hunk-header
|
|
|
|
let hunk_body = getline(1, '$')
|
|
|
|
|
|
|
|
let [removed, added] = [0, 0]
|
|
|
|
for line in hunk_body
|
|
|
|
if line[0] == '-'
|
|
|
|
let removed += 1
|
|
|
|
elseif line[0] == '+'
|
|
|
|
let added += 1
|
|
|
|
endif
|
|
|
|
endfor
|
|
|
|
|
|
|
|
let hunk_header = b:hunk_header
|
|
|
|
" from count
|
|
|
|
let hunk_header[4] = substitute(hunk_header[4], '\(-\d\+\)\(,\d\+\)\?', '\=submatch(1).",".removed', '')
|
|
|
|
" to count
|
|
|
|
let hunk_header[4] = substitute(hunk_header[4], '\(+\d\+\)\(,\d\+\)\?', '\=submatch(1).",".added', '')
|
|
|
|
|
|
|
|
let hunk_diff = join(hunk_header + hunk_body, "\n")."\n"
|
|
|
|
|
2023-08-20 10:33:32 -04:00
|
|
|
if &previewwindow
|
|
|
|
call s:goto_original_window()
|
|
|
|
endif
|
2021-06-23 05:57:12 -04:00
|
|
|
call gitgutter#hunk#close_hunk_preview_window()
|
2019-08-22 11:36:17 -04:00
|
|
|
call s:stage(hunk_diff)
|
|
|
|
endif
|
|
|
|
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
2018-03-31 10:56:26 -04:00
|
|
|
if gitgutter#utility#is_active(bufnr)
|
|
|
|
" Get a (synchronous) diff.
|
|
|
|
let [async, g:gitgutter_async] = [g:gitgutter_async, 0]
|
2019-11-16 10:28:42 -05:00
|
|
|
let diff = gitgutter#diff#run_diff(bufnr, g:gitgutter_diff_relative_to, 1)
|
2018-03-31 10:56:26 -04:00
|
|
|
let g:gitgutter_async = async
|
|
|
|
|
|
|
|
call gitgutter#hunk#set_hunks(bufnr, gitgutter#diff#parse_diff(diff))
|
2021-05-05 04:25:00 -04:00
|
|
|
call gitgutter#diff#process_hunks(bufnr, gitgutter#hunk#hunks(bufnr)) " so the hunk summary is updated
|
2018-03-31 10:56:26 -04:00
|
|
|
|
|
|
|
if empty(s:current_hunk())
|
2021-05-05 04:25:00 -04:00
|
|
|
call gitgutter#utility#warn('Cursor is not in a hunk')
|
2018-11-01 06:03:42 -04:00
|
|
|
elseif s:cursor_in_two_hunks()
|
|
|
|
let choice = input('Choose hunk: upper or lower (u/l)? ')
|
|
|
|
" Clear input
|
|
|
|
normal! :<ESC>
|
|
|
|
if choice =~ 'u'
|
|
|
|
call a:op(gitgutter#diff#hunk_diff(bufnr, diff, 0))
|
|
|
|
elseif choice =~ 'l'
|
|
|
|
call a:op(gitgutter#diff#hunk_diff(bufnr, diff, 1))
|
|
|
|
else
|
2021-05-05 04:25:00 -04:00
|
|
|
call gitgutter#utility#warn('Did not recognise your choice')
|
2018-11-01 06:03:42 -04:00
|
|
|
endif
|
2018-03-31 10:56:26 -04:00
|
|
|
else
|
2019-08-22 11:36:17 -04:00
|
|
|
let hunk_diff = gitgutter#diff#hunk_diff(bufnr, diff)
|
|
|
|
|
|
|
|
if a:0
|
|
|
|
let hunk_first_line = s:current_hunk()[2]
|
|
|
|
let hunk_diff = s:part_of_diff(hunk_diff, a:1-hunk_first_line, a:2-hunk_first_line)
|
|
|
|
endif
|
|
|
|
|
|
|
|
call a:op(hunk_diff)
|
2018-03-31 10:56:26 -04:00
|
|
|
endif
|
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
|
|
function! s:stage(hunk_diff)
|
|
|
|
let bufnr = bufnr('')
|
2023-07-15 06:43:27 -04:00
|
|
|
|
|
|
|
if gitgutter#utility#clean_smudge_filter_applies(bufnr)
|
|
|
|
let choice = input('File uses clean/smudge filter. Stage entire file (y/n)? ')
|
|
|
|
normal! :<ESC>
|
|
|
|
if choice =~ 'y'
|
|
|
|
" We are about to add the file to the index so write the buffer to
|
|
|
|
" ensure the file on disk matches it (the buffer).
|
|
|
|
write
|
|
|
|
let path = gitgutter#utility#repo_path(bufnr, 1)
|
|
|
|
" Add file to index.
|
|
|
|
let cmd = gitgutter#utility#cd_cmd(bufnr,
|
|
|
|
\ gitgutter#git().' add '.
|
|
|
|
\ gitgutter#utility#shellescape(gitgutter#utility#filename(bufnr)))
|
|
|
|
call gitgutter#utility#system(cmd)
|
|
|
|
else
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
else
|
|
|
|
let diff = s:adjust_header(bufnr, a:hunk_diff)
|
|
|
|
" Apply patch to index.
|
|
|
|
call gitgutter#utility#system(
|
|
|
|
\ gitgutter#utility#cd_cmd(bufnr, gitgutter#git().' apply --cached --unidiff-zero - '),
|
|
|
|
\ diff)
|
|
|
|
endif
|
|
|
|
|
2019-08-22 11:36:17 -04:00
|
|
|
if v:shell_error
|
2021-05-05 04:25:00 -04:00
|
|
|
call gitgutter#utility#warn('Patch does not apply')
|
2020-04-25 21:56:16 -04:00
|
|
|
else
|
|
|
|
if exists('#User#GitGutterStage')
|
|
|
|
execute 'doautocmd' s:nomodeline 'User GitGutterStage'
|
|
|
|
endif
|
2019-08-22 11:36:17 -04:00
|
|
|
endif
|
2018-03-31 10:56:26 -04:00
|
|
|
|
|
|
|
" Refresh gitgutter's view of buffer.
|
|
|
|
call gitgutter#process_buffer(bufnr, 1)
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
|
|
function! s:undo(hunk_diff)
|
|
|
|
" Apply reverse patch to buffer.
|
|
|
|
let hunk = gitgutter#diff#parse_hunk(split(a:hunk_diff, '\n')[4])
|
2019-11-16 10:28:42 -05:00
|
|
|
let lines = map(split(a:hunk_diff, '\r\?\n')[5:], 'v:val[1:]')
|
2018-03-31 10:56:26 -04:00
|
|
|
let lnum = hunk[2]
|
|
|
|
let added_only = hunk[1] == 0 && hunk[3] > 0
|
|
|
|
let removed_only = hunk[1] > 0 && hunk[3] == 0
|
|
|
|
|
|
|
|
if removed_only
|
|
|
|
call append(lnum, lines)
|
|
|
|
elseif added_only
|
2019-08-22 11:36:17 -04:00
|
|
|
execute lnum .','. (lnum+len(lines)-1) .'d _'
|
2018-03-31 10:56:26 -04:00
|
|
|
else
|
|
|
|
call append(lnum-1, lines[0:hunk[1]])
|
2019-08-22 11:36:17 -04:00
|
|
|
execute (lnum+hunk[1]) .','. (lnum+hunk[1]+hunk[3]) .'d _'
|
2018-03-31 10:56:26 -04:00
|
|
|
endif
|
2023-04-01 16:48:04 -04:00
|
|
|
|
|
|
|
" Refresh gitgutter's view of buffer.
|
|
|
|
call gitgutter#process_buffer(bufnr(''), 1)
|
2018-03-31 10:56:26 -04:00
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
|
|
function! s:preview(hunk_diff)
|
2019-11-16 10:28:42 -05:00
|
|
|
let lines = split(a:hunk_diff, '\r\?\n')
|
2019-08-22 11:36:17 -04:00
|
|
|
let header = lines[0:4]
|
|
|
|
let body = lines[5:]
|
|
|
|
|
2019-11-16 10:28:42 -05:00
|
|
|
call s:open_hunk_preview_window()
|
|
|
|
call s:populate_hunk_preview_window(header, body)
|
|
|
|
call s:enable_staging_from_hunk_preview_window()
|
|
|
|
if &previewwindow
|
|
|
|
call s:goto_original_window()
|
2018-03-31 10:56:26 -04:00
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
2019-08-22 11:36:17 -04:00
|
|
|
" Returns a new hunk diff using the specified lines from the given one.
|
|
|
|
" Assumes all lines are additions.
|
|
|
|
" a:first, a:last - 0-based indexes into the body of the hunk.
|
|
|
|
function! s:part_of_diff(hunk_diff, first, last)
|
|
|
|
let diff_lines = split(a:hunk_diff, '\n', 1)
|
|
|
|
|
|
|
|
" adjust 'to' line count in header
|
|
|
|
let diff_lines[4] = substitute(diff_lines[4], '\(+\d\+\)\(,\d\+\)\?', '\=submatch(1).",".(a:last-a:first+1)', '')
|
|
|
|
|
|
|
|
return join(diff_lines[0:4] + diff_lines[5+a:first:5+a:last], "\n")."\n"
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
2018-03-31 10:56:26 -04:00
|
|
|
function! s:adjust_header(bufnr, hunk_diff)
|
|
|
|
let filepath = gitgutter#utility#repo_path(a:bufnr, 0)
|
|
|
|
return s:adjust_hunk_summary(s:fix_file_references(filepath, a:hunk_diff))
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
|
|
" Replaces references to temp files with the actual file.
|
|
|
|
function! s:fix_file_references(filepath, hunk_diff)
|
|
|
|
let lines = split(a:hunk_diff, '\n')
|
|
|
|
|
|
|
|
let left_prefix = matchstr(lines[2], '[abciow12]').'/'
|
|
|
|
let right_prefix = matchstr(lines[3], '[abciow12]').'/'
|
|
|
|
let quote = lines[0][11] == '"' ? '"' : ''
|
|
|
|
|
|
|
|
let left_file = quote.left_prefix.a:filepath.quote
|
|
|
|
let right_file = quote.right_prefix.a:filepath.quote
|
|
|
|
|
|
|
|
let lines[0] = 'diff --git '.left_file.' '.right_file
|
|
|
|
let lines[2] = '--- '.left_file
|
|
|
|
let lines[3] = '+++ '.right_file
|
|
|
|
|
|
|
|
return join(lines, "\n")."\n"
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
|
|
function! s:adjust_hunk_summary(hunk_diff) abort
|
|
|
|
let line_adjustment = s:line_adjustment_for_current_hunk()
|
|
|
|
let diff = split(a:hunk_diff, '\n', 1)
|
2019-08-22 11:36:17 -04:00
|
|
|
let diff[4] = substitute(diff[4], '+\zs\(\d\+\)', '\=submatch(1)+line_adjustment', '')
|
2018-03-31 10:56:26 -04:00
|
|
|
return join(diff, "\n")
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
|
|
" Returns the number of lines the current hunk is offset from where it would
|
|
|
|
" be if any changes above it in the file didn't exist.
|
|
|
|
function! s:line_adjustment_for_current_hunk() abort
|
|
|
|
let bufnr = bufnr('')
|
|
|
|
let adj = 0
|
|
|
|
for hunk in gitgutter#hunk#hunks(bufnr)
|
|
|
|
if gitgutter#hunk#cursor_in_hunk(hunk)
|
|
|
|
break
|
|
|
|
else
|
|
|
|
let adj += hunk[1] - hunk[3]
|
|
|
|
endif
|
|
|
|
endfor
|
|
|
|
return adj
|
|
|
|
endfunction
|
|
|
|
|
2019-11-16 10:28:42 -05:00
|
|
|
|
|
|
|
function! s:in_hunk_preview_window()
|
|
|
|
if g:gitgutter_preview_win_floating
|
|
|
|
return win_id2win(s:winid) == winnr()
|
|
|
|
else
|
|
|
|
return &previewwindow
|
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
|
|
" Floating window: does not move cursor to floating window.
|
|
|
|
" Preview window: moves cursor to preview window.
|
|
|
|
function! s:open_hunk_preview_window()
|
2023-07-15 06:43:27 -04:00
|
|
|
let source_wrap = &wrap
|
|
|
|
let source_window = winnr()
|
|
|
|
|
2019-11-16 10:28:42 -05:00
|
|
|
if g:gitgutter_preview_win_floating
|
|
|
|
if exists('*nvim_open_win')
|
2021-06-23 05:57:12 -04:00
|
|
|
call gitgutter#hunk#close_hunk_preview_window()
|
2019-11-16 10:28:42 -05:00
|
|
|
|
|
|
|
let buf = nvim_create_buf(v:false, v:false)
|
|
|
|
" Set default width and height for now.
|
2022-08-08 09:45:56 -04:00
|
|
|
let s:winid = nvim_open_win(buf, v:false, g:gitgutter_floating_window_options)
|
2023-07-15 06:43:27 -04:00
|
|
|
call nvim_win_set_option(s:winid, 'wrap', source_wrap ? v:true : v:false)
|
2019-11-16 10:28:42 -05:00
|
|
|
call nvim_buf_set_option(buf, 'filetype', 'diff')
|
|
|
|
call nvim_buf_set_option(buf, 'buftype', 'acwrite')
|
|
|
|
call nvim_buf_set_option(buf, 'bufhidden', 'delete')
|
|
|
|
call nvim_buf_set_option(buf, 'swapfile', v:false)
|
|
|
|
call nvim_buf_set_name(buf, 'gitgutter://hunk-preview')
|
|
|
|
|
2021-05-05 04:25:00 -04:00
|
|
|
if g:gitgutter_close_preview_on_escape
|
2023-08-20 10:33:32 -04:00
|
|
|
let winnr = nvim_win_get_number(s:winid)
|
|
|
|
execute winnr.'wincmd w'
|
2021-06-23 05:57:12 -04:00
|
|
|
nnoremap <buffer> <silent> <Esc> :<C-U>call gitgutter#hunk#close_hunk_preview_window()<CR>
|
2023-08-20 10:33:32 -04:00
|
|
|
wincmd w
|
2021-05-05 04:25:00 -04:00
|
|
|
endif
|
2019-11-16 10:28:42 -05:00
|
|
|
|
2023-08-20 10:33:32 -04:00
|
|
|
" Assumes cursor is in original window.
|
|
|
|
autocmd CursorMoved,TabLeave <buffer> ++once call gitgutter#hunk#close_hunk_preview_window()
|
|
|
|
|
2019-11-16 10:28:42 -05:00
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
if exists('*popup_create')
|
2021-05-05 04:25:00 -04:00
|
|
|
if g:gitgutter_close_preview_on_escape
|
2022-08-08 09:45:56 -04:00
|
|
|
let g:gitgutter_floating_window_options.filter = function('s:close_popup_on_escape')
|
2021-05-05 04:25:00 -04:00
|
|
|
endif
|
|
|
|
|
2022-08-08 09:45:56 -04:00
|
|
|
let s:winid = popup_create('', g:gitgutter_floating_window_options)
|
2019-11-16 10:28:42 -05:00
|
|
|
|
|
|
|
call setbufvar(winbufnr(s:winid), '&filetype', 'diff')
|
2023-07-15 06:43:27 -04:00
|
|
|
call setwinvar(s:winid, '&wrap', source_wrap)
|
2019-11-16 10:28:42 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
endif
|
|
|
|
|
2022-08-08 09:45:56 -04:00
|
|
|
if exists('&previewpopup')
|
|
|
|
let [previewpopup, &previewpopup] = [&previewpopup, '']
|
|
|
|
endif
|
|
|
|
|
2021-05-05 04:25:00 -04:00
|
|
|
" Specifying where to open the preview window can lead to the cursor going
|
|
|
|
" to an unexpected window when the preview window is closed (#769).
|
|
|
|
silent! noautocmd execute g:gitgutter_preview_win_location 'pedit gitgutter://hunk-preview'
|
2019-11-16 10:28:42 -05:00
|
|
|
silent! wincmd P
|
2021-05-05 04:25:00 -04:00
|
|
|
setlocal statusline=%{''}
|
|
|
|
doautocmd WinEnter
|
2020-06-21 11:50:44 -04:00
|
|
|
if exists('*win_getid')
|
|
|
|
let s:winid = win_getid()
|
|
|
|
else
|
|
|
|
let s:preview_bufnr = bufnr('')
|
|
|
|
endif
|
|
|
|
setlocal filetype=diff buftype=acwrite bufhidden=delete
|
2023-07-15 06:43:27 -04:00
|
|
|
let &l:wrap = source_wrap
|
|
|
|
let b:source_window = source_window
|
2020-06-21 11:50:44 -04:00
|
|
|
" Reset some defaults in case someone else has changed them.
|
|
|
|
setlocal noreadonly modifiable noswapfile
|
2020-12-04 16:15:32 -05:00
|
|
|
if g:gitgutter_close_preview_on_escape
|
2021-05-05 04:25:00 -04:00
|
|
|
" Ensure cursor goes to the expected window.
|
2023-07-15 06:43:27 -04:00
|
|
|
nnoremap <buffer> <silent> <Esc> :<C-U>execute b:source_window . "wincmd w"<Bar>pclose<CR>
|
2020-12-04 16:15:32 -05:00
|
|
|
endif
|
2022-08-08 09:45:56 -04:00
|
|
|
|
|
|
|
if exists('&previewpopup')
|
|
|
|
let &previewpopup=previewpopup
|
|
|
|
endif
|
2019-11-16 10:28:42 -05:00
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
2021-05-05 04:25:00 -04:00
|
|
|
function! s:close_popup_on_escape(winid, key)
|
|
|
|
if a:key == "\<Esc>"
|
|
|
|
call popup_close(a:winid)
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
return 0
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
2019-11-16 10:28:42 -05:00
|
|
|
" Floating window: does not care where cursor is.
|
|
|
|
" Preview window: assumes cursor is in preview window.
|
|
|
|
function! s:populate_hunk_preview_window(header, body)
|
|
|
|
if g:gitgutter_preview_win_floating
|
|
|
|
if exists('*nvim_open_win')
|
|
|
|
" Assumes cursor is not in previewing window.
|
|
|
|
call nvim_buf_set_var(winbufnr(s:winid), 'hunk_header', a:header)
|
|
|
|
|
2020-06-21 11:50:44 -04:00
|
|
|
let [_scrolloff, &scrolloff] = [&scrolloff, 0]
|
|
|
|
|
2022-11-20 06:21:23 -05:00
|
|
|
let [width, height] = s:screen_lines(a:body)
|
|
|
|
let height = min([height, g:gitgutter_floating_window_options.height])
|
2019-11-16 10:28:42 -05:00
|
|
|
call nvim_win_set_width(s:winid, width)
|
|
|
|
call nvim_win_set_height(s:winid, height)
|
|
|
|
|
2020-06-21 11:50:44 -04:00
|
|
|
let &scrolloff=_scrolloff
|
|
|
|
|
2019-11-16 10:28:42 -05:00
|
|
|
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)
|
|
|
|
|
|
|
|
for region in gitgutter#diff_highlight#process(a:body)
|
|
|
|
let group = region[1] == '+' ? 'GitGutterAddIntraLine' : 'GitGutterDeleteIntraLine'
|
|
|
|
call win_execute(s:winid, "call matchaddpos('".group."', [[".region[0].", ".region[2].", ".(region[3]-region[2]+1)."]])")
|
|
|
|
endfor
|
|
|
|
endif
|
|
|
|
|
|
|
|
else
|
|
|
|
let b:hunk_header = a:header
|
|
|
|
|
|
|
|
%delete _
|
|
|
|
call setline(1, a:body)
|
|
|
|
setlocal nomodified
|
|
|
|
|
2022-11-20 06:21:23 -05:00
|
|
|
let [_, height] = s:screen_lines(a:body)
|
2020-06-21 11:50:44 -04:00
|
|
|
execute 'resize' height
|
|
|
|
1
|
|
|
|
|
2019-11-16 10:28:42 -05:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2022-11-20 06:21:23 -05:00
|
|
|
" Calculates the number of columns and the number of screen lines the given
|
|
|
|
" array of lines will take up, taking account of wrapping.
|
|
|
|
function! s:screen_lines(lines)
|
|
|
|
let [_virtualedit, &virtualedit]=[&virtualedit, 'all']
|
|
|
|
let cursor = getcurpos()
|
2023-04-01 16:48:04 -04:00
|
|
|
normal! 0g$
|
2022-11-20 06:21:23 -05:00
|
|
|
let available_width = virtcol('.')
|
|
|
|
call setpos('.', cursor)
|
|
|
|
let &virtualedit=_virtualedit
|
|
|
|
let width = min([max(map(copy(a:lines), 'strdisplaywidth(v:val)')), available_width])
|
|
|
|
|
|
|
|
if exists('*reduce')
|
|
|
|
let height = reduce(a:lines, { acc, val -> acc + strdisplaywidth(val) / width + (strdisplaywidth(val) % width == 0 ? 0 : 1) }, 0)
|
|
|
|
else
|
|
|
|
let height = eval(join(map(copy(a:lines), 'strdisplaywidth(v:val) / width + (strdisplaywidth(v:val) % width == 0 ? 0 : 1)'), '+'))
|
|
|
|
endif
|
|
|
|
|
|
|
|
return [width, height]
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
2019-11-16 10:28:42 -05:00
|
|
|
function! s:enable_staging_from_hunk_preview_window()
|
|
|
|
augroup gitgutter_hunk_preview
|
|
|
|
autocmd!
|
2020-04-25 21:56:16 -04:00
|
|
|
let bufnr = s:winid != 0 ? winbufnr(s:winid) : s:preview_bufnr
|
|
|
|
execute 'autocmd BufWriteCmd <buffer='.bufnr.'> GitGutterStageHunk'
|
2019-11-16 10:28:42 -05:00
|
|
|
augroup END
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
|
|
|
function! s:goto_original_window()
|
2023-07-15 06:43:27 -04:00
|
|
|
noautocmd execute b:source_window . "wincmd w"
|
2020-04-25 21:56:16 -04:00
|
|
|
doautocmd WinEnter
|
2019-11-16 10:28:42 -05:00
|
|
|
endfunction
|
|
|
|
|
|
|
|
|
2021-06-23 05:57:12 -04:00
|
|
|
function! gitgutter#hunk#close_hunk_preview_window()
|
2020-04-25 21:56:16 -04:00
|
|
|
let bufnr = s:winid != 0 ? winbufnr(s:winid) : s:preview_bufnr
|
|
|
|
call setbufvar(bufnr, '&modified', 0)
|
2019-11-16 10:28:42 -05:00
|
|
|
|
|
|
|
if g:gitgutter_preview_win_floating
|
|
|
|
if win_id2win(s:winid) > 0
|
|
|
|
execute win_id2win(s:winid).'wincmd c'
|
|
|
|
endif
|
|
|
|
else
|
|
|
|
pclose
|
|
|
|
endif
|
|
|
|
|
|
|
|
let s:winid = 0
|
2020-04-25 21:56:16 -04:00
|
|
|
let s:preview_bufnr = 0
|
2019-11-16 10:28:42 -05:00
|
|
|
endfunction
|
2021-05-05 04:25:00 -04:00
|
|
|
|
|
|
|
|
2021-06-23 05:57:12 -04:00
|
|
|
function gitgutter#hunk#is_preview_window_open()
|
|
|
|
if g:gitgutter_preview_win_floating
|
|
|
|
if win_id2win(s:winid) > 0
|
|
|
|
execute win_id2win(s:winid).'wincmd c'
|
2021-05-05 04:25:00 -04:00
|
|
|
endif
|
2021-06-23 05:57:12 -04:00
|
|
|
else
|
|
|
|
for i in range(1, winnr('$'))
|
|
|
|
if getwinvar(i, '&previewwindow')
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
endfor
|
|
|
|
endif
|
2021-05-05 04:25:00 -04:00
|
|
|
return 0
|
|
|
|
endfunction
|