You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

237 lines
7.9 KiB
VimL

" extradite.vim -- a git browser plugin that extends fugitive.vim
" Maintainer: Jezreel Ng <jezreel@gmail.com>
" Version: 1.0
" License: This file is placed in the public domain.
if exists('g:loaded_extradite')
finish
endif
let g:loaded_extradite = 1
if !exists('g:extradite_width')
let g:extradite_width = 60
endif
command! -buffer -bang Extradite :execute s:Extradite(<bang>0)
autocmd Syntax extradite call s:ExtraditeSyntax()
let g:extradite_bufnr = -1
function! s:Extradite(bang) abort
if !exists('b:git_dir')
echo 'Not a git repository.'
return
endif
" if we are open, close.
if g:extradite_bufnr >= 0
call <SID>ExtraditeClose()
return
endif
let path = fugitive#buffer().path()
try
let git_dir = fugitive#buffer().repo().dir()
" insert literal tabs in the format string because git does not seem to provide an escape code for it
let template_cmd = ['--no-pager', 'log', '-n100']
let bufnr = bufnr('')
let base_file_name = tempname()
call s:ExtraditeLoadCommitData(a:bang, base_file_name, template_cmd, path)
let b:base_file_name = base_file_name
let b:git_dir = git_dir
let b:fugitive_type = 'log'
let b:fugitive_logged_bufnr = bufnr
exe 'vertical resize '.g:extradite_width
command! -buffer -bang Extradite :execute s:Extradite(<bang>0)
" invoke ExtraditeClose instead of bdelete so we can do the necessary cleanup
nnoremap <buffer> <silent> q :<C-U>call <SID>ExtraditeClose()<CR>
nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>ExtraditeJump("edit")<CR>
nnoremap <buffer> <silent> ov :<C-U>exe <SID>ExtraditeJump((&splitbelow ? "botright" : "topleft")." vsplit")<CR>
nnoremap <buffer> <silent> oh :<C-U>exe <SID>ExtraditeJump((&splitbelow ? "botright" : "topleft")." split")<CR>
nnoremap <buffer> <silent> ot :<C-U>exe <SID>ExtraditeJump("tabedit")<CR>
nnoremap <buffer> <silent> dv :<C-U>exe <SID>ExtraditeDiff(0)<CR>
nnoremap <buffer> <silent> dh :<C-U>exe <SID>ExtraditeDiff(1)<CR>
" hack to make the cursor stay in the same position. putting line= in ExtraditeDiffToggle / removing <C-U>
" doesn't seem to work
nnoremap <buffer> <silent> t :let line=line('.')<cr> :<C-U>exe <SID>ExtraditeDiffToggle()<CR> :exe line<cr>
autocmd CursorMoved <buffer> exe 'setlocal statusline='.escape(b:extradata_list[line(".")-1]['date'], ' ')
call s:ExtraditeDiffToggle()
let g:extradite_bufnr = bufnr('')
return ''
catch /^fugitive:/
return 'echoerr v:errmsg'
endtry
endfunction
function! s:ExtraditeLoadCommitData(bang, base_file_name, template_cmd, ...) abort
if a:0 >= 1
let path = a:1
else
let path = ''
endif
let cmd = a:template_cmd + ['--pretty=format:\%an \%d \%s', '--', path]
let extradata_cmd = a:template_cmd + ['--pretty=format:\%h \%ad', '--', path]
let basecmd = call(fugitive#buffer().repo().git_command,cmd,fugitive#buffer().repo())
let extradata_basecmd = call(fugitive#buffer().repo().git_command,extradata_cmd,fugitive#buffer().repo())
let log_file = a:base_file_name.'.extradite'
" put the commit IDs in a separate file -- the user doesn't have to know
" exactly what they are
let extradata_file = a:base_file_name.'.extraditecommits'
if &shell =~# 'csh'
silent! execute '%write !('.extradata_basecmd.' > '.extradata_file.') >& '.a:base_file_name
silent! execute '%write !('.basecmd.' > '.log_file.') >& '.a:base_file_name
else
silent! execute '%write !'.extradata_basecmd.' > '.extradata_file.' 2> '.a:base_file_name
silent! execute '%write !'.basecmd.' > '.log_file.' 2> '.a:base_file_name
endif
if v:shell_error
call s:throw(join(readfile(error),"\n"))
endif
if g:extradite_bufnr >= 0
edit
else
if a:bang
exe 'leftabove vsplit '.log_file
else
exe 'edit' log_file
endif
endif
" Some components of the log may have no value. Or may insert whitespace of their own. Remove the repeated
" whitespace that result from this. Side effect: removes intended whitespace in the commit data.
setlocal modifiable
silent! %s/\(\s\)\s\+/\1/g
normal! gg
let b:extradata_list = []
let extradata = readfile(extradata_file)
for line in extradata
let tokens = matchlist(line, '\([^\t]\+\)\t\([^\t]\+\)')
call add(b:extradata_list, {'commit': tokens[1], 'date': tokens[2]})
endfor
setlocal nomodified nomodifiable bufhidden=delete nonumber nowrap foldcolumn=0 nofoldenable filetype=extradite ts=1 cursorline nobuflisted so=0
endfunction
" Returns the `commit:path` associated with the current line in the Extradite buffer
function! s:ExtraditePath(...) abort
if exists('a:1')
let modifier = a:1
else
let modifier = ''
endif
return b:extradata_list[line(".")-1]['commit'].modifier.':'.fugitive#buffer(b:fugitive_logged_bufnr).path()
endfunction
" Closes the file log and returns the selected `commit:path`
function! s:ExtraditeClose() abort
if (g:extradite_bufnr >= 0)
let filelog_winnr = bufwinnr(g:extradite_bufnr)
exe filelog_winnr.'wincmd w'
else
return
endif
let rev = s:ExtraditePath()
let fugitive_logged_bufnr = b:fugitive_logged_bufnr
if exists('b:fugitive_simplediff_bufnr') && bufwinnr(b:fugitive_simplediff_bufnr) >= 0
exe 'bd!' . b:fugitive_simplediff_bufnr
endif
bd
let logged_winnr = bufwinnr(fugitive_logged_bufnr)
if logged_winnr >= 0
exe logged_winnr.'wincmd w'
endif
let g:extradite_bufnr = -1
return rev
endfunction
function! s:ExtraditeJump(cmd) abort
let rev = s:ExtraditeClose()
if a:cmd == 'tabedit'
exe ':Gtabedit '.rev
else
exe a:cmd
exe ':Gedit '.rev
endif
endfunction
function! s:ExtraditeDiff(bang) abort
let rev = s:ExtraditeClose()
exe ':Gdiff'.(a:bang ? '!' : '').' '.rev
endfunction
function! s:ExtraditeSyntax() abort
let b:current_syntax = 'extradite'
syn match FugitivelogName "\(\w\| \)\+\t"
syn match FugitivelogTag "(.*)\t"
hi def link FugitivelogName String
hi def link FugitivelogTag Identifier
hi! def link CursorLine Visual
" make the cursor less obvious. has no effect on xterm
hi! def link Cursor Visual
endfunction
function! s:ExtraditeDiffToggle() abort
if !exists('b:fugitive_simplediff_bufnr') || b:fugitive_simplediff_bufnr == -1
augroup extradite
autocmd CursorMoved <buffer> call s:SimpleFileDiff(s:ExtraditePath('~1'), s:ExtraditePath())
" vim seems to get confused if we jump around buffers during a CursorMoved event. Moving the cursor
" around periodically helps vim figure out where it should really be.
autocmd CursorHold <buffer> normal! lh
augroup END
else
exe "bd" b:fugitive_simplediff_bufnr
unlet b:fugitive_simplediff_bufnr
au! extradite
endif
endfunction
" Does a git diff on a single file and discards the top few lines of extraneous
" information
function! s:SimpleFileDiff(a,b) abort
call s:SimpleDiff(a:a,a:b)
let win = bufwinnr(b:fugitive_simplediff_bufnr)
exe win.'wincmd w'
set modifiable
silent normal! gg5dd
set nomodifiable
wincmd p
endfunction
" Does a git diff of commits a and b. Will create one simplediff-buffer that is
" unique wrt the buffer that it is invoked from.
function! s:SimpleDiff(a,b) abort
if !exists('b:fugitive_simplediff_bufnr') || b:fugitive_simplediff_bufnr == -1
belowright split
enew!
let bufnr = bufnr('')
wincmd p
let b:fugitive_simplediff_bufnr = bufnr
endif
let win = bufwinnr(b:fugitive_simplediff_bufnr)
exe win.'wincmd w'
" check if we have generated this diff already, to reduce unnecessary shell requests
if exists('b:files') && b:files['a'] == a:a && b:files['b'] == a:b
wincmd p
return
endif
set modifiable
silent! %delete _
let diff = system('git diff '.a:a.' '.a:b)
silent put = diff
setlocal ft=diff buftype=nofile nomodifiable
" somehow this is necessary to prevent future buffers from having `nomodifiable`
autocmd BufDelete <buffer> set modifiable
let b:files = { 'a': a:a, 'b': a:b }
wincmd p
endfunction