Asynchronous diffs in Vim.

This commit is contained in:
Andy Stewart
2016-04-25 15:40:49 +01:00
parent a13478c7d6
commit e607a997ce
7 changed files with 119 additions and 61 deletions

View File

@@ -5,7 +5,8 @@ A Vim plugin which shows a git diff in the 'gutter' (sign column). It shows whe
Features: Features:
* Shows signs for added, modified, and removed lines. * Shows signs for added, modified, and removed lines.
* Neovim: runs the diffs asynchronously. * Runs the diffs asynchronously in Vim (7.4.1791+) and NeoVim.
- MacVim in GUI mode runs synchronously because it doesn't support Vim's async operations yet ([macvim#272](/macvim-dev/macvim/issues/272)).
* Ensures signs are always as up to date as possible (but without running more than necessary). * Ensures signs are always as up to date as possible (but without running more than necessary).
* Quick jumping between blocks of changed lines ("hunks"). * Quick jumping between blocks of changed lines ("hunks").
* Stage/undo/preview individual hunks. * Stage/undo/preview individual hunks.
@@ -202,7 +203,7 @@ You can customise:
* Whether or not line highlighting is on initially (defaults to off) * Whether or not line highlighting is on initially (defaults to off)
* Whether or not vim-gitgutter runs in "realtime" (defaults to yes) * Whether or not vim-gitgutter runs in "realtime" (defaults to yes)
* Whether or not vim-gitgutter runs eagerly (defaults to yes) * Whether or not vim-gitgutter runs eagerly (defaults to yes)
* Whether or not vim-gitgutter runs asynchronously in NeoVim (defaults to yes) * Whether or not vim-gitgutter runs asynchronously (defaults to yes)
Please note that vim-gitgutter won't override any colours or highlights you've set in your colorscheme. Please note that vim-gitgutter won't override any colours or highlights you've set in your colorscheme.
@@ -317,7 +318,7 @@ Add `let g:gitgutter_highlight_lines = 1` to your `~/.vimrc`.
#### To turn off asynchronous updates #### To turn off asynchronous updates
By default diffs are run asynchronously in NeoVim. To run diffs synchronously instead: By default diffs are run asynchronously. To run diffs synchronously instead:
```viml ```viml
let g:gitgutter_async = 0 let g:gitgutter_async = 0

View File

@@ -34,30 +34,6 @@ function! gitgutter#process_buffer(bufnr, realtime)
endfunction endfunction
function! gitgutter#handle_diff_job(job_id, data, event)
call gitgutter#debug#log('job_id: '.a:job_id.', event: '.a:event)
if a:event == 'stdout'
" a:data is a list
call gitgutter#utility#job_output_received(a:job_id, 'stdout')
call gitgutter#handle_diff(join(a:data,"\n")."\n")
elseif a:event == 'exit'
" If the exit event is triggered without a preceding stdout event,
" the diff was empty.
if gitgutter#utility#is_pending_job(a:job_id)
call gitgutter#handle_diff("")
call gitgutter#utility#job_output_received(a:job_id, 'exit')
endif
else
call gitgutter#hunk#reset()
call gitgutter#utility#job_output_received(a:job_id, 'stderr')
endif
endfunction
function! gitgutter#handle_diff(diff) function! gitgutter#handle_diff(diff)
call gitgutter#debug#log(a:diff) call gitgutter#debug#log(a:diff)

View File

@@ -0,0 +1,109 @@
let s:jobs = {}
" Async broken on MacVim in GUI mode:
" https://github.com/macvim-dev/macvim/issues/272
let s:available = has('nvim') || (has('patch-7-4-1791') && !(has('gui_macvim') && has('gui_running')))
function! gitgutter#async#available()
return s:available
endfunction
function! gitgutter#async#execute(cmd)
if has('nvim')
let job_id = jobstart([&shell, &shellcmdflag, a:cmd], {
\ 'on_stdout': function('gitgutter#async#handle_diff_job_nvim'),
\ 'on_stderr': function('gitgutter#async#handle_diff_job_nvim'),
\ 'on_exit': function('gitgutter#async#handle_diff_job_nvim')
\ })
call gitgutter#debug#log('[nvim job: '.job_id.'] '.a:cmd)
if job_id < 1
throw 'diff failed'
endif
" Note that when `cmd` doesn't produce any output, i.e. the diff is empty,
" the `stdout` event is not fired on the job handler. Therefore we keep
" track of the jobs ourselves so we can spot empty diffs.
call s:job_started(job_id)
else
" Pass a handler for stdout but not for stderr so that errors are
" ignored (and thus signs are not updated; this assumes that an error
" only occurs when a file is not tracked by git).
let job = job_start([&shell, &shellcmdflag, a:cmd], {
\ 'out_cb': 'gitgutter#async#handle_diff_job_vim'
\ })
" \ 'close_cb': 'gitgutter#handle_diff_job_vim_close'
call gitgutter#debug#log('[vim job: '.string(job_info(job)).'] '.a:cmd)
endif
endfunction
function! gitgutter#async#handle_diff_job_nvim(job_id, data, event)
call gitgutter#debug#log('job_id: '.a:job_id.', event: '.a:event)
if a:event == 'stdout'
" a:data is a list
call s:job_finished(a:job_id)
call gitgutter#handle_diff(join(a:data,"\n")."\n")
elseif a:event == 'exit'
" If the exit event is triggered without a preceding stdout event,
" the diff was empty.
if s:is_job_started(a:job_id)
call gitgutter#handle_diff("")
call s:job_finished(a:job_id)
endif
else " a:event is stderr
call gitgutter#hunk#reset()
call s:job_finished(a:job_id)
endif
endfunction
" Channel is in NL mode.
function! gitgutter#async#handle_diff_job_vim(channel, line)
call gitgutter#debug#log('channel: '.a:channel.', line: '.a:line)
" This seems to be the only way to get info about the channel once closed.
let channel_id = matchstr(a:channel, '\d\+')
if a:line ==# 'DETACH' " End of job output
call gitgutter#handle_diff(s:job_output(channel_id))
call s:job_finished(channel_id)
else
call s:accumulate_job_output(channel_id, a:line)
endif
endfunction
function! s:job_started(id)
let s:jobs[a:id] = 1
endfunction
function! s:is_job_started(id)
return has_key(s:jobs, a:id)
endfunction
function! s:accumulate_job_output(id, line)
if has_key(s:jobs, a:id)
let s:jobs[a:id] = add(s:jobs[a:id], a:line)
else
let s:jobs[a:id] = [a:line]
endif
endfunction
" Returns a string
function! s:job_output(id)
if has_key(s:jobs, a:id)
return join(s:jobs[a:id], "\n")."\n"
else
return ""
endif
endfunction
function! s:job_finished(id)
if has_key(s:jobs, a:id)
unlet s:jobs[a:id]
endif
endfunction

View File

@@ -125,23 +125,10 @@ function! gitgutter#diff#run_diff(realtime, preserve_full_diff)
let cmd = gitgutter#utility#command_in_directory_of_file(cmd) let cmd = gitgutter#utility#command_in_directory_of_file(cmd)
if g:gitgutter_async && has('nvim') && !a:preserve_full_diff if g:gitgutter_async && gitgutter#async#available() && !a:preserve_full_diff
let job_id = jobstart([&shell, '-c', cmd], { call gitgutter#async#execute(cmd)
\ 'on_stdout': function('gitgutter#handle_diff_job'),
\ 'on_stderr': function('gitgutter#handle_diff_job'),
\ 'on_exit': function('gitgutter#handle_diff_job')
\ })
call gitgutter#debug#log('[job_id: '.job_id.'] '.cmd)
if job_id < 1
throw 'diff failed'
endif
" Note that when `cmd` doesn't produce any output, i.e. the diff is empty,
" the `stdout` event is not fired on the job handler. Therefore we keep
" track of the jobs ourselves so we can spot empty diffs.
call gitgutter#utility#pending_job(job_id)
return 'async' return 'async'
else else
let diff = gitgutter#utility#system(cmd) let diff = gitgutter#utility#system(cmd)

View File

@@ -2,7 +2,6 @@ let s:file = ''
let s:using_xolox_shell = -1 let s:using_xolox_shell = -1
let s:exit_code = 0 let s:exit_code = 0
let s:fish = &shell =~# 'fish' let s:fish = &shell =~# 'fish'
let s:jobs = {}
function! gitgutter#utility#warn(message) function! gitgutter#utility#warn(message)
echohl WarningMsg echohl WarningMsg
@@ -170,20 +169,6 @@ function! gitgutter#utility#strip_trailing_new_line(line)
return substitute(a:line, '\n$', '', '') return substitute(a:line, '\n$', '', '')
endfunction endfunction
function! gitgutter#utility#pending_job(job_id)
let s:jobs[a:job_id] = 1
endfunction
function! gitgutter#utility#is_pending_job(job_id)
return has_key(s:jobs, a:job_id)
endfunction
function! gitgutter#utility#job_output_received(job_id, event)
if has_key(s:jobs, a:job_id)
unlet s:jobs[a:job_id]
endif
endfunction
function! gitgutter#utility#git_version() function! gitgutter#utility#git_version()
return matchstr(system('git --version'), '[0-9.]\+') return matchstr(system('git --version'), '[0-9.]\+')
endfunction endfunction

View File

@@ -126,7 +126,7 @@ You can customise:
- Whether or not line highlighting is on initially (defaults to off) - Whether or not line highlighting is on initially (defaults to off)
- Whether or not vim-gitgutter runs in realtime (defaults to yes) - Whether or not vim-gitgutter runs in realtime (defaults to yes)
- Whether or not vim-gitgutter runs eagerly (defaults to yes) - Whether or not vim-gitgutter runs eagerly (defaults to yes)
- Whether or not vim-gitgutter runs asynchronously in NeoVim (defaults to yes) - Whether or not vim-gitgutter runs asynchronously (defaults to yes)
Please note that vim-gitgutter won't override any colours or highlights you've Please note that vim-gitgutter won't override any colours or highlights you've
set in your colorscheme. set in your colorscheme.
@@ -277,7 +277,7 @@ Add to your |vimrc|
TO TURN OFF ASYNCHRONOUS UPDATES TO TURN OFF ASYNCHRONOUS UPDATES
By default diffs are run asynchronously in NeoVim. To run diffs synchronously By default diffs are run asynchronously. To run diffs synchronously
instead: instead:
Add to your |vimrc| Add to your |vimrc|

View File

@@ -4,7 +4,7 @@ VIM="/Applications/MacVim.app/Contents/MacOS/Vim -v"
# Execute the tests. # Execute the tests.
for testcase in test*.vim; do for testcase in test*.vim; do
$VIM -N -u NONE -S $testcase -c 'quit!' $VIM -N -u NONE --cmd 'let g:gitgutter_async=0' -S $testcase -c 'quit!'
git reset HEAD fixture.txt > /dev/null git reset HEAD fixture.txt > /dev/null
git checkout fixture.txt git checkout fixture.txt