" extradite.vim -- a git browser plugin that extends fugitive.vim " Maintainer: Jezreel Ng " 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(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 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(0) " invoke ExtraditeClose instead of bdelete so we can do the necessary cleanup nnoremap q :call ExtraditeClose() nnoremap :exe ExtraditeJump("edit") nnoremap ov :exe ExtraditeJump((&splitbelow ? "botright" : "topleft")." vsplit") nnoremap oh :exe ExtraditeJump((&splitbelow ? "botright" : "topleft")." split") nnoremap ot :exe ExtraditeJump("tabedit") nnoremap dv :exe ExtraditeDiff(0) nnoremap dh :exe ExtraditeDiff(1) " hack to make the cursor stay in the same position. putting line= in ExtraditeDiffToggle / removing " doesn't seem to work nnoremap t :let line=line('.') :exe ExtraditeDiffToggle() :exe line autocmd CursorMoved 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 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 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 set modifiable let b:files = { 'a': a:a, 'b': a:b } wincmd p endfunction