Support expanding inline diffs in :Gstatus

This commit is contained in:
Tim Pope 2019-01-03 00:19:21 -05:00
parent 51abc6a1bb
commit d948ec3376
3 changed files with 243 additions and 78 deletions

View File

@ -1481,6 +1481,12 @@ function! fugitive#BufReadStatus() abort
endfor
endif
let b:fugitive_diff = {
\ 'Staged': split(system(fugitive#Prepare('diff', '--no-ext-diff', '--no-prefix', '--cached', '--')), "\n"),
\ 'Unstaged': split(system(fugitive#Prepare('diff', '--no-ext-diff', '--no-prefix', '--')), "\n")}
let expanded = get(b:, 'fugitive_expanded', {'Staged': {}, 'Unstaged': {}})
let b:fugitive_expanded = {'Staged': {}, 'Unstaged': {}}
silent keepjumps %delete_
call s:AddHeader('Head', head)
@ -1490,7 +1496,9 @@ function! fugitive#BufReadStatus() abort
endif
call s:AddSection('Rebasing ' . rebasing_head, rebasing)
call s:AddSection('Unstaged', unstaged)
let unstaged_end = len(unstaged) ? line('$') : 0
call s:AddSection('Staged', staged)
let staged_end = len(staged) ? line('$') : 0
call s:AddSection('Unpushed to ' . push, unpushed)
call s:AddSection('Unpulled from ' . pull, unpulled)
@ -1522,6 +1530,14 @@ function! fugitive#BufReadStatus() abort
nnoremap <buffer> <silent> cw :<C-U>Gcommit --amend --only<CR>
nnoremap <buffer> <silent> cva :<C-U>Gcommit -v --amend<CR>
nnoremap <buffer> <silent> cvc :<C-U>Gcommit -v<CR>
nnoremap <buffer> <silent> a :<C-U>execute <SID>StageInline('toggle',line('.'),v:count)<CR>
nnoremap <buffer> <silent> i :<C-U>execute <SID>StageInline('toggle',line('.'),v:count)<CR>
exe 'nnoremap <buffer> <silent>' nowait "= :<C-U>execute <SID>StageInline('toggle',line('.'),v:count)<CR>"
exe 'nnoremap <buffer> <silent>' nowait "< :<C-U>execute <SID>StageInline('show', line('.'),v:count)<CR>"
exe 'nnoremap <buffer> <silent>' nowait "> :<C-U>execute <SID>StageInline('hide', line('.'),v:count)<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('hide', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>"
nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
@ -1537,9 +1553,20 @@ function! fugitive#BufReadStatus() abort
nnoremap <buffer> <silent> R :<C-U>exe <SID>ReloadStatus()<CR>
nnoremap <buffer> <silent> U :<C-U>echoerr 'Changed to g<Bar>'<CR>
nnoremap <buffer> <silent> g<Bar> :<C-U>execute <SID>StageUndo()<CR>
nnoremap <buffer> . : <C-R>=<SID>fnameescape(<SID>StatusCfile())<CR><Home>
nnoremap <buffer> . : <C-R>=<SID>fnameescape(get(<SID>StatusCfile(),0,''))<CR><Home>
nnoremap <buffer> <silent> g? :help fugitive-:Gstatus<CR>
nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
for [lnum, section] in [[staged_end, 'Staged'], [unstaged_end, 'Unstaged']]
while len(getline(lnum))
let filename = matchstr(getline(lnum), '^[A-Z?] \zs.*')
if has_key(expanded[section], filename)
call s:StageInline('show', lnum)
endif
let lnum -= 1
endwhile
endfor
return ''
catch /^fugitive:/
return 'echoerr v:errmsg'
@ -1867,10 +1894,71 @@ function! s:Status(bang, count, mods) abort
return ''
endfunction
function! s:ReloadStatus() abort
let pos = getpos('.')
function! s:StageSeek(info, fallback) abort
let info = a:info
if empty(info.section)
return a:fallback
endif
let line = search('^' . info.section, 'wn')
if !line
for section in get({'Staged': ['Unstaged'], 'Unstaged': ['Staged']}, info.section, [])
let line = search('^' . section, 'wn')
if line
return line + (info.index > 0 ? 1 : 0)
endif
endfor
return 1
endif
let i = 0
while len(getline(line))
let filename = matchstr(getline(line), '^[A-Z?] \zs.*')
if len(filename) &&
\ ((info.filename[-1:-1] ==# '/' && filename[0 : len(info.filename) - 1] ==# info.filename) ||
\ (filename[-1:-1] ==# '/' && filename ==# info.filename[0 : len(filename) - 1]) ||
\ filename ==# info.filename)
if info.offset < 0
return line
else
if getline(line+1) !~# '^@'
exe s:StageInline('show', line)
endif
if getline(line+1) !~# '^@'
return line
endif
let type = info.sigil ==# '-' ? '-' : '+'
let offset = -1
while offset < info.offset
let line += 1
if getline(line) =~# '^@'
let offset = +matchstr(getline(line), type . '\zs\d\+') - 1
elseif getline(line) =~# '^[ ' . type . ']'
let offset += 1
elseif getline(line) !~# '^[ @+-]'
return line - 1
endif
endwhile
return line
endif
endif
let commit = matchstr(getline(line), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\+')
if len(commit) && commit ==# info.commit
return line
endif
if i ==# info.index
let backup = line
endif
let i += getline(line) !~# '^[ @+-]'
let line += 1
endwhile
return exists('backup') ? backup : line - 1
endfunction
function! s:ReloadStatus(...) abort
let original_lnum = a:0 ? a:1 : line('.')
let info = s:StageInfo(original_lnum)
call fugitive#BufReadStatus()
call setpos('.', pos)
exe s:StageSeek(info, original_lnum)
normal! 0
return ''
endfunction
@ -1910,28 +1998,44 @@ endfunction
function! s:StageInfo(...) abort
let lnum = a:0 ? a:1 : line('.')
let sigil = matchstr(getline('.'), '^[ @+-]')
let offset = -1
if getline(lnum) =~# '^[ @+-]'
let type = sigil ==# '-' ? '-' : '+'
while lnum > 0 && getline(lnum) !~# '^@'
if getline(lnum) =~# '^[ '.type.']'
let offset += 1
endif
let lnum -= 1
endwhile
let offset += matchstr(getline(lnum), type.'\zs\d\+')
while getline(lnum) =~# '^[ @+-]'
let lnum -= 1
endwhile
endif
let slnum = lnum + 1
let section = ''
let index = 0
while len(getline(slnum - 1)) && empty(section)
let slnum -= 1
let section = matchstr(getline(slnum), '^\u\l\+\ze.* (\d\+)$')
if empty(section) && getline(slnum) !~# '^[ @+-]'
let index += 1
endif
endwhile
return {'section': section,
\ 'heading': getline(slnum),
\ 'sigil': sigil,
\ 'offset': offset,
\ 'filename': matchstr(getline(lnum), '^[A-Z?] \zs.*'),
\ 'commit': matchstr(getline(lnum), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze '),
\ 'status': matchstr(getline(lnum), '^[A-Z?]\ze \|^\%(\x\x\x\)\@!\l\+\ze [0-9a-f]'),
\ 'index': lnum - slnum}
endfunction
function! s:StageFileSection(lnum) abort
let info = s:StageInfo(a:lnum)
return [info.filename, info.section]
\ 'index': index}
endfunction
function! s:StageNext(count) abort
for i in range(a:count)
call search('^[A-Z?] .\|^[0-9a-f]\{4,\} ','W')
call search('^[A-Z?] .\|^[0-9a-f]\{4,\} \|^@','W')
endfor
return '.'
endfunction
@ -1941,35 +2045,15 @@ function! s:StagePrevious(count) abort
return 'CtrlP '.fnameescape(s:Tree())
else
for i in range(a:count)
call search('^[A-Z?] .\|^[0-9a-f]\{4,\} ','Wbe')
call search('^[A-Z?] .\|^[0-9a-f]\{4,\} \|^@','Wbe')
endfor
return '.'
endif
endfunction
function! s:StageReloadSeek(target,lnum1,lnum2) abort
let jump = a:target
let target = s:StageFileSection(a:lnum2 + 1)
if empty(target[0])
let target = s:StageFileSection(a:lnum1 - 1)
endif
if empty(target[0])
let target = a:target
endif
call s:ReloadStatus()
1
redraw
let lnum = 0
while lnum < line('$')
let lnum += 1
let file = getline(lnum)[2:-1]
if (target[0][-1:-1] ==# '/' && file[0 : len(target[0]) - 1] ==# target[0]) ||
\ (file[-1:-1] ==# '/' && file ==# target[0][0 : len(file) - 1]) ||
\ file ==# target[0]
exe lnum
return ''
endif
endwhile
exe s:ReloadStatus(a:lnum1)
return ''
endfunction
function! s:StageUndo() abort
@ -1995,28 +2079,108 @@ function! s:StageUndo() abort
endif
endfunction
function! s:StageInline(mode, ...) abort
let lnum1 = a:0 ? a:1 : line('.')
let lnum = lnum1 + 1
if a:0 > 1 && a:2 == 0
let info = s:StageInfo(lnum - 1)
if empty(info.filename) && len(info.section)
while len(getline(lnum))
let lnum += 1
endwhile
endif
elseif a:0 > 1
let lnum += a:2 - 1
endif
while lnum > lnum1
let lnum -= 1
while lnum > 0 && getline(lnum) =~# '^[ @+-]'
let lnum -= 1
endwhile
let info = s:StageInfo(lnum)
if !has_key(b:fugitive_diff, info.section)
continue
endif
if getline(lnum + 1) =~# '^[ @+-]'
let lnum2 = lnum + 1
while getline(lnum2 + 1) =~# '^[ @+-]'
let lnum2 += 1
endwhile
if a:mode !=# 'show'
setlocal modifiable noreadonly
exe 'silent keepjumps ' . (lnum + 1) . ',' . lnum2 . 'delete _'
call remove(b:fugitive_expanded[info.section], info.filename)
setlocal nomodifiable readonly nomodified
endif
continue
endif
if !has_key(b:fugitive_diff, info.section) || info.status !~# '^[ADM]$' || a:mode ==# 'hide'
continue
endif
let mode = ''
let diff = []
let index = 0
let start = -1
for line in b:fugitive_diff[info.section]
if mode ==# 'await' && line[0] ==# '@'
let mode = 'capture'
endif
if line[0] ==# 'd'
if len(diff)
break
endif
let start = index
let mode = 'head'
elseif mode ==# 'head' && line ==# '--- ' . info.filename
let mode = 'await'
elseif mode ==# 'head' && line ==# '+++ ' . info.filename
let mode = 'await'
elseif mode ==# 'capture'
call add(diff, line)
elseif line[0] ==# '@'
let mode = ''
endif
let index += 1
endfor
if len(diff)
setlocal modifiable noreadonly
silent call append(lnum, diff)
let b:fugitive_expanded[info.section][info.filename] = [start, len(diff)]
setlocal nomodifiable readonly nomodified
endif
endwhile
return lnum
endfunction
function! s:StageDiff(diff) abort
let [filename, section] = s:StageFileSection(line('.'))
if filename ==# '' && section ==# 'Staged'
let lnum = line('.')
let info = s:StageInfo(lnum)
let prefix = info.offset > 0 ? '+' . info.offset : ''
if empty(info.filename) && info.section ==# 'Staged'
return 'Git! diff --no-ext-diff --cached'
elseif filename ==# ''
elseif empty(info.filename)
return 'Git! diff --no-ext-diff'
elseif filename =~# ' -> '
let [old, new] = split(filename,' -> ')
execute 'Gedit '.s:fnameescape(':0:'.new)
elseif info.filename =~# ' -> '
let [old, new] = split(info.filename,' -> ')
execute 'Gedit' . prefix s:fnameescape(':0:'.new)
return a:diff.' HEAD:'.s:fnameescape(old)
elseif section ==# 'Staged'
execute 'Gedit '.s:fnameescape(':0:'.filename)
return a:diff.' -'
elseif info.section ==# 'Staged' && info.sigil ==# '-'
execute 'Gedit' prefix s:fnameescape('@:'.info.filename)
return a:diff.'! :0'
elseif info.section ==# 'Staged'
execute 'Gedit' prefix s:fnameescape(':0:'.info.filename)
return a:diff . (info.sigil ==# '+' ? '!' : '') . ' -'
elseif info.sigil ==# '-'
execute 'Gedit' prefix s:fnameescape(':0:'.info.filename)
return a:diff . '!'
else
execute 'Gedit '.s:fnameescape(':(top)'.filename)
return a:diff
execute 'Gedit' prefix s:fnameescape(':(top)'.info.filename)
return a:diff . (info.sigil ==# '+' ? '!' : '')
endif
endfunction
function! s:StageDiffEdit() abort
let info = s:StageInfo(line('.'))
let [filename, section] = s:StageFileSection(line('.'))
let arg = (empty(info.filename) ? '.' : info.filename)
if info.section ==# 'Staged'
return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
@ -2029,7 +2193,7 @@ function! s:StageDiffEdit() abort
call search('^Staged','W')
endif
else
call s:StageReloadSeek([filename, 'Staged'], line('.'), line('.'))
call s:StageReloadSeek([info.filename, 'Staged'], line('.'), line('.'))
endif
return ''
else
@ -2107,24 +2271,21 @@ function! s:StagePatch(lnum1,lnum2) abort
let reset = []
for lnum in range(a:lnum1,a:lnum2)
let [filename, section] = s:StageFileSection(lnum)
if empty(filename) && section ==# 'Staged'
let info = s:StageInfo(lnum)
if empty(info.filename) && info.section ==# 'Staged'
return 'Git reset --patch'
elseif empty(filename) && section ==# 'Unstaged'
elseif empty(info.filename) && info.section ==# 'Unstaged'
return 'Git add --patch'
elseif filename ==# ''
elseif info.filename ==# ''
continue
endif
if !exists('first_filename')
let first_filename = filename
endif
execute lnum
if filename =~ ' -> '
let reset += [split(filename,' -> ')[1]]
elseif section ==# 'Staged'
let reset += [filename]
elseif getline(lnum) !~# '^.\=\tdeleted:'
let add += [filename]
if info.filename =~ ' -> '
let reset += [split(info.filename,' -> ')[1]]
elseif info.section ==# 'Staged'
let reset += [info.filename]
elseif info.status !~# '^D'
let add += [info.filename]
endif
endfor
try
@ -2134,16 +2295,10 @@ function! s:StagePatch(lnum1,lnum2) abort
if !empty(reset)
execute "Git reset --patch -- ".join(map(reset,'s:shellesc(v:val)'))
endif
if exists('first_filename')
silent! edit!
1
redraw
call search('^[A-Z?] \V'.first_filename.'\$','W')
endif
catch /^fugitive:/
return 'echoerr v:errmsg'
endtry
return 'checktime'
return s:ReloadStatus()
endfunction
" Section: :Gcommit
@ -3801,22 +3956,29 @@ endfunction
function! s:StatusCfile(...) abort
let tree = FugitiveTreeForGitDir(b:git_dir)
let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
let info = s:StageInfo()
let line = getline('.')
if line =~# '^\S '
return lead . line[2:-1]
elseif line =~# '^[0-9a-f]\{4,\}\s'
return matchstr(line, '^\S\+')
elseif line =~# '^\l\+\s\+[0-9a-f]\{4,\}\s'
return matchstr(line, '^\l\+\s\+\zs\S\+')
if len(info.sigil) && len(info.section) && len(info.filename)
if info.section ==# 'Unstaged' && info.sigil !=# '-'
return [lead . info.filename, info.offset, 'normal!zv']
elseif info.section ==# 'Staged' && info.sigil ==# '-'
return ['@:' . info.filename, info.offset, 'normal!zv']
else
return [':0:' . info.filename, info.offset, 'normal!zv']
endif
elseif len(info.filename)
return [lead . info.filename]
elseif len(info.commit)
return [info.commit]
elseif line =~# '^\%(Head\|Merge\|Rebase\|Upstream\|Pull\|Push\): '
return matchstr(line, ' \zs.*')
return [matchstr(line, ' \zs.*')]
else
return ''
return ['']
endif
endfunction
function! fugitive#StatusCfile() abort
let file = s:Generate(s:StatusCfile())
let file = s:Generate(s:StatusCfile()[0])
return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
endfunction
@ -4003,7 +4165,7 @@ endfunction
function! s:GF(mode) abort
try
let results = &filetype ==# 'fugitive' ? [s:StatusCfile()] : &filetype ==# 'gitcommit' ? [s:MessageCfile()] : s:cfile()
let results = &filetype ==# 'fugitive' ? s:StatusCfile() : &filetype ==# 'gitcommit' ? [s:MessageCfile()] : s:cfile()
catch /^fugitive:/
return 'echoerr v:errmsg'
endtry

View File

@ -49,6 +49,9 @@ that are part of Git repositories).
cf |:Gcommit| --fixup=
cs |:Gcommit| --squash=
cA |:Gcommit| --edit --squash=
= toggle inline diff
< show inline diff
> hide inline diff
D |:Gdiff|
ds |:Gsdiff|
dp |:Git!| diff (p for patch; use :Gw to apply)

View File

@ -22,7 +22,7 @@ syn match FugitiveSymbolicRef /\.\@!\%(\.\.\@!\|[^[:space:][:cntrl:]\:.]\)\+\.\@
syn match fugitiveHash /^\x\{4,\}\>/ contained containedin=fugitiveSection
syn match fugitiveHash /\<\x\{4,\}\>/ contained
syn region fugitiveHunk start=/^\%(@@ -\)\@=/ end=/^\%(diff --\%(git\|cc\|combined\) \|@@\|$\)\@=/ contains=@fugitiveDiff containedin=fugitiveSection fold
syn region fugitiveHunk start=/^\%(@@ -\)\@=/ end=/^\%([A-Za-z?@]\|$\)\@=/ contains=@fugitiveDiff containedin=fugitiveSection fold
hi def link fugitiveHeader Label
hi def link fugitiveHeading PreProc