Files
vim-gitgutter/plugin/gitgutter.vim
Andy Stewart f2e08dbeb3 Move git-tracking check into diff command.
This avoids shelling out twice per buffer: once to check whether git
knows about the file and once to perform the diff.  Now we simply do
both in one external call.

Profiling showed external calls to git taking ~20ms.  This doesn't seem
too bad but it adds up.
2014-01-27 10:48:47 +01:00

378 lines
10 KiB
VimL

if exists('g:loaded_gitgutter') || !executable('git') || !has('signs') || &cp
finish
endif
let g:loaded_gitgutter = 1
" Initialisation {{{
function! s:set(var, default)
if !exists(a:var)
if type(a:default)
execute 'let' a:var '=' string(a:default)
else
execute 'let' a:var '=' a:default
endif
endif
endfunction
call s:set('g:gitgutter_enabled', 1)
call s:set('g:gitgutter_signs', 1)
call s:set('g:gitgutter_highlight_lines', 0)
call s:set('g:gitgutter_sign_column_always', 0)
call s:set('g:gitgutter_realtime', 1)
call s:set('g:gitgutter_eager', 1)
call s:set('g:gitgutter_sign_added', '+')
call s:set('g:gitgutter_sign_modified', '~')
call s:set('g:gitgutter_sign_removed', '_')
call s:set('g:gitgutter_sign_modified_removed', '~_')
call s:set('g:gitgutter_diff_args', '')
call s:set('g:gitgutter_escape_grep', 0)
call s:set('g:gitgutter_map_keys', 1)
call highlight#define_sign_column_highlight()
call highlight#define_highlights()
call highlight#define_signs()
" }}}
" Primary functions {{{
function! GitGutterAll()
for buffer_id in tabpagebuflist()
let file = expand('#' . buffer_id . ':p')
if !empty(file)
call GitGutter(file, 0)
endif
endfor
endfunction
command GitGutterAll call GitGutterAll()
" Does the actual work.
"
" file: (string) the file to process.
" realtime: (boolean) when truthy, do a realtime diff; otherwise do a disk-based diff.
function! GitGutter(file, realtime)
call utility#set_file(a:file)
if utility#is_active()
try
if !a:realtime || utility#has_fresh_changes(a:file)
let diff = diff#run_diff(a:realtime || utility#has_unsaved_changes(a:file), 1)
let s:hunks = diff#parse_diff(diff)
let modified_lines = diff#process_hunks(s:hunks)
if g:gitgutter_signs
if g:gitgutter_sign_column_always
call sign#add_dummy_sign()
else
if utility#differences(s:hunks)
call sign#add_dummy_sign() " prevent flicker
else
call sign#remove_dummy_sign()
endif
endif
call sign#update_signs(a:file, modified_lines)
endif
call utility#save_last_seen_change(a:file)
endif
catch /git-diff failed/
call hunk#reset()
endtry
else
call hunk#reset()
endif
endfunction
command GitGutter call GitGutter(utility#current_file(), 0)
" }}}
" The plugin: enable / disable / toggle {{{
function! GitGutterDisable()
let g:gitgutter_enabled = 0
call sign#clear_signs(utility#file())
call sign#remove_dummy_sign()
call hunk#reset()
endfunction
command GitGutterDisable call GitGutterDisable()
function! GitGutterEnable()
let g:gitgutter_enabled = 1
call GitGutter(utility#current_file(), 0)
endfunction
command GitGutterEnable call GitGutterEnable()
function! GitGutterToggle()
if g:gitgutter_enabled
call GitGutterDisable()
else
call GitGutterEnable()
endif
endfunction
command GitGutterToggle call GitGutterToggle()
" }}}
" Line highlights: enable / disable / toggle {{{
function! GitGutterLineHighlightsDisable()
let g:gitgutter_highlight_lines = 0
call highlight#define_sign_line_highlights()
redraw!
endfunction
command GitGutterLineHighlightsDisable call GitGutterLineHighlightsDisable()
function! GitGutterLineHighlightsEnable()
let g:gitgutter_highlight_lines = 1
call highlight#define_sign_line_highlights()
redraw!
endfunction
command GitGutterLineHighlightsEnable call GitGutterLineHighlightsEnable()
function! GitGutterLineHighlightsToggle()
let g:gitgutter_highlight_lines = !g:gitgutter_highlight_lines
call highlight#define_sign_line_highlights()
redraw!
endfunction
command GitGutterLineHighlightsToggle call GitGutterLineHighlightsToggle()
" }}}
" Signs: enable / disable / toggle {{{
function! GitGutterSignsEnable()
let g:gitgutter_signs = 1
call GitGutterAll()
endfunction
command GitGutterSignsEnable call GitGutterSignsEnable()
function! GitGutterSignsDisable()
let g:gitgutter_signs = 0
call sign#clear_signs(utility#file())
call sign#remove_dummy_sign()
endfunction
command GitGutterSignsDisable call GitGutterSignsDisable()
function! GitGutterSignsToggle()
if g:gitgutter_signs
call GitGutterSignsDisable()
else
call GitGutterSignsEnable()
endif
endfunction
command GitGutterSignsToggle call GitGutterSignsToggle()
" }}}
" Hunks: jump to next/previous {{{
function! GitGutterNextHunk(count)
if utility#is_active()
let current_line = line('.')
let hunk_count = 0
for hunk in s:hunks
if hunk[2] > current_line
let hunk_count += 1
if hunk_count == a:count
execute 'normal!' hunk[2] . 'G'
break
endif
endif
endfor
endif
endfunction
command -count=1 GitGutterNextHunk call GitGutterNextHunk(<count>)
function! GitGutterPrevHunk(count)
if utility#is_active()
let current_line = line('.')
let hunk_count = 0
for hunk in reverse(copy(s:hunks))
if hunk[2] < current_line
let hunk_count += 1
if hunk_count == a:count
execute 'normal!' hunk[2] . 'G'
break
endif
endif
endfor
endif
endfunction
command -count=1 GitGutterPrevHunk call GitGutterPrevHunk(<count>)
" }}}
" Hunks: stage/revert {{{
function! GitGutterStageHunk()
if utility#is_active()
" Ensure the working copy of the file is up to date.
" It doesn't make sense to stage a hunk otherwise.
silent write
" find current hunk (i.e. which one the cursor is in)
let current_hunk = []
let current_line = line('.')
for hunk in s:hunks
if current_line >= hunk[2] && current_line < hunk[2] + (hunk[3] == 0 ? 1 : hunk[3])
let current_hunk = hunk
break
endif
endfor
if len(current_hunk) != 4
return
endif
" construct a diff
let diff_for_hunk = diff#generate_diff_for_hunk(current_hunk)
" apply the diff
call system(utility#command_in_directory_of_file('git apply --cached --unidiff-zero - '), diff_for_hunk)
" refresh gitgutter's view of buffer
silent execute "GitGutter"
endif
endfunction
command GitGutterStageHunk call GitGutterStageHunk()
function! GitGutterRevertHunk()
if utility#is_active()
" Ensure the working copy of the file is up to date.
" It doesn't make sense to stage a hunk otherwise.
write
" find current hunk (i.e. which one the cursor is in)
let current_hunk = []
let current_line = line('.')
for hunk in s:hunks
if current_line >= hunk[2] && current_line < hunk[2] + (hunk[3] == 0 ? 1 : hunk[3])
let current_hunk = hunk
break
endif
endfor
if len(current_hunk) != 4
return
endif
" construct a diff
let diff_for_hunk = diff#generate_diff_for_hunk(current_hunk)
" apply the diff
call system(utility#command_in_directory_of_file('git apply --reverse --unidiff-zero - '), diff_for_hunk)
" reload file
silent edit
endif
endfunction
command GitGutterRevertHunk call GitGutterRevertHunk()
" }}}
" Hunk stats {{{
" Returns the git-diff hunks for the file or an empty list if there
" aren't any hunks.
"
" The return value is a list of lists. There is one inner list per hunk.
"
" [
" [from_line, from_count, to_line, to_count],
" [from_line, from_count, to_line, to_count],
" ...
" ]
"
" where:
"
" `from` - refers to the staged file
" `to` - refers to the working tree's file
" `line` - refers to the line number where the change starts
" `count` - refers to the number of lines the change covers
function! GitGutterGetHunks()
return utility#is_active() ? s:hunks : []
endfunction
" Returns an array that contains a summary of the current hunk status.
" The format is [ added, modified, removed ], where each value represents
" the number of lines added/modified/removed respectively.
function! GitGutterGetHunkSummary()
return hunk#summary()
endfunction
" }}}
" Maps {{{
nnoremap <silent> <expr> <Plug>GitGutterNextHunk &diff ? ']c' : ":\<C-U>execute v:count1 . 'GitGutterNextHunk'\<CR>"
nnoremap <silent> <expr> <Plug>GitGutterPrevHunk &diff ? '[c' : ":\<C-U>execute v:count1 . 'GitGutterPrevHunk'\<CR>"
if g:gitgutter_map_keys
if !hasmapto('<Plug>GitGutterPrevHunk') && maparg('[c', 'n') ==# ''
nmap [c <Plug>GitGutterPrevHunk
endif
if !hasmapto('<Plug>GitGutterNextHunk') && maparg(']c', 'n') ==# ''
nmap ]c <Plug>GitGutterNextHunk
endif
endif
nnoremap <silent> <Plug>GitGutterStageHunk :GitGutterStageHunk<CR>
nnoremap <silent> <Plug>GitGutterRevertHunk :GitGutterRevertHunk<CR>
if g:gitgutter_map_keys
if !hasmapto('<Plug>GitGutterStageHunk') && maparg('<Leader>hs', 'n') ==# ''
nmap <Leader>hs <Plug>GitGutterStageHunk
endif
if !hasmapto('<Plug>GitGutterRevertHunk') && maparg('<Leader>hr', 'n') ==# ''
nmap <Leader>hr <Plug>GitGutterRevertHunk
endif
endif
" }}}
" Autocommands {{{
augroup gitgutter
autocmd!
if g:gitgutter_realtime
autocmd CursorHold,CursorHoldI * call GitGutter(utility#current_file(), 1)
endif
if g:gitgutter_eager
autocmd BufEnter,BufWritePost,FileChangedShellPost *
\ if gettabvar(tabpagenr(), 'gitgutter_didtabenter')
\| call settabvar(tabpagenr(), 'gitgutter_didtabenter', 0)
\| else
\| call GitGutter(utility#current_file(), 0)
\| endif
autocmd TabEnter *
\ call settabvar(tabpagenr(), 'gitgutter_didtabenter', 1)
\| call GitGutterAll()
if !has('gui_win32')
autocmd FocusGained * call GitGutterAll()
endif
else
autocmd BufRead,BufWritePost,FileChangedShellPost * call GitGutter(utility#current_file(), 0)
endif
autocmd ColorScheme * call highlight#define_sign_column_highlight() | call highlight#define_highlights()
" Disable during :vimgrep
autocmd QuickFixCmdPre *vimgrep* let g:gitgutter_enabled = 0
autocmd QuickFixCmdPost *vimgrep* let g:gitgutter_enabled = 1
augroup END
" }}}
" vim:set et sw=2 fdm=marker: