From 4daa0c558ceb14223c6c6861063ef0ae9a35233b Mon Sep 17 00:00:00 2001 From: Tim Pope Date: Sun, 11 Aug 2019 00:36:31 -0400 Subject: [PATCH] Support nearly all git-blame flags --- autoload/fugitive.vim | 137 ++++++++++++++++++++++++++++-------------- doc/fugitive.txt | 9 ++- 2 files changed, 95 insertions(+), 51 deletions(-) diff --git a/autoload/fugitive.vim b/autoload/fugitive.vim index 84e5b99..d81bb58 100644 --- a/autoload/fugitive.vim +++ b/autoload/fugitive.vim @@ -4558,7 +4558,7 @@ augroup fugitive_blame autocmd FileType fugitiveblame setlocal nomodeline | if len(s:Dir()) | let &l:keywordprg = s:Keywordprg() | endif autocmd User Fugitive \ if get(b:, 'fugitive_type') =~# '^\%(file\|blob\)$' || s:BlameBufnr() > 0 || filereadable(@%) | - \ exe "command! -buffer -bar -bang -range=-1 -nargs=* Gblame :execute s:BlameCommand(,,+'',,0,'',,,[])" | + \ exe "command! -buffer -bar -bang -range=-1 -nargs=* -complete=customlist,s:BlameComplete Gblame :execute s:BlameCommand(,,+'',,0,'',,,[])" | \ endif autocmd ColorScheme,GUIEnter * call s:RehighlightBlame() autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('')), 'fugitive_leave') @@ -4583,6 +4583,20 @@ function! s:BlameBufnr(...) abort endif endfunction +function! s:BlameCommitFileLnum(...) abort + let line = a:0 ? a:1 : getline('.') + let commit = matchstr(line, '^\^\=\zs\x\+') + if commit =~# '^0\+$' + let commit = '' + endif + let lnum = +matchstr(line, ' \zs\d\+\ze \%((\| *\d\+)\)') + let path = matchstr(line, '^\^\=\x* \+\%(\d\+ \+\d\+ \+\)\=\zs.\{-\}\ze\s\+\%(\%( \d\+ \)\@ 0 @@ -4604,29 +4618,56 @@ function! s:BlameQuit() abort endif endfunction +function! s:BlameComplete(A, L, P) abort + return s:CompleteSub('blame', a:A, a:L, a:P, []) +endfunction + function! s:BlameCommand(line1, line2, range, count, bang, mods, reg, arg, args) abort - if exists('b:fugitive_blame_arguments') + if has_key(s:TempState(), 'blame_flags') return substitute(s:BlameLeave(), '^$', 'bdelete', '') endif exe s:DirCheck() + let flags = copy(a:args) + let i = 0 + while i < len(flags) + let match = matchlist(flags[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)') + if len(match) && len(match[2]) + call insert(flags, match[1]) + let flags[i+1] = '-' . match[2] + continue + endif + let arg = flags[i] + if arg =~# '^-[Lp]$\|^--\%(help\|porcelain\|line-porcelain\|incremental\|contents\)$' + return 'echoerr ' . string('fugitive: blame ' . arg . ' unsupported') + elseif arg =~# '^-[GLS]$\|^--\%(date\|encoding\)$' + let i += 1 + if i == len(flags) + echohl ErrorMsg + echo s:ChompError(['blame', arg])[0] + echohl NONE + return '' + endif + elseif arg !~# '^-' + return 'echoerr ' . string("fugitive: '-' required for all blame options") + elseif arg ==# '--' + call remove(flags, i) + continue + endif + let i += 1 + endwhile try if empty(s:Relative('/')) call s:throw('file or blob required') endif let commit = matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$') - if filter(copy(a:args),'v:val !~# "^-"') != [] - call s:throw("'-' required for all options") - elseif filter(copy(a:args),'v:val !~# "^\\%(--abbrev=\\d*\\|--relative-date\\|--first-parent\\|--root\\|--show-name' . (len(commit) ? '\\|--reverse' : '') . '\\|-\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != [] - call s:throw('unsupported option' . commit) - endif let cmd = ['--no-pager', '-c', 'blame.coloring=none', '-c', 'blame.blankBoundary=false', 'blame', '--show-number'] + call extend(cmd, filter(copy(flags), 'v:val !~# "\\v^%(-b|--%(no-)=color-.*|--progress)$"')) if a:count > 0 let cmd += ['-L', (a:line1 ? a:line1 : line('.')) . ',' . (a:line1 ? a:line1 : line('.'))] endif - let cmd += a:args if len(commit) let cmd += [commit] - else + elseif !s:HasOpt(flags, '--reverse') let cmd += ['--contents', '-'] endif let cmd += ['--', expand('%:p')] @@ -4638,16 +4679,34 @@ function! s:BlameCommand(line1, line2, range, count, bang, mods, reg, arg, args) else silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error endif + redraw try if v:shell_error - call s:throw(join(readfile(error),"\n")) + let lines = readfile(error) + if empty(lines) + let lines = readfile(temp) + endif + for i in range(len(lines)) + if lines[i] =~# '^error: \|^fatal: ' + echohl ErrorMsg + echon lines[i] + echohl NONE + break + else + echon lines[i] + endif + if i != len(lines) - 1 + echon "\n" + endif + endfor + return '' endif if a:count > 0 let edit = s:Mods(a:mods) . get(['edit', 'split', 'pedit', 'vsplit', 'tabedit'], a:count - (a:line1 ? a:line1 : 1), 'split') return s:BlameCommit(edit, get(readfile(temp), 0, ''), bufnr('')) else let temp = s:Resolve(temp) - let s:temp_files[s:cpath(temp)] = {'dir': s:Dir(), 'filetype': 'fugitiveblame', 'args': cmd, 'modifiable': 0} + let s:temp_files[s:cpath(temp)] = {'dir': s:Dir(), 'filetype': 'fugitiveblame', 'blame_flags': flags, 'modifiable': 0} for winnr in range(winnr('$'),1,-1) if getwinvar(winnr, '&scrollbind') call setwinvar(winnr, '&scrollbind', 0) @@ -4679,7 +4738,6 @@ function! s:BlameCommand(line1, line2, range, count, bang, mods, reg, arg, args) let current = line('.') exe 'keepalt' (a:bang ? 'split' : 'leftabove vsplit') s:fnameescape(temp) let w:fugitive_leave = restore - let b:fugitive_blame_arguments = join(a:args,' ') execute top normal! zt execute current @@ -4705,9 +4763,9 @@ function! s:BlameCommand(line1, line2, range, count, bang, mods, reg, arg, args) nnoremap P :exe BlameJump('^'.v:count1) nnoremap ~ :exe BlameJump('~'.v:count1) nnoremap i :exe BlameCommit("exe BlameLeave()edit") - nnoremap o :exe BlameCommit((&splitbelow ? "botright" : "topleft")." split") + nnoremap o :exe BlameCommit("split") nnoremap O :exe BlameCommit("tabedit") - nnoremap p :exe Open((&splitbelow ? "botright" : "topleft").' pedit', 0, '', matchstr(getline('.'), '\x\+'), [matchstr(getline('.'), '\x\+')]) + nnoremap p :exe BlameCommit("pedit") nnoremap A :exe "vertical resize ".(linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count) nnoremap C :exe "vertical resize ".(linechars('^\S\+')+1+v:count) nnoremap D :exe "vertical resize ".(linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count) @@ -4722,24 +4780,16 @@ function! s:BlameCommand(line1, line2, range, count, bang, mods, reg, arg, args) endfunction function! s:BlameCommit(cmd, ...) abort - let line = a:0 ? a:1 : getline('.') - if line =~# '^0\{4,\} ' - return 'echoerr ' . string('Not Committed Yet') + let [commit, path, lnum] = call('s:BlameCommitFileLnum', a:000) + if commit =~# '^0*$' + return 'echoerr ' . string('fugitive: no commit') endif - let cmd = s:Open(a:cmd, 0, '', matchstr(line, '\x\+'), [matchstr(line, '\x\+')]) + let cmd = s:Open((&splitbelow ? "botright " : "topleft ") . a:cmd, 0, '', commit, [commit]) if cmd =~# '^echoerr' return cmd endif - let lnum = matchstr(line, ' \zs\d\+\ze\s\+[([:digit:]]') - let path = matchstr(line, '^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ') - if empty(path) - let path = fugitive#Path(bufname(a:0 ? a:2 : s:BlameBufnr()), '') - endif - if empty(path) - return 'echoerr ' . string('fugitive: could not determine filename for blame') - endif execute cmd - if a:cmd ==# 'pedit' + if a:cmd ==# 'pedit' || empty(path) return '' endif if search('^diff .* b/\M'.escape(path,'\').'$','W') @@ -4773,25 +4823,18 @@ function! s:BlameCommit(cmd, ...) abort endfunction function! s:BlameJump(suffix) abort - let commit = matchstr(getline('.'),'^\^\=\zs\x\+') let suffix = a:suffix - if commit =~# '^0\+$' - let commit = 'HEAD' - let suffix = '' - endif - let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]') - let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ') - let original_bufnr = s:BlameBufnr() - if empty(path) - let path = fugitive#Path(bufname(original_bufnr), '') - endif + let [commit, path, lnum] = s:BlameCommitFileLnum() if empty(path) return 'echoerr ' . string('fugitive: could not determine filename for blame') endif - let args = get(b:, 'fugitive_blame_arguments', '') + if commit =~# '^0*$' + let commit = 'HEAD' + let suffix = '' + endif let offset = line('.') - line('w0') let bufnr = bufnr('%') - let winnr = bufwinnr(original_bufnr) + let winnr = bufwinnr(s:BlameBufnr()) if winnr > 0 exe winnr.'wincmd w' endif @@ -4801,7 +4844,8 @@ function! s:BlameJump(suffix) abort exe bufnr.'bdelete' endif if exists(':Gblame') - execute 'Gblame '.args + let flags = get(s:TempState(), 'blame_flags', []) + execute 'Gblame ' . s:fnameescape(flags) execute lnum let delta = line('.') - line('w0') - offset if delta > 0 @@ -4819,10 +4863,11 @@ let s:hash_colors = {} function! fugitive#BlameSyntax() abort let conceal = has('conceal') ? ' conceal' : '' let config = fugitive#Config() + let flags = get(s:TempState(), 'blame_flags', []) syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite syn match FugitiveblameHash "\%(^\^\=\)\@<=\<\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite - if get(get(config, 'blame.blankboundary', ['x']), 0, 'x') =~# '^$\|^true$' + if get(get(config, 'blame.blankboundary', ['x']), 0, 'x') =~# '^$\|^true$' || s:HasOpt(flags, '-b') syn match FugitiveblameBoundaryIgnore "^\^\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite else syn match FugitiveblameBoundary "^\^" @@ -4831,9 +4876,9 @@ function! fugitive#BlameSyntax() abort syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%(\s\d\+\)\@<=)" contained keepend oneline syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%(\s\+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation exec 'syn match FugitiveblameLineNumber "\s*\d\+)\@=" contained containedin=FugitiveblameAnnotation' conceal - exec 'syn match FugitiveblameOriginalFile "\s\%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite' conceal - exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite' conceal - exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite' conceal + exec 'syn match FugitiveblameOriginalFile "\s\%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-name', '-f') ? '' : conceal) + exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal) + exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal) syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation hi def link FugitiveblameBoundary Keyword @@ -4848,7 +4893,7 @@ function! fugitive#BlameSyntax() abort hi def link FugitiveblameShort FugitiveblameDelimiter hi def link FugitiveblameDelimiter Delimiter hi def link FugitiveblameNotCommittedYet Comment - if !get(g:, 'fugitive_dynamic_colors', 1) + if !get(g:, 'fugitive_dynamic_colors', 1) && !s:HasOpt(flags, '--color-lines') || s:HasOpt(flags, '--no-color-lines') return endif let seen = {} diff --git a/doc/fugitive.txt b/doc/fugitive.txt index d239677..ee1d977 100644 --- a/doc/fugitive.txt +++ b/doc/fugitive.txt @@ -193,11 +193,10 @@ that are part of Git repositories). :Gremove Like :Gdelete, but keep the (now empty) buffer around. *fugitive-:Gblame* -:Gblame [flags] Run git-blame on the file and open the results in a - scroll-bound vertical split. You can give any of - -ltsewMC as flags, and they will be passed along to - git-blame. The following maps, which work on the - cursor line commit where sensible, are provided: +:Gblame [flags] Run git-blame [flags] on the current file and open the + results in a scroll-bound vertical split. The + following maps, which work on the cursor line commit + where sensible, are provided: g? show this help A resize to end of author column