branch.vim 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. " MIT License. Copyright (c) 2013-2021 Bailey Ling et al.
  2. " Plugin: fugitive, gina, lawrencium and vcscommand
  3. " vim: et ts=2 sts=2 sw=2
  4. scriptencoding utf-8
  5. " s:vcs_config contains static configuration of VCSes and their status relative
  6. " to the active file.
  7. " 'branch' - The name of currently active branch. This field is empty iff it
  8. " has not been initialized yet or the current file is not in
  9. " an active branch.
  10. " 'untracked' - Cache of untracked files represented as a dictionary with files
  11. " as keys. A file has a not exists symbol set as its value if it
  12. " is untracked. A file is present in this dictionary iff its
  13. " status is considered up to date.
  14. " 'untracked_mark' - used as regexp to test against the output of 'cmd'
  15. let s:vcs_config = {
  16. \ 'git': {
  17. \ 'exe': 'git',
  18. \ 'cmd': 'git --no-optional-locks status --porcelain -- ',
  19. \ 'dirty': 'git --no-optional-locks status -uno --porcelain --ignore-submodules',
  20. \ 'untracked_mark': '??',
  21. \ 'exclude': '\.git',
  22. \ 'update_branch': 's:update_git_branch',
  23. \ 'display_branch': 's:display_git_branch',
  24. \ 'branch': '',
  25. \ 'untracked': {},
  26. \ },
  27. \ 'mercurial': {
  28. \ 'exe': 'hg',
  29. \ 'cmd': 'hg status -u -- ',
  30. \ 'dirty': 'hg status -mard',
  31. \ 'untracked_mark': '?',
  32. \ 'exclude': '\.hg',
  33. \ 'update_branch': 's:update_hg_branch',
  34. \ 'display_branch': 's:display_hg_branch',
  35. \ 'branch': '',
  36. \ 'untracked': {},
  37. \ },
  38. \}
  39. " Initializes b:buffer_vcs_config. b:buffer_vcs_config caches the branch and
  40. " untracked status of the file in the buffer. Caching those fields is necessary,
  41. " because s:vcs_config may be updated asynchronously and s:vcs_config fields may
  42. " be invalid during those updates. b:buffer_vcs_config fields are updated
  43. " whenever corresponding fields in s:vcs_config are updated or an inconsistency
  44. " is detected during update_* operation.
  45. "
  46. " b:airline_head caches the head string it is empty iff it needs to be
  47. " recalculated. b:airline_head is recalculated based on b:buffer_vcs_config.
  48. function! s:init_buffer()
  49. let b:buffer_vcs_config = {}
  50. for vcs in keys(s:vcs_config)
  51. let b:buffer_vcs_config[vcs] = {
  52. \ 'branch': '',
  53. \ 'untracked': '',
  54. \ 'dirty': 0,
  55. \ }
  56. endfor
  57. unlet! b:airline_head
  58. endfunction
  59. let s:head_format = get(g:, 'airline#extensions#branch#format', 0)
  60. if s:head_format == 1
  61. function! s:format_name(name)
  62. return fnamemodify(a:name, ':t')
  63. endfunction
  64. elseif s:head_format == 2
  65. function! s:format_name(name)
  66. return pathshorten(a:name)
  67. endfunction
  68. elseif type(s:head_format) == type('')
  69. function! s:format_name(name)
  70. return call(s:head_format, [a:name])
  71. endfunction
  72. else
  73. function! s:format_name(name)
  74. return a:name
  75. endfunction
  76. endif
  77. " Fugitive special revisions. call '0' "staging" ?
  78. let s:names = {'0': 'index', '1': 'orig', '2':'fetch', '3':'merge'}
  79. let s:sha1size = get(g:, 'airline#extensions#branch#sha1_len', 7)
  80. function! s:update_git_branch()
  81. call airline#util#ignore_next_focusgain()
  82. if airline#util#has_fugitive()
  83. call s:config_fugitive_branch()
  84. elseif airline#util#has_gina()
  85. call s:config_gina_branch()
  86. else
  87. let s:vcs_config['git'].branch = ''
  88. return
  89. endif
  90. endfunction
  91. function! s:config_fugitive_branch() abort
  92. let s:vcs_config['git'].branch = FugitiveHead(s:sha1size)
  93. if s:vcs_config['git'].branch is# 'master' &&
  94. \ airline#util#winwidth() < 81
  95. " Shorten default a bit
  96. let s:vcs_config['git'].branch='mas'
  97. endif
  98. endfunction
  99. function! s:config_gina_branch() abort
  100. try
  101. let g:gina#component#repo#commit_length = s:sha1size
  102. let s:vcs_config['git'].branch = gina#component#repo#branch()
  103. catch
  104. endtry
  105. if s:vcs_config['git'].branch is# 'master' &&
  106. \ airline#util#winwidth() < 81
  107. " Shorten default a bit
  108. let s:vcs_config['git'].branch='mas'
  109. endif
  110. endfunction
  111. function! s:display_git_branch()
  112. let name = b:buffer_vcs_config['git'].branch
  113. try
  114. let commit = matchstr(FugitiveParse()[0], '^\x\+')
  115. if has_key(s:names, commit)
  116. let name = get(s:names, commit)."(".name.")"
  117. elseif !empty(commit)
  118. if exists('*FugitiveExecute')
  119. let ref = FugitiveExecute(['describe', '--all', '--exact-match', commit], bufnr('')).stdout[0]
  120. else
  121. noautocmd let ref = fugitive#repo().git_chomp('describe', '--all', '--exact-match', commit)
  122. if ref =~# ':'
  123. let ref = ''
  124. endif
  125. endif
  126. if !empty(ref)
  127. let name = s:format_name(substitute(ref, '\v\C^%(heads/|remotes/|tags/)=','',''))."(".name.")"
  128. else
  129. let name = matchstr(commit, '.\{'.s:sha1size.'}')."(".name.")"
  130. endif
  131. endif
  132. catch
  133. endtry
  134. return name
  135. endfunction
  136. function! s:update_hg_branch()
  137. if airline#util#has_lawrencium()
  138. let cmd='LC_ALL=C hg qtop'
  139. let stl=lawrencium#statusline()
  140. let file=expand('%:p')
  141. if !empty(stl) && get(b:, 'airline_do_mq_check', 1)
  142. if g:airline#init#vim_async
  143. noa call airline#async#get_mq_async(cmd, file)
  144. elseif has("nvim")
  145. noa call airline#async#nvim_get_mq_async(cmd, file)
  146. else
  147. " remove \n at the end of the command
  148. let output=system(cmd)[0:-2]
  149. noa call airline#async#mq_output(output, file)
  150. endif
  151. endif
  152. " do not do mq check anymore
  153. let b:airline_do_mq_check = 0
  154. if exists("b:mq") && !empty(b:mq)
  155. if stl is# 'default'
  156. " Shorten default a bit
  157. let stl='def'
  158. endif
  159. let stl.=' ['.b:mq.']'
  160. endif
  161. let s:vcs_config['mercurial'].branch = stl
  162. else
  163. let s:vcs_config['mercurial'].branch = ''
  164. endif
  165. endfunction
  166. function! s:display_hg_branch()
  167. return b:buffer_vcs_config['mercurial'].branch
  168. endfunction
  169. function! s:update_branch()
  170. for vcs in keys(s:vcs_config)
  171. call {s:vcs_config[vcs].update_branch}()
  172. if b:buffer_vcs_config[vcs].branch != s:vcs_config[vcs].branch
  173. let b:buffer_vcs_config[vcs].branch = s:vcs_config[vcs].branch
  174. unlet! b:airline_head
  175. endif
  176. endfor
  177. endfunction
  178. function! airline#extensions#branch#update_untracked_config(file, vcs)
  179. if !has_key(s:vcs_config[a:vcs].untracked, a:file)
  180. return
  181. elseif s:vcs_config[a:vcs].untracked[a:file] != b:buffer_vcs_config[a:vcs].untracked
  182. let b:buffer_vcs_config[a:vcs].untracked = s:vcs_config[a:vcs].untracked[a:file]
  183. unlet! b:airline_head
  184. endif
  185. endfunction
  186. function! s:update_untracked()
  187. let file = expand("%:p")
  188. if empty(file) || isdirectory(file) || !empty(&buftype)
  189. return
  190. endif
  191. let needs_update = 1
  192. let vcs_checks = get(g:, "airline#extensions#branch#vcs_checks", ["untracked", "dirty"])
  193. for vcs in keys(s:vcs_config)
  194. if file =~ s:vcs_config[vcs].exclude
  195. " Skip check for files that live in the exclude directory
  196. let needs_update = 0
  197. endif
  198. if has_key(s:vcs_config[vcs].untracked, file)
  199. let needs_update = 0
  200. call airline#extensions#branch#update_untracked_config(file, vcs)
  201. endif
  202. endfor
  203. if !needs_update
  204. return
  205. endif
  206. for vcs in keys(s:vcs_config)
  207. " only check, for git, if fugitive is installed
  208. " and for 'hg' if lawrencium is installed, else skip
  209. if vcs is# 'git' && (!airline#util#has_fugitive() && !airline#util#has_gina())
  210. continue
  211. elseif vcs is# 'mercurial' && !airline#util#has_lawrencium()
  212. continue
  213. endif
  214. let config = s:vcs_config[vcs]
  215. " Note that asynchronous update updates s:vcs_config only, and only
  216. " s:update_untracked updates b:buffer_vcs_config. If s:vcs_config is
  217. " invalidated again before s:update_untracked is called, then we lose the
  218. " result of the previous call, i.e. the head string is not updated. It
  219. " doesn't happen often in practice, so we let it be.
  220. if index(vcs_checks, 'untracked') > -1
  221. call airline#async#vcs_untracked(config, file, vcs)
  222. endif
  223. " Check clean state of repo
  224. if index(vcs_checks, 'dirty') > -1
  225. call airline#async#vcs_clean(config.dirty, file, vcs)
  226. endif
  227. endfor
  228. endfunction
  229. function! airline#extensions#branch#head()
  230. if !exists('b:buffer_vcs_config')
  231. call s:init_buffer()
  232. endif
  233. call s:update_branch()
  234. call s:update_untracked()
  235. if exists('b:airline_head') && !empty(b:airline_head)
  236. return b:airline_head
  237. endif
  238. let b:airline_head = ''
  239. let vcs_priority = get(g:, "airline#extensions#branch#vcs_priority", ["git", "mercurial"])
  240. let heads = []
  241. for vcs in vcs_priority
  242. if !empty(b:buffer_vcs_config[vcs].branch)
  243. let heads += [vcs]
  244. endif
  245. endfor
  246. for vcs in heads
  247. if !empty(b:airline_head)
  248. let b:airline_head .= ' | '
  249. endif
  250. if len(heads) > 1
  251. let b:airline_head .= s:vcs_config[vcs].exe .':'
  252. endif
  253. let b:airline_head .= s:format_name({s:vcs_config[vcs].display_branch}())
  254. let additional = b:buffer_vcs_config[vcs].untracked
  255. if empty(additional) &&
  256. \ has_key(b:buffer_vcs_config[vcs], 'dirty') &&
  257. \ b:buffer_vcs_config[vcs].dirty
  258. let additional = g:airline_symbols['dirty']
  259. endif
  260. let b:airline_head .= additional
  261. endfor
  262. if empty(heads)
  263. if airline#util#has_vcscommand()
  264. noa call VCSCommandEnableBufferSetup()
  265. if exists('b:VCSCommandBufferInfo')
  266. let b:airline_head = s:format_name(get(b:VCSCommandBufferInfo, 0, ''))
  267. endif
  268. endif
  269. endif
  270. if empty(heads)
  271. if airline#util#has_custom_scm()
  272. try
  273. let Fn = function(g:airline#extensions#branch#custom_head)
  274. let b:airline_head = Fn()
  275. endtry
  276. endif
  277. endif
  278. if exists("g:airline#extensions#branch#displayed_head_limit")
  279. let w:displayed_head_limit = g:airline#extensions#branch#displayed_head_limit
  280. if strwidth(b:airline_head) > w:displayed_head_limit - 1
  281. let b:airline_head =
  282. \ airline#util#strcharpart(b:airline_head, 0, w:displayed_head_limit - 1)
  283. \ . (&encoding ==? 'utf-8' ? '…' : '.')
  284. endif
  285. endif
  286. return b:airline_head
  287. endfunction
  288. function! airline#extensions#branch#get_head()
  289. let head = airline#extensions#branch#head()
  290. let winwidth = get(airline#parts#get('branch'), 'minwidth', 120)
  291. let minwidth = empty(get(b:, 'airline_hunks', '')) ? 14 : 7
  292. let head = airline#util#shorten(head, winwidth, minwidth)
  293. let symbol = get(g:, 'airline#extensions#branch#symbol', g:airline_symbols.branch)
  294. return empty(head)
  295. \ ? get(g:, 'airline#extensions#branch#empty_message', '')
  296. \ : printf('%s%s', empty(symbol) ? '' : symbol.(g:airline_symbols.space), head)
  297. endfunction
  298. function! s:reset_untracked_cache(shellcmdpost)
  299. " shellcmdpost - whether function was called as a result of ShellCmdPost hook
  300. if !exists('#airline')
  301. " airline disabled
  302. return
  303. endif
  304. if !g:airline#init#vim_async && !has('nvim')
  305. if a:shellcmdpost
  306. " Clear cache only if there was no error or the script uses an
  307. " asynchronous interface. Otherwise, cache clearing would overwrite
  308. " v:shell_error with a system() call inside get_*_untracked.
  309. if v:shell_error
  310. return
  311. endif
  312. endif
  313. endif
  314. let file = expand("%:p")
  315. for vcs in keys(s:vcs_config)
  316. " Dump the value of the cache for the current file. Partially mitigates the
  317. " issue of cache invalidation happening before a call to
  318. " s:update_untracked()
  319. call airline#extensions#branch#update_untracked_config(file, vcs)
  320. let s:vcs_config[vcs].untracked = {}
  321. endfor
  322. endfunction
  323. function! s:sh_autocmd_handler()
  324. if exists('#airline')
  325. unlet! b:airline_head b:airline_do_mq_check
  326. endif
  327. endfunction
  328. function! airline#extensions#branch#init(ext)
  329. call airline#parts#define_function('branch', 'airline#extensions#branch#get_head')
  330. autocmd ShellCmdPost,CmdwinLeave * call s:sh_autocmd_handler()
  331. autocmd User AirlineBeforeRefresh call s:sh_autocmd_handler()
  332. autocmd BufWritePost * call s:reset_untracked_cache(0)
  333. autocmd ShellCmdPost * call s:reset_untracked_cache(1)
  334. endfunction