PageRenderTime 52ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/vim_runtime/sources_non_forked/vim-airline/autoload/airline/extensions/branch.vim

https://gitlab.com/lokiexinferis/vim-configs
Vim Script | 403 lines | 316 code | 44 blank | 43 comment | 71 complexity | 71c4f3a97cae4c8bf7b1524133d8a0b6 MD5 | raw file
  1. " MIT License. Copyright (c) 2013-2016 Bailey Ling et al.
  2. " vim: et ts=2 sts=2 sw=2
  3. scriptencoding utf-8
  4. let s:has_fugitive = exists('*fugitive#head')
  5. let s:has_lawrencium = exists('*lawrencium#statusline')
  6. let s:has_vcscommand = get(g:, 'airline#extensions#branch#use_vcscommand', 0) && exists('*VCSCommandGetStatusLine')
  7. if !s:has_fugitive && !s:has_lawrencium && !s:has_vcscommand
  8. finish
  9. endif
  10. let s:has_async = airline#util#async
  11. " s:vcs_config contains static configuration of VCSes and their status relative
  12. " to the active file.
  13. " 'branch' - The name of currently active branch. This field is empty iff it
  14. " has not been initialized yet or the current file is not in
  15. " an active branch.
  16. " 'untracked' - Cache of untracked files represented as a dictionary with files
  17. " as keys. A file has a not exists symbol set as its value if it
  18. " is untracked. A file is present in this dictionary iff its
  19. " status is considered up to date.
  20. " 'untracked_mark' - used as regexp to test against the output of 'cmd'
  21. let s:vcs_config = {
  22. \ 'git': {
  23. \ 'exe': 'git',
  24. \ 'cmd': 'git status --porcelain -- ',
  25. \ 'untracked_mark': '??',
  26. \ 'update_branch': 's:update_git_branch',
  27. \ 'branch': '',
  28. \ 'untracked': {},
  29. \ },
  30. \ 'mercurial': {
  31. \ 'exe': 'hg',
  32. \ 'cmd': 'hg status -u -- ',
  33. \ 'untracked_mark': '?',
  34. \ 'update_branch': 's:update_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. \ }
  55. endfor
  56. unlet! b:airline_head
  57. endfunction
  58. let s:head_format = get(g:, 'airline#extensions#branch#format', 0)
  59. if s:head_format == 1
  60. function! s:format_name(name)
  61. return fnamemodify(a:name, ':t')
  62. endfunction
  63. elseif s:head_format == 2
  64. function! s:format_name(name)
  65. return pathshorten(a:name)
  66. endfunction
  67. elseif type(s:head_format) == type('')
  68. function! s:format_name(name)
  69. return call(s:head_format, [a:name])
  70. endfunction
  71. else
  72. function! s:format_name(name)
  73. return a:name
  74. endfunction
  75. endif
  76. let s:git_dirs = {}
  77. function! s:update_git_branch(path)
  78. if !s:has_fugitive
  79. let s:vcs_config['git'].branch = ''
  80. return
  81. endif
  82. let name = fugitive#head(7)
  83. if empty(name)
  84. if has_key(s:git_dirs, a:path)
  85. let s:vcs_config['git'].branch = s:git_dirs[a:path]
  86. return
  87. endif
  88. let dir = fugitive#extract_git_dir(a:path)
  89. if empty(dir)
  90. let name = ''
  91. else
  92. try
  93. let line = join(readfile(dir . '/HEAD'))
  94. if strpart(line, 0, 16) == 'ref: refs/heads/'
  95. let name = strpart(line, 16)
  96. else
  97. " raw commit hash
  98. let name = strpart(line, 0, 7)
  99. endif
  100. catch
  101. let name = ''
  102. endtry
  103. endif
  104. endif
  105. let s:git_dirs[a:path] = name
  106. let s:vcs_config['git'].branch = name
  107. endfunction
  108. function! s:update_hg_branch(path)
  109. if s:has_lawrencium
  110. let stl=lawrencium#statusline()
  111. if !empty(stl) && s:has_async
  112. call s:get_mq_async('hg qtop', expand('%:p'))
  113. endif
  114. if exists("s:mq") && !empty(s:mq)
  115. if stl is# 'default'
  116. " Shorten default a bit
  117. let stl='def'
  118. endif
  119. let stl.=' ['.s:mq.']'
  120. endif
  121. let s:vcs_config['mercurial'].branch = stl
  122. else
  123. let s:vcs_config['mercurial'].branch = ''
  124. endif
  125. endfunction
  126. function! s:update_branch()
  127. let l:path = exists("*fnamemodify") ? fnamemodify(resolve(@%), ":p:h") : expand("%:p:h")
  128. for vcs in keys(s:vcs_config)
  129. call {s:vcs_config[vcs].update_branch}(l:path)
  130. if b:buffer_vcs_config[vcs].branch != s:vcs_config[vcs].branch
  131. let b:buffer_vcs_config[vcs].branch = s:vcs_config[vcs].branch
  132. unlet! b:airline_head
  133. endif
  134. endfor
  135. endfunction
  136. function! s:update_untracked_in_buffer_config(file, vcs)
  137. if !has_key(s:vcs_config[a:vcs].untracked, a:file)
  138. return
  139. elseif s:vcs_config[a:vcs].untracked[a:file] != b:buffer_vcs_config[a:vcs].untracked
  140. let b:buffer_vcs_config[a:vcs].untracked = s:vcs_config[a:vcs].untracked[a:file]
  141. unlet! b:airline_head
  142. endif
  143. endfunction
  144. function! s:update_untracked()
  145. let l:file = expand("%:p")
  146. if empty(l:file) || isdirectory(l:file)
  147. return
  148. endif
  149. let l:needs_update = 1
  150. for vcs in keys(s:vcs_config)
  151. if has_key(s:vcs_config[vcs].untracked, l:file)
  152. let l:needs_update = 0
  153. call s:update_untracked_in_buffer_config(l:file, vcs)
  154. endif
  155. endfor
  156. if !l:needs_update
  157. return
  158. endif
  159. for vcs in keys(s:vcs_config)
  160. let l:config = s:vcs_config[vcs]
  161. if s:has_async
  162. " Note that asynchronous update updates s:vcs_config only, and only
  163. " s:update_untracked updates b:buffer_vcs_config. If s:vcs_config is
  164. " invalidated again before s:update_untracked is called, then we lose the
  165. " result of the previous call, i.e. the head string is not updated. It
  166. " doesn't happen often in practice, so we let it be.
  167. call s:get_vcs_untracked_async(l:config, l:file)
  168. else
  169. let output = airline#util#system(l:config.cmd . shellescape(l:file))
  170. if output =~? ('^' . l:config.untracked_mark)
  171. let l:config.untracked[l:file] = get(g:, 'airline#extensions#branch#notexists', g:airline_symbols.notexists)
  172. else
  173. let l:config.untracked[l:file] = ''
  174. endif
  175. call s:update_untracked_in_buffer_config(l:file, vcs)
  176. endif
  177. endfor
  178. endfunction
  179. if s:has_async
  180. let s:jobs = {}
  181. function! s:on_stdout(channel, msg) dict abort
  182. let self.buf .= a:msg
  183. endfunction
  184. function! s:on_exit(channel) dict abort
  185. if self.buf =~? ('^' . self.config['untracked_mark'])
  186. let self.config.untracked[self.file] = get(g:, 'airline#extensions#branch#notexists', g:airline_symbols.notexists)
  187. else
  188. let self.config.untracked[self.file] = ''
  189. endif
  190. " b:buffer_vcs_config will be updated on next call of update_untracked if
  191. " needed
  192. if has_key(s:jobs, self.file)
  193. call remove(s:jobs, self.file)
  194. endif
  195. endfunction
  196. function! s:get_vcs_untracked_async(config, file)
  197. if g:airline#util#is_windows && &shell =~ 'cmd'
  198. let cmd = a:config['cmd'] . shellescape(a:file)
  199. else
  200. let cmd = ['sh', '-c', a:config['cmd'] . shellescape(a:file)]
  201. endif
  202. let options = {'config': a:config, 'buf': '', 'file': a:file}
  203. if has_key(s:jobs, a:file)
  204. if job_status(get(s:jobs, a:file)) == 'run'
  205. return
  206. elseif has_key(s:jobs, a:file)
  207. call remove(s:jobs, a:file)
  208. endif
  209. endif
  210. let id = job_start(cmd, {
  211. \ 'err_io': 'out',
  212. \ 'out_cb': function('s:on_stdout', options),
  213. \ 'close_cb': function('s:on_exit', options)})
  214. let s:jobs[a:file] = id
  215. endfu
  216. function! s:on_exit_mq(channel) dict abort
  217. if !empty(self.buf)
  218. if self.buf is# 'no patches applied' ||
  219. \ self.buf =~# "unknown command 'qtop'"
  220. let self.buf = ''
  221. elseif exists("s:mq") && s:mq isnot# self.buf
  222. " make sure, statusline is updated
  223. unlet! b:airline_head
  224. endif
  225. let s:mq = self.buf
  226. endif
  227. if has_key(s:jobs, self.file)
  228. call remove(s:jobs, self.file)
  229. endif
  230. endfunction
  231. function! s:get_mq_async(cmd, file)
  232. if g:airline#util#is_windows && &shell =~ 'cmd'
  233. let cmd = a:cmd. shellescape(a:file)
  234. else
  235. let cmd = ['sh', '-c', a:cmd]
  236. endif
  237. let options = {'cmd': a:cmd, 'buf': '', 'file': a:file}
  238. if has_key(s:jobs, a:file)
  239. if job_status(get(s:jobs, a:file)) == 'run'
  240. return
  241. elseif has_key(s:jobs, a:file)
  242. call remove(s:jobs, a:file)
  243. endif
  244. endif
  245. let id = job_start(cmd, {
  246. \ 'err_io': 'out',
  247. \ 'out_cb': function('s:on_stdout', options),
  248. \ 'close_cb': function('s:on_exit_mq', options)})
  249. let s:jobs[a:file] = id
  250. endfu
  251. endif
  252. function! airline#extensions#branch#head()
  253. if !exists('b:buffer_vcs_config')
  254. call s:init_buffer()
  255. endif
  256. call s:update_branch()
  257. call s:update_untracked()
  258. if exists('b:airline_head') && !empty(b:airline_head)
  259. return b:airline_head
  260. endif
  261. let b:airline_head = ''
  262. let l:vcs_priority = get(g:, "airline#extensions#branch#vcs_priority", ["git", "mercurial"])
  263. let l:heads = {}
  264. for vcs in l:vcs_priority
  265. if !empty(b:buffer_vcs_config[vcs].branch)
  266. let l:heads[vcs] = b:buffer_vcs_config[vcs].branch
  267. endif
  268. endfor
  269. for vcs in keys(l:heads)
  270. if !empty(b:airline_head)
  271. let b:airline_head .= ' | '
  272. endif
  273. let b:airline_head .= (len(l:heads) > 1 ? s:vcs_config[l:vcs].exe : '') . s:format_name(l:heads[l:vcs])
  274. let b:airline_head .= b:buffer_vcs_config[vcs].untracked
  275. endfor
  276. if empty(l:heads)
  277. if s:has_vcscommand
  278. call VCSCommandEnableBufferSetup()
  279. if exists('b:VCSCommandBufferInfo')
  280. let b:airline_head = s:format_name(get(b:VCSCommandBufferInfo, 0, ''))
  281. endif
  282. endif
  283. endif
  284. if exists("g:airline#extensions#branch#displayed_head_limit")
  285. let w:displayed_head_limit = g:airline#extensions#branch#displayed_head_limit
  286. if len(b:airline_head) > w:displayed_head_limit - 1
  287. let b:airline_head = b:airline_head[0:(w:displayed_head_limit - 1)].(&encoding ==? 'utf-8' ? '…' : '.')
  288. endif
  289. endif
  290. if has_key(l:heads, 'git') && !s:check_in_path()
  291. let b:airline_head = ''
  292. endif
  293. let minwidth = empty(get(b:, 'airline_hunks', '')) ? 14 : 7
  294. let b:airline_head = airline#util#shorten(b:airline_head, 120, minwidth)
  295. return b:airline_head
  296. endfunction
  297. function! airline#extensions#branch#get_head()
  298. let head = airline#extensions#branch#head()
  299. let empty_message = get(g:, 'airline#extensions#branch#empty_message', '')
  300. let symbol = get(g:, 'airline#extensions#branch#symbol', g:airline_symbols.branch)
  301. return empty(head)
  302. \ ? empty_message
  303. \ : printf('%s%s', empty(symbol) ? '' : symbol.(g:airline_symbols.space), head)
  304. endfunction
  305. function! s:check_in_path()
  306. if !exists('b:airline_file_in_root')
  307. let root = get(b:, 'git_dir', get(b:, 'mercurial_dir', ''))
  308. let bufferpath = resolve(fnamemodify(expand('%'), ':p'))
  309. if !filereadable(root) "not a file
  310. " if .git is a directory, it's the old submodule format
  311. if match(root, '\.git$') >= 0
  312. let root = expand(fnamemodify(root, ':h'))
  313. else
  314. " else it's the newer format, and we need to guesstimate
  315. " 1) check for worktrees
  316. if match(root, 'worktrees') > -1
  317. " worktree can be anywhere, so simply assume true here
  318. return 1
  319. endif
  320. " 2) check for submodules
  321. let pattern = '\.git[\\/]\(modules\)[\\/]'
  322. if match(root, pattern) >= 0
  323. let root = substitute(root, pattern, '', '')
  324. endif
  325. endif
  326. endif
  327. let b:airline_file_in_root = stridx(bufferpath, root) > -1
  328. endif
  329. return b:airline_file_in_root
  330. endfunction
  331. function! s:reset_untracked_cache(shellcmdpost)
  332. " shellcmdpost - whether function was called as a result of ShellCmdPost hook
  333. if !s:has_async && !has('nvim')
  334. if a:shellcmdpost
  335. " Clear cache only if there was no error or the script uses an
  336. " asynchronous interface. Otherwise, cache clearing would overwrite
  337. " v:shell_error with a system() call inside get_*_untracked.
  338. if v:shell_error
  339. return
  340. endif
  341. endif
  342. endif
  343. let l:file = expand("%:p")
  344. for vcs in keys(s:vcs_config)
  345. " Dump the value of the cache for the current file. Partially mitigates the
  346. " issue of cache invalidation happening before a call to
  347. " s:update_untracked()
  348. call s:update_untracked_in_buffer_config(l:file, l:vcs)
  349. let s:vcs_config[vcs].untracked = {}
  350. endfor
  351. endfunction
  352. function! airline#extensions#branch#init(ext)
  353. call airline#parts#define_function('branch', 'airline#extensions#branch#get_head')
  354. autocmd BufReadPost * unlet! b:airline_file_in_root
  355. autocmd CursorHold,ShellCmdPost,CmdwinLeave * unlet! b:airline_head
  356. autocmd User AirlineBeforeRefresh unlet! b:airline_head
  357. autocmd BufWritePost * call s:reset_untracked_cache(0)
  358. autocmd ShellCmdPost * call s:reset_untracked_cache(1)
  359. endfunction