Retool diffing

Support mods, rename to match the built-in :diffsplit, and (perhaps
controversially) move three-way diffing to the bang variant, so that the
default behavior always focuses the other window.

Closes https://github.com/tpope/vim-fugitive/pull/715
This commit is contained in:
Tim Pope 2019-07-07 10:34:04 -04:00
parent e49b9a9303
commit cd78a08543
2 changed files with 73 additions and 50 deletions

View File

@ -76,11 +76,12 @@ function! s:DirCheck(...) abort
endfunction endfunction
function! s:Mods(mods, ...) abort function! s:Mods(mods, ...) abort
let mods = a:mods ==# '<mods>' || empty(a:mods) ? '' : a:mods . ' ' let mods = substitute(a:mods, '\C<mods>', '', '')
if a:0 && mods !~# 'aboveleft\|belowright\|leftabove\|rightbelow\|topleft\|botright\|tab' let mods = mods =~# '\S$' ? a:mods . ' ' : a:mods
if a:0 && mods !~# '\<\%(aboveleft\|belowright\|leftabove\|rightbelow\|topleft\|botright\|tab\)\>'
let mods = a:1 . ' ' . mods let mods = a:1 . ' ' . mods
endif endif
return mods return substitute(mods, '\s\+', ' ', 'g')
endfunction endfunction
function! s:Slash(path) abort function! s:Slash(path) abort
@ -1710,12 +1711,12 @@ function! fugitive#BufReadStatus() abort
exe 'xnoremap <buffer> <silent>' nowait "= :<C-U>execute <SID>StageInline('toggle',line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>" exe 'xnoremap <buffer> <silent>' nowait "= :<C-U>execute <SID>StageInline('toggle',line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>"
exe 'xnoremap <buffer> <silent>' nowait "< :<C-U>execute <SID>StageInline('show', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>" exe 'xnoremap <buffer> <silent>' nowait "< :<C-U>execute <SID>StageInline('show', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>"
exe 'xnoremap <buffer> <silent>' nowait "> :<C-U>execute <SID>StageInline('hide', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>" exe 'xnoremap <buffer> <silent>' nowait "> :<C-U>execute <SID>StageInline('hide', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>"
nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR> nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiffsplit')<CR>
nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR> nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiffsplit')<CR>
nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR> nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>
nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR> nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>
nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR> nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR> nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiffsplit')<CR>
nnoremap <buffer> <silent> J :<C-U>execute <SID>StageNext(v:count1)<CR> nnoremap <buffer> <silent> J :<C-U>execute <SID>StageNext(v:count1)<CR>
nnoremap <buffer> <silent> K :<C-U>execute <SID>StagePrevious(v:count1)<CR> nnoremap <buffer> <silent> K :<C-U>execute <SID>StagePrevious(v:count1)<CR>
nnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR> nnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
@ -3739,9 +3740,9 @@ endfunction
" Section: :Gdiff " Section: :Gdiff
call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#CompleteObject Gdiff :execute s:Diff('',<bang>0,<f-args>)") call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#CompleteObject Gdiffsplit :execute s:Diff(1, <bang>0, '<mods>', <f-args>)")
call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#CompleteObject Gvdiff :execute s:Diff('keepalt vert ',<bang>0,<f-args>)") call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#CompleteObject Gvdiffsplit :execute s:Diff(0, <bang>0, 'vertical <mods>', <f-args>)")
call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#CompleteObject Gsdiff :execute s:Diff('keepalt ',<bang>0,<f-args>)") call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#CompleteObject Ghdiffsplit :execute s:Diff(0, <bang>0, '<mods>', <f-args>)")
augroup fugitive_diff augroup fugitive_diff
autocmd! autocmd!
@ -3768,13 +3769,13 @@ endfunction
function! s:diff_modifier(count) abort function! s:diff_modifier(count) abort
let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+') let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical' if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
return 'keepalt ' return ''
elseif &diffopt =~# 'vertical' elseif &diffopt =~# 'vertical'
return 'keepalt vert ' return 'vertical '
elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc)) elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
return 'keepalt ' return ''
else else
return 'keepalt vert ' return 'vertical '
endif endif
endfunction endfunction
@ -3855,26 +3856,37 @@ function! s:CompareAge(mine, theirs) abort
return my_time < their_time ? -1 : my_time != their_time return my_time < their_time ? -1 : my_time != their_time
endfunction endfunction
function! s:Diff(vert,keepfocus,...) abort function! s:IsConflicted() abort
return !empty(s:TreeChomp('ls-files', '--unmerged', '--', expand('%:p')))
endfunction
function! s:Diff(autodir, keepfocus, mods, ...) abort
if exists(':DiffGitCached') && !a:0
return s:Mods(a:mods) . 'DiffGitCached'
endif
let args = copy(a:000) let args = copy(a:000)
let post = '' let post = ''
if get(args, 0) =~# '^+' if get(args, 0) =~# '^+'
let post = remove(args, 0)[1:-1] let post = remove(args, 0)[1:-1]
endif endif
let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
let commit = s:DirCommitFile(@%)[1] let commit = s:DirCommitFile(@%)[1]
if a:mods =~# '\<tab\>'
let mods = substitute(a:mods, '\<tab\>', '', 'g')
tab split
else
let mods = 'keepalt ' . a:mods
endif
let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p' let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
if exists(':DiffGitCached') if (empty(args) || args[0] ==# ':') && commit =~# '^[0-1]\=$' && a:keepfocus && s:IsConflicted()
return 'DiffGitCached' let mods = (a:autodir ? s:diff_modifier(3) : '') . s:Mods(mods, 'leftabove')
elseif (empty(args) || args[0] ==# ':') && commit =~# '^[0-1]\=$' && !empty(s:TreeChomp('ls-files', '--unmerged', '--', expand('%:p')))
let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
let nr = bufnr('') let nr = bufnr('')
execute 'leftabove '.vert.'split' s:fnameescape(s:Generate(s:Relative(':2:'))) execute mods 'split' s:fnameescape(s:Generate(s:Relative(':2:')))
execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>' execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
let nr2 = bufnr('') let nr2 = bufnr('')
call s:diffthis() call s:diffthis()
exe back exe back
execute 'rightbelow '.vert.'split' s:fnameescape(s:Generate(s:Relative(':3:'))) let mods = substitute(mods, '\Cleftabove\|rightbelow\|aboveleft\|belowright', '\=submatch(0) =~# "f" ? "rightbelow" : "leftabove"', '')
execute mods 'split' s:fnameescape(s:Generate(s:Relative(':3:')))
execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>' execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
let nr3 = bufnr('') let nr3 = bufnr('')
call s:diffthis() call s:diffthis()
@ -3903,8 +3915,13 @@ function! s:Diff(vert,keepfocus,...) abort
if file !~# ':' && file !~# '^/' && s:TreeChomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$' if file !~# ':' && file !~# '^/' && s:TreeChomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
let file = file.s:Relative(':') let file = file.s:Relative(':')
endif endif
elseif len(commit)
let file = s:Relative()
elseif s:IsConflicted()
let file = s:Relative(':1:')
let post = 'echohl WarningMsg|echo "Use :Gdiffsplit! for 3 way diff"|echohl NONE|' . post
else else
let file = empty(commit) ? s:Relative(':0:') : s:Relative() let file = s:Relative(':0:')
endif endif
try try
let spec = s:Generate(file) let spec = s:Generate(file)
@ -3914,10 +3931,12 @@ function! s:Diff(vert,keepfocus,...) abort
endif endif
let w:fugitive_diff_restore = restore let w:fugitive_diff_restore = restore
if s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0 if s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0
execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec) let mods = s:Mods(mods, 'rightbelow')
else else
execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec) let mods = s:Mods(mods, 'leftabove')
endif endif
let mods = (a:autodir ? s:diff_modifier(2) : '') . mods
execute mods 'diffsplit' s:fnameescape(spec)
let &l:readonly = &l:readonly let &l:readonly = &l:readonly
redraw redraw
let w:fugitive_diff_restore = restore let w:fugitive_diff_restore = restore
@ -4789,18 +4808,18 @@ function! s:cfile() abort
let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)') let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)') let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
let dcmd = 'Gdiff! +'.offset let dcmd = 'Gdiffsplit! +'.offset
elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)' elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)') let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)') let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
let dcmd = 'Gdiff!' let dcmd = 'Gdiffsplit!'
elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)' elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
let line = getline(line('.')-1) let line = getline(line('.')-1)
let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)') let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)') let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
let dcmd = 'Gdiff!' let dcmd = 'Gdiffsplit!'
elseif line('$') == 1 && getline('.') =~ '^\x\{40,\}$' elseif line('$') == 1 && getline('.') =~ '^\x\{40,\}$'
let ref = getline('.') let ref = getline('.')

View File

@ -143,25 +143,28 @@ that are part of Git repositories).
:Gwq! [path] Like |:Gwrite|! followed by |:quit|! if the write :Gwq! [path] Like |:Gwrite|! followed by |:quit|! if the write
succeeded. succeeded.
*fugitive-:Gdiff* *fugitive-:Gdiffsplit*
:Gdiff [object] Perform a |vimdiff| against the given file, or if a :Gdiffsplit [object] Perform a |vimdiff| against the given file, or if a
commit is given, the current file in that commit. commit is given, the current file in that commit.
With no argument, the version in the index is used With no argument, the version in the index or work
(which means a three-way diff during a merge conflict, tree is used. The newer of the two files is placed to
making it a git-mergetool alternative). The newer of the right or bottom, depending on 'diffopt' and the
the two files is placed to the right or bottom, width of the window relative to 'textwidth'. Use
depending on 'diffopt', and the width of the window Vim's |do| and |dp| to stage and unstage changes.
relative to 'textwidth'. Use |do| and |dp| and write
to the index file to simulate "git add --patch". For
the three-way diff, there is also d2o and d3o pulling
the hunk to the middle from the left or the right
window, respectively.
*fugitive-:Gsdiff* *fugitive-:Gdiffsplit!*
:Gsdiff [object] Like |:Gdiff|, but always split horizontally. :Gdiffsplit! [object] During a merge conflict, do a three-way diff against
both ancestors. Additional d2o and d3o maps are
provided to to obtain the hunk from the "ours" or
"theirs" ancestor, respectively. If the file is not
conflicted, behaves the same as if no bang was given,
but keeps focus in the original window.
*fugitive-:Gvdiff* *fugitive-:Gvdiffsplit*
:Gvdiff [object] Like |:Gdiff|, but always split vertically. :Gvdiffsplit [object] Like |:Gdiffsplit|, but always split vertically.
*fugitive-:Ghdiffsplit* *fugitive-:Gsdiff*
:Ghdiffsplit [object] Like |:Gdiffsplit|, but always split horizontally.
*fugitive-:Gmove* *fugitive-:Gmove*
:Gmove {destination} Wrapper around git-mv that renames the buffer :Gmove {destination} Wrapper around git-mv that renames the buffer
@ -276,13 +279,14 @@ i On untracked files, call |:Git| add --intent-to-add.
automatically. automatically.
*fugitive_dd* *fugitive_dd*
dd Perform a |:Gdiff| on the file under the cursor. dd Perform a |:Gdiffsplit| on the file under the cursor.
*fugitive_ds*
ds Perform a |:Gsdiff| on the file under the cursor.
*fugitive_dv* *fugitive_dv*
dv Perform a |:Gvdiff| on the file under the cursor. dv Perform a |:Gvdiffsplit| on the file under the cursor.
*fugitive_ds* *fugitive_dh*
ds Perform a |:Ghdiffsplit| on the file under the cursor.
dh
*fugitive_dp* *fugitive_dp*
dp Invoke |:Git!| diff on the file under the cursor. dp Invoke |:Git!| diff on the file under the cursor.