370 lines
12 KiB
VimL
370 lines
12 KiB
VimL
|
" MIT License. Copyright (c) 2013-2021 Bailey Ling et al.
|
||
|
" Plugin: fugitive, gina, lawrencium and vcscommand
|
||
|
" vim: et ts=2 sts=2 sw=2
|
||
|
|
||
|
scriptencoding utf-8
|
||
|
|
||
|
" 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 -- ',
|
||
|
\ 'dirty': 'git status -uno --porcelain --ignore-submodules',
|
||
|
\ 'untracked_mark': '??',
|
||
|
\ 'exclude': '\.git',
|
||
|
\ 'update_branch': 's:update_git_branch',
|
||
|
\ 'display_branch': 's:display_git_branch',
|
||
|
\ 'branch': '',
|
||
|
\ 'untracked': {},
|
||
|
\ },
|
||
|
\ 'mercurial': {
|
||
|
\ 'exe': 'hg',
|
||
|
\ 'cmd': 'hg status -u -- ',
|
||
|
\ 'dirty': 'hg status -mard',
|
||
|
\ 'untracked_mark': '?',
|
||
|
\ 'exclude': '\.hg',
|
||
|
\ 'update_branch': 's:update_hg_branch',
|
||
|
\ 'display_branch': 's:display_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': '',
|
||
|
\ 'dirty': 0,
|
||
|
\ }
|
||
|
endfor
|
||
|
unlet! b:airline_head
|
||
|
endfunction
|
||
|
|
||
|
let s:head_format = get(g:, 'airline#extensions#branch#format', 0)
|
||
|
if s:head_format == 1
|
||
|
function! s:format_name(name)
|
||
|
return fnamemodify(a:name, ':t')
|
||
|
endfunction
|
||
|
elseif s:head_format == 2
|
||
|
function! s:format_name(name)
|
||
|
return pathshorten(a:name)
|
||
|
endfunction
|
||
|
elseif type(s:head_format) == type('')
|
||
|
function! s:format_name(name)
|
||
|
return call(s:head_format, [a:name])
|
||
|
endfunction
|
||
|
else
|
||
|
function! s:format_name(name)
|
||
|
return a:name
|
||
|
endfunction
|
||
|
endif
|
||
|
|
||
|
|
||
|
" Fugitive special revisions. call '0' "staging" ?
|
||
|
let s:names = {'0': 'index', '1': 'orig', '2':'fetch', '3':'merge'}
|
||
|
let s:sha1size = get(g:, 'airline#extensions#branch#sha1_len', 7)
|
||
|
|
||
|
function! s:update_git_branch()
|
||
|
call airline#util#ignore_next_focusgain()
|
||
|
if airline#util#has_fugitive()
|
||
|
call s:config_fugitive_branch()
|
||
|
elseif airline#util#has_gina()
|
||
|
call s:config_gina_branch()
|
||
|
else
|
||
|
let s:vcs_config['git'].branch = ''
|
||
|
return
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:config_fugitive_branch() abort
|
||
|
let s:vcs_config['git'].branch = FugitiveHead(s:sha1size)
|
||
|
if s:vcs_config['git'].branch is# 'master' &&
|
||
|
\ airline#util#winwidth() < 81
|
||
|
" Shorten default a bit
|
||
|
let s:vcs_config['git'].branch='mas'
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:config_gina_branch() abort
|
||
|
try
|
||
|
let g:gina#component#repo#commit_length = s:sha1size
|
||
|
let s:vcs_config['git'].branch = gina#component#repo#branch()
|
||
|
catch
|
||
|
endtry
|
||
|
if s:vcs_config['git'].branch is# 'master' &&
|
||
|
\ airline#util#winwidth() < 81
|
||
|
" Shorten default a bit
|
||
|
let s:vcs_config['git'].branch='mas'
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:display_git_branch()
|
||
|
let name = b:buffer_vcs_config['git'].branch
|
||
|
try
|
||
|
let commit = matchstr(FugitiveParse()[0], '^\x\+')
|
||
|
|
||
|
if has_key(s:names, commit)
|
||
|
let name = get(s:names, commit)."(".name.")"
|
||
|
elseif !empty(commit)
|
||
|
if exists('*FugitiveExecute')
|
||
|
let ref = FugitiveExecute(['describe', '--all', '--exact-match', commit], bufnr('')).stdout[0]
|
||
|
else
|
||
|
noautocmd let ref = fugitive#repo().git_chomp('describe', '--all', '--exact-match', commit)
|
||
|
if ref =~# ':'
|
||
|
let ref = ''
|
||
|
endif
|
||
|
endif
|
||
|
if !empty(ref)
|
||
|
let name = s:format_name(substitute(ref, '\v\C^%(heads/|remotes/|tags/)=','',''))."(".name.")"
|
||
|
else
|
||
|
let name = matchstr(commit, '.\{'.s:sha1size.'}')."(".name.")"
|
||
|
endif
|
||
|
endif
|
||
|
catch
|
||
|
endtry
|
||
|
return name
|
||
|
endfunction
|
||
|
|
||
|
function! s:update_hg_branch()
|
||
|
if airline#util#has_lawrencium()
|
||
|
let cmd='LC_ALL=C hg qtop'
|
||
|
let stl=lawrencium#statusline()
|
||
|
let file=expand('%:p')
|
||
|
if !empty(stl) && get(b:, 'airline_do_mq_check', 1)
|
||
|
if g:airline#init#vim_async
|
||
|
noa call airline#async#get_mq_async(cmd, file)
|
||
|
elseif has("nvim")
|
||
|
noa call airline#async#nvim_get_mq_async(cmd, file)
|
||
|
else
|
||
|
" remove \n at the end of the command
|
||
|
let output=system(cmd)[0:-2]
|
||
|
noa call airline#async#mq_output(output, file)
|
||
|
endif
|
||
|
endif
|
||
|
" do not do mq check anymore
|
||
|
let b:airline_do_mq_check = 0
|
||
|
if exists("b:mq") && !empty(b:mq)
|
||
|
if stl is# 'default'
|
||
|
" Shorten default a bit
|
||
|
let stl='def'
|
||
|
endif
|
||
|
let stl.=' ['.b:mq.']'
|
||
|
endif
|
||
|
let s:vcs_config['mercurial'].branch = stl
|
||
|
else
|
||
|
let s:vcs_config['mercurial'].branch = ''
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:display_hg_branch()
|
||
|
return b:buffer_vcs_config['mercurial'].branch
|
||
|
endfunction
|
||
|
|
||
|
function! s:update_branch()
|
||
|
for vcs in keys(s:vcs_config)
|
||
|
call {s:vcs_config[vcs].update_branch}()
|
||
|
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! airline#extensions#branch#update_untracked_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 file = expand("%:p")
|
||
|
if empty(file) || isdirectory(file) || !empty(&buftype)
|
||
|
return
|
||
|
endif
|
||
|
|
||
|
let needs_update = 1
|
||
|
let vcs_checks = get(g:, "airline#extensions#branch#vcs_checks", ["untracked", "dirty"])
|
||
|
for vcs in keys(s:vcs_config)
|
||
|
if file =~ s:vcs_config[vcs].exclude
|
||
|
" Skip check for files that live in the exclude directory
|
||
|
let needs_update = 0
|
||
|
endif
|
||
|
if has_key(s:vcs_config[vcs].untracked, file)
|
||
|
let needs_update = 0
|
||
|
call airline#extensions#branch#update_untracked_config(file, vcs)
|
||
|
endif
|
||
|
endfor
|
||
|
|
||
|
if !needs_update
|
||
|
return
|
||
|
endif
|
||
|
|
||
|
for vcs in keys(s:vcs_config)
|
||
|
" only check, for git, if fugitive is installed
|
||
|
" and for 'hg' if lawrencium is installed, else skip
|
||
|
if vcs is# 'git' && (!airline#util#has_fugitive() && !airline#util#has_gina())
|
||
|
continue
|
||
|
elseif vcs is# 'mercurial' && !airline#util#has_lawrencium()
|
||
|
continue
|
||
|
endif
|
||
|
let config = s:vcs_config[vcs]
|
||
|
" 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.
|
||
|
if index(vcs_checks, 'untracked') > -1
|
||
|
call airline#async#vcs_untracked(config, file, vcs)
|
||
|
endif
|
||
|
" Check clean state of repo
|
||
|
if index(vcs_checks, 'dirty') > -1
|
||
|
call airline#async#vcs_clean(config.dirty, file, vcs)
|
||
|
endif
|
||
|
endfor
|
||
|
endfunction
|
||
|
|
||
|
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)
|
||
|
return b:airline_head
|
||
|
endif
|
||
|
|
||
|
let b:airline_head = ''
|
||
|
let vcs_priority = get(g:, "airline#extensions#branch#vcs_priority", ["git", "mercurial"])
|
||
|
|
||
|
let heads = []
|
||
|
for vcs in vcs_priority
|
||
|
if !empty(b:buffer_vcs_config[vcs].branch)
|
||
|
let heads += [vcs]
|
||
|
endif
|
||
|
endfor
|
||
|
|
||
|
for vcs in heads
|
||
|
if !empty(b:airline_head)
|
||
|
let b:airline_head .= ' | '
|
||
|
endif
|
||
|
if len(heads) > 1
|
||
|
let b:airline_head .= s:vcs_config[vcs].exe .':'
|
||
|
endif
|
||
|
let b:airline_head .= s:format_name({s:vcs_config[vcs].display_branch}())
|
||
|
let additional = b:buffer_vcs_config[vcs].untracked
|
||
|
if empty(additional) &&
|
||
|
\ has_key(b:buffer_vcs_config[vcs], 'dirty') &&
|
||
|
\ b:buffer_vcs_config[vcs].dirty
|
||
|
let additional = g:airline_symbols['dirty']
|
||
|
endif
|
||
|
let b:airline_head .= additional
|
||
|
endfor
|
||
|
|
||
|
if empty(heads)
|
||
|
if airline#util#has_vcscommand()
|
||
|
noa call VCSCommandEnableBufferSetup()
|
||
|
if exists('b:VCSCommandBufferInfo')
|
||
|
let b:airline_head = s:format_name(get(b:VCSCommandBufferInfo, 0, ''))
|
||
|
endif
|
||
|
endif
|
||
|
endif
|
||
|
|
||
|
if empty(heads)
|
||
|
if airline#util#has_custom_scm()
|
||
|
try
|
||
|
let Fn = function(g:airline#extensions#branch#custom_head)
|
||
|
let b:airline_head = Fn()
|
||
|
endtry
|
||
|
endif
|
||
|
endif
|
||
|
|
||
|
if exists("g:airline#extensions#branch#displayed_head_limit")
|
||
|
let w:displayed_head_limit = g:airline#extensions#branch#displayed_head_limit
|
||
|
if strwidth(b:airline_head) > w:displayed_head_limit - 1
|
||
|
let b:airline_head =
|
||
|
\ airline#util#strcharpart(b:airline_head, 0, w:displayed_head_limit - 1)
|
||
|
\ . (&encoding ==? 'utf-8' ? '…' : '.')
|
||
|
endif
|
||
|
endif
|
||
|
|
||
|
return b:airline_head
|
||
|
endfunction
|
||
|
|
||
|
function! airline#extensions#branch#get_head()
|
||
|
let head = airline#extensions#branch#head()
|
||
|
let winwidth = get(airline#parts#get('branch'), 'minwidth', 120)
|
||
|
let minwidth = empty(get(b:, 'airline_hunks', '')) ? 14 : 7
|
||
|
let head = airline#util#shorten(head, winwidth, minwidth)
|
||
|
let symbol = get(g:, 'airline#extensions#branch#symbol', g:airline_symbols.branch)
|
||
|
return empty(head)
|
||
|
\ ? get(g:, 'airline#extensions#branch#empty_message', '')
|
||
|
\ : printf('%s%s', empty(symbol) ? '' : symbol.(g:airline_symbols.space), head)
|
||
|
endfunction
|
||
|
|
||
|
function! s:reset_untracked_cache(shellcmdpost)
|
||
|
" shellcmdpost - whether function was called as a result of ShellCmdPost hook
|
||
|
if !exists('#airline')
|
||
|
" airline disabled
|
||
|
return
|
||
|
endif
|
||
|
if !g:airline#init#vim_async && !has('nvim')
|
||
|
if a:shellcmdpost
|
||
|
" Clear cache only if there was no error or the script uses an
|
||
|
" asynchronous interface. Otherwise, cache clearing would overwrite
|
||
|
" v:shell_error with a system() call inside get_*_untracked.
|
||
|
if v:shell_error
|
||
|
return
|
||
|
endif
|
||
|
endif
|
||
|
endif
|
||
|
|
||
|
let 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 airline#extensions#branch#update_untracked_config(file, vcs)
|
||
|
let s:vcs_config[vcs].untracked = {}
|
||
|
endfor
|
||
|
endfunction
|
||
|
|
||
|
function! s:sh_autocmd_handler()
|
||
|
if exists('#airline')
|
||
|
unlet! b:airline_head b:airline_do_mq_check
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! airline#extensions#branch#init(ext)
|
||
|
call airline#parts#define_function('branch', 'airline#extensions#branch#get_head')
|
||
|
|
||
|
autocmd ShellCmdPost,CmdwinLeave * call s:sh_autocmd_handler()
|
||
|
autocmd User AirlineBeforeRefresh call s:sh_autocmd_handler()
|
||
|
autocmd BufWritePost * call s:reset_untracked_cache(0)
|
||
|
autocmd ShellCmdPost * call s:reset_untracked_cache(1)
|
||
|
endfunction
|