Adapt data structures of branch.vim to async

* The head string is now calculated iff it has changed.
* The not exists symbol for current file appears as soon as its status is known.
* Fixes various problems with asynchronous status checking, such as:
  * The not exists symbol keeps appearing and disapearing. This happened when
    file was marked as not existing, the untracked cache was invalidated, and
    the cache update is started, but in the meantime, the head string
    calculation used the current (empty) value of the cache.
  * The not exists symbol never appears, because cache keeps getting invalidated
    before b:airline_head is emptied and updated.

closes #1306
This commit is contained in:
Grzegorz Milka 2016-10-30 09:48:24 +01:00 committed by Christian Brabandt
parent 02ecb8631d
commit 3899f848a7
1 changed files with 143 additions and 64 deletions

View File

@ -12,7 +12,55 @@ if !s:has_fugitive && !s:has_lawrencium && !s:has_vcscommand
endif endif
let s:has_async = airline#util#async let s:has_async = airline#util#async
let s:git_dirs = {}
" s:vcs_config contains static configuration of VCSes and their status relative
" to the active file.
" 'branch' - The name of currently active branch. This field is empty iff it
" has not been initialized yet or the current file is not in
" an active branch.
" 'untracked' - Cache of untracked files represented as a dictionary with files
" as keys. A file has a not exists symbol set as its value if it
" is untracked. A file is present in this dictionary iff its
" status is considered up to date.
" 'untracked_mark' - used as regexp to test against the output of 'cmd'
let s:vcs_config = {
\ 'git': {
\ 'exe': 'git',
\ 'cmd': 'git status --porcelain -- ',
\ 'untracked_mark': '??',
\ 'update_branch': 's:update_git_branch',
\ 'branch': '',
\ 'untracked': {},
\ },
\ 'mercurial': {
\ 'exe': 'hg',
\ 'cmd': 'hg status -u -- ',
\ 'untracked_mark': '?',
\ 'update_branch': 's:update_hg_branch',
\ 'branch': '',
\ 'untracked': {},
\ },
\}
" Initializes b:buffer_vcs_config. b:buffer_vcs_config caches the branch and
" untracked status of the file in the buffer. Caching those fields is necessary,
" because s:vcs_config may be updated asynchronously and s:vcs_config fields may
" be invalid during those updates. b:buffer_vcs_config fields are updated
" whenever corresponding fields in s:vcs_config are updated or an inconsistency
" is detected during update_* operation.
"
" b:airline_head caches the head string it is empty iff it needs to be
" recalculated. b:airline_head is recalculated based on b:buffer_vcs_config.
function! s:init_buffer()
let b:buffer_vcs_config = {}
for vcs in keys(s:vcs_config)
let b:buffer_vcs_config[vcs] = {
\ 'branch': '',
\ 'untracked': '',
\ }
endfor
unlet! b:airline_head
endfunction
let s:head_format = get(g:, 'airline#extensions#branch#format', 0) let s:head_format = get(g:, 'airline#extensions#branch#format', 0)
if s:head_format == 1 if s:head_format == 1
@ -33,15 +81,19 @@ else
endfunction endfunction
endif endif
function! s:get_git_branch(path) let s:git_dirs = {}
function! s:update_git_branch(path)
if !s:has_fugitive if !s:has_fugitive
return '' let s:vcs_config['git'].branch = ''
return
endif endif
let name = fugitive#head(7) let name = fugitive#head(7)
if empty(name) if empty(name)
if has_key(s:git_dirs, a:path) if has_key(s:git_dirs, a:path)
return s:git_dirs[a:path] let s:vcs_config['git'].branch = s:git_dirs[a:path]
return
endif endif
let dir = fugitive#extract_git_dir(a:path) let dir = fugitive#extract_git_dir(a:path)
@ -63,50 +115,10 @@ function! s:get_git_branch(path)
endif endif
let s:git_dirs[a:path] = name let s:git_dirs[a:path] = name
return name let s:vcs_config['git'].branch = name
endfunction endfunction
" 'untracked' - dictionary with files as keys. A file has a not exists symbol function! s:update_hg_branch(path)
" set as its value if it is untracked.
" untracked_mark is taken as regex!
let s:vcs_config = {
\ 'git': {
\ 'exe': 'git',
\ 'cmd': 'git status --porcelain -- ',
\ 'untracked_mark': '??',
\ 'get_branch': 's:get_git_branch',
\ 'untracked': {},
\ },
\ 'mercurial': {
\ 'exe': 'hg',
\ 'cmd': 'hg status -u -- ',
\ 'untracked_mark': '?',
\ 'get_branch': 's:get_hg_branch',
\ 'untracked': {},
\ },
\}
function! s:get_untracked(file, config)
" Assigns the notexists symbol to 'file's entry in the untracked cache if
" 'file' is indeed untracked by current VCS.
" 'config' is this script's configuration of the VCS.
if empty(a:file) || !executable(a:config['exe'])
return
endif
if s:has_async
call s:get_vcs_untracked_async(a:config, a:file)
else
let output = system(a:config['cmd'] . shellescape(a:file))
if output =~? ('^' . a:config['untracked_mark'])
let a:config['untracked'][a:file] = get(g:, 'airline#extensions#branch#notexists', g:airline_symbols.notexists)
else
let a:config['untracked'][a:file] = ''
endif
endif
endfunction
function! s:get_hg_branch(path)
if s:has_lawrencium if s:has_lawrencium
let stl=lawrencium#statusline() let stl=lawrencium#statusline()
if !empty(stl) && s:has_async if !empty(stl) && s:has_async
@ -119,9 +131,69 @@ function! s:get_hg_branch(path)
endif endif
let stl.=' ['.s:mq.']' let stl.=' ['.s:mq.']'
endif endif
return stl s:vcs_config['mercurial'].branch = stl
else
let s:vcs_config['mercurial'].branch = ''
endif endif
return '' endfunction
function! s:update_branch()
let l:path = exists("*fnamemodify") ? fnamemodify(resolve(@%), ":p:h") : expand("%:p:h")
for vcs in keys(s:vcs_config)
call {s:vcs_config[vcs].update_branch}(l:path)
if b:buffer_vcs_config[vcs].branch != s:vcs_config[vcs].branch
let b:buffer_vcs_config[vcs].branch = s:vcs_config[vcs].branch
unlet! b:airline_head
endif
endfor
endfunction
function! s:update_untracked_in_buffer_config(file, vcs)
if !has_key(s:vcs_config[a:vcs].untracked, a:file)
return
elseif s:vcs_config[a:vcs].untracked[a:file] != b:buffer_vcs_config[a:vcs].untracked
let b:buffer_vcs_config[a:vcs].untracked = s:vcs_config[a:vcs].untracked[a:file]
unlet! b:airline_head
endif
endfunction
function! s:update_untracked()
let l:file = expand("%:p")
if empty(l:file) || isdirectory(l:file)
return
endif
let l:needs_update = 1
for vcs in keys(s:vcs_config)
if has_key(s:vcs_config[vcs].untracked, l:file)
let l:needs_update = 0
call s:update_untracked_in_buffer_config(l:file, vcs)
endif
endfor
if !l:needs_update
return
endif
for vcs in keys(s:vcs_config)
let l:config = s:vcs_config[vcs]
if s:has_async
" Note that asynchronous update updates s:vcs_config only, and only
" s:update_untracked updates b:buffer_vcs_config. If s:vcs_config is
" invalidated again before s:update_untracked is called, then we lose the
" result of the previous call, i.e. the head string is not updated. It
" doesn't happen often in practice, so we let it be.
call s:get_vcs_untracked_async(l:config, l:file)
else
let output = system(l:config.cmd . shellescape(l:file))
if output =~? ('^' . l:config.untracked_mark)
let l:config.untracked[l:file] = get(g:, 'airline#extensions#branch#notexists', g:airline_symbols.notexists)
else
let l:config.untracked[l:file] = ''
endif
call s:update_untracked_in_buffer_config(l:file, vcs)
endif
endfor
endfunction endfunction
if s:has_async if s:has_async
@ -133,10 +205,12 @@ if s:has_async
function! s:on_exit(channel) dict abort function! s:on_exit(channel) dict abort
if self.buf =~? ('^' . self.config['untracked_mark']) if self.buf =~? ('^' . self.config['untracked_mark'])
let self.config['untracked'][self.file] = get(g:, 'airline#extensions#branch#notexists', g:airline_symbols.notexists) let self.config.untracked[self.file] = get(g:, 'airline#extensions#branch#notexists', g:airline_symbols.notexists)
else else
let self.config['untracked'][self.file] = '' let self.config.untracked[self.file] = ''
endif endif
" b:buffer_vcs_config will be updated on next call of update_untracked if
" needed
if has_key(s:jobs, self.file) if has_key(s:jobs, self.file)
call remove(s:jobs, self.file) call remove(s:jobs, self.file)
endif endif
@ -204,34 +278,33 @@ if s:has_async
endif endif
function! airline#extensions#branch#head() function! airline#extensions#branch#head()
if !exists('b:buffer_vcs_config')
call s:init_buffer()
endif
call s:update_branch()
call s:update_untracked()
if exists('b:airline_head') && !empty(b:airline_head) if exists('b:airline_head') && !empty(b:airline_head)
return b:airline_head return b:airline_head
endif endif
let b:airline_head = '' let b:airline_head = ''
let l:vcs_priority = get(g:, "airline#extensions#branch#vcs_priority", ["git", "mercurial"]) let l:vcs_priority = get(g:, "airline#extensions#branch#vcs_priority", ["git", "mercurial"])
let l:heads = {}
let l:heads = {}
for vcs in l:vcs_priority for vcs in l:vcs_priority
let l:path = exists("*fnamemodify") ? fnamemodify(resolve(@%), ":p:h") : expand("%:p:h") if !empty(b:buffer_vcs_config[vcs].branch)
let l:head = {s:vcs_config[vcs].get_branch}(l:path) let l:heads[vcs] = b:buffer_vcs_config[vcs].branch
if !empty(l:head)
let l:heads[vcs] = l:head
endif endif
endfor endfor
let l:file = expand("%:p")
" Do not get untracked flag if we are modifying a directory.
let l:is_file_and_not_dir = !isdirectory(l:file)
for vcs in keys(l:heads) for vcs in keys(l:heads)
if !empty(b:airline_head) if !empty(b:airline_head)
let b:airline_head .= ' | ' let b:airline_head .= ' | '
endif endif
let b:airline_head .= (len(l:heads) > 1 ? s:vcs_config[l:vcs].exe : '') . s:format_name(l:heads[l:vcs]) let b:airline_head .= (len(l:heads) > 1 ? s:vcs_config[l:vcs].exe : '') . s:format_name(l:heads[l:vcs])
if l:is_file_and_not_dir let b:airline_head .= b:buffer_vcs_config[vcs].untracked
call s:get_untracked(l:file, s:vcs_config[l:vcs])
let b:airline_head .= get(s:vcs_config[l:vcs]['untracked'], l:file, '')
endif
endfor endfor
if empty(l:heads) if empty(l:heads)
@ -302,8 +375,14 @@ function! s:reset_untracked_cache(shellcmdpost)
endif endif
endif endif
endif endif
for vcs in ["git", "mercurial"]
let s:vcs_config[vcs]['untracked'] = {} let l:file = expand("%:p")
for vcs in keys(s:vcs_config)
" Dump the value of the cache for the current file. Partially mitigates the
" issue of cache invalidation happening before a call to
" s:update_untracked()
call s:update_untracked_in_buffer_config(l:file, l:vcs)
let s:vcs_config[vcs].untracked = {}
endfor endfor
endfunction endfunction