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

@@ -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)
if g:gitgutter_async && has('nvim') && !a:preserve_full_diff
let job_id = jobstart([&shell, '-c', 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)
if g:gitgutter_async && gitgutter#async#available() && !a:preserve_full_diff
call gitgutter#async#execute(cmd)
return 'async'
else
let diff = gitgutter#utility#system(cmd)

View File

@@ -2,7 +2,6 @@ let s:file = ''
let s:using_xolox_shell = -1
let s:exit_code = 0
let s:fish = &shell =~# 'fish'
let s:jobs = {}
function! gitgutter#utility#warn(message)
echohl WarningMsg
@@ -170,20 +169,6 @@ function! gitgutter#utility#strip_trailing_new_line(line)
return substitute(a:line, '\n$', '', '')
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()
return matchstr(system('git --version'), '[0-9.]\+')
endfunction