async.vim 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. " MIT License. Copyright (c) 2013-2021 Christian Brabandt et al.
  2. " vim: et ts=2 sts=2 sw=2
  3. scriptencoding utf-8
  4. let s:untracked_jobs = {}
  5. let s:mq_jobs = {}
  6. let s:po_jobs = {}
  7. let s:clean_jobs = {}
  8. " Generic functions handling on exit event of the various async functions
  9. function! s:untracked_output(dict, buf)
  10. if a:buf =~? ('^'. a:dict.cfg['untracked_mark'])
  11. let a:dict.cfg.untracked[a:dict.file] = get(g:, 'airline#extensions#branch#notexists', g:airline_symbols.notexists)
  12. else
  13. let a:dict.cfg.untracked[a:dict.file] = ''
  14. endif
  15. endfunction
  16. " also called from branch extension (for non-async vims)
  17. function! airline#async#mq_output(buf, file)
  18. let buf=a:buf
  19. if !empty(a:buf)
  20. if a:buf =~# 'no patches applied' ||
  21. \ a:buf =~# "unknown command 'qtop'" ||
  22. \ a:buf =~# "abort"
  23. let buf = ''
  24. elseif exists("b:mq") && b:mq isnot# buf
  25. " make sure, statusline is updated
  26. unlet! b:airline_head
  27. endif
  28. let b:mq = buf
  29. endif
  30. if has_key(s:mq_jobs, a:file)
  31. call remove(s:mq_jobs, a:file)
  32. endif
  33. endfunction
  34. function! s:po_output(buf, file)
  35. if !empty(a:buf)
  36. let b:airline_po_stats = printf("%s", a:buf)
  37. else
  38. let b:airline_po_stats = ''
  39. endif
  40. if has_key(s:po_jobs, a:file)
  41. call remove(s:po_jobs, a:file)
  42. endif
  43. endfunction
  44. function! s:valid_dir(dir)
  45. if empty(a:dir) || !isdirectory(a:dir)
  46. return getcwd()
  47. endif
  48. return a:dir
  49. endfunction
  50. function! airline#async#vcs_untracked(config, file, vcs)
  51. if g:airline#init#vim_async
  52. " Vim 8 with async support
  53. noa call airline#async#vim_vcs_untracked(a:config, a:file)
  54. else
  55. " nvim async or vim without job-feature
  56. noa call airline#async#nvim_vcs_untracked(a:config, a:file, a:vcs)
  57. endif
  58. endfunction
  59. function! s:set_clean_variables(file, vcs, val)
  60. let var=getbufvar(fnameescape(a:file), 'buffer_vcs_config', {})
  61. if has_key(var, a:vcs) && has_key(var[a:vcs], 'dirty') &&
  62. \ type(getbufvar(fnameescape(a:file), 'buffer_vcs_config')) == type({})
  63. let var[a:vcs].dirty=a:val
  64. try
  65. call setbufvar(fnameescape(a:file), 'buffer_vcs_config', var)
  66. unlet! b:airline_head
  67. catch
  68. endtry
  69. endif
  70. endfunction
  71. function! s:set_clean_jobs_variable(vcs, file, id)
  72. if !has_key(s:clean_jobs, a:vcs)
  73. let s:clean_jobs[a:vcs] = {}
  74. endif
  75. let s:clean_jobs[a:vcs][a:file]=a:id
  76. endfunction
  77. function! s:on_exit_clean(...) dict abort
  78. let buf=self.buf
  79. call s:set_clean_variables(self.file, self.vcs, !empty(buf))
  80. if has_key(get(s:clean_jobs, self.vcs, {}), self.file)
  81. call remove(s:clean_jobs[self.vcs], self.file)
  82. endif
  83. endfunction
  84. function! airline#async#vcs_clean(cmd, file, vcs)
  85. if g:airline#init#vim_async
  86. " Vim 8 with async support
  87. noa call airline#async#vim_vcs_clean(a:cmd, a:file, a:vcs)
  88. elseif has("nvim")
  89. " nvim async
  90. noa call airline#async#nvim_vcs_clean(a:cmd, a:file, a:vcs)
  91. else
  92. " Vim pre 8 using system()
  93. call airline#async#vim7_vcs_clean(a:cmd, a:file, a:vcs)
  94. endif
  95. endfunction
  96. if v:version >= 800 && has("job")
  97. " Vim 8.0 with Job feature
  98. " TODO: Check if we need the cwd option for the job_start() functions
  99. " (only works starting with Vim 8.0.0902)
  100. function! s:on_stdout(channel, msg) dict abort
  101. let self.buf .= a:msg
  102. endfunction
  103. function! s:on_exit_mq(channel) dict abort
  104. call airline#async#mq_output(self.buf, self.file)
  105. endfunction
  106. function! s:on_exit_untracked(channel) dict abort
  107. call s:untracked_output(self, self.buf)
  108. if has_key(s:untracked_jobs, self.file)
  109. call remove(s:untracked_jobs, self.file)
  110. endif
  111. endfunction
  112. function! s:on_exit_po(channel) dict abort
  113. call s:po_output(self.buf, self.file)
  114. call airline#extensions#po#shorten()
  115. endfunction
  116. function! airline#async#get_mq_async(cmd, file)
  117. if g:airline#init#is_windows && &shell =~ 'cmd\|powershell'
  118. let cmd = a:cmd
  119. else
  120. let cmd = [&shell, &shellcmdflag, a:cmd]
  121. endif
  122. let options = {'cmd': a:cmd, 'buf': '', 'file': a:file}
  123. if has_key(s:mq_jobs, a:file)
  124. if job_status(get(s:mq_jobs, a:file)) == 'run'
  125. return
  126. elseif has_key(s:mq_jobs, a:file)
  127. call remove(s:mq_jobs, a:file)
  128. endif
  129. endif
  130. let id = job_start(cmd, {
  131. \ 'err_io': 'out',
  132. \ 'out_cb': function('s:on_stdout', options),
  133. \ 'close_cb': function('s:on_exit_mq', options)})
  134. let s:mq_jobs[a:file] = id
  135. endfunction
  136. function! airline#async#get_msgfmt_stat(cmd, file)
  137. if !executable('msgfmt')
  138. " no msgfmt
  139. return
  140. endif
  141. if g:airline#init#is_windows
  142. let cmd = 'cmd /C ' . a:cmd. shellescape(a:file)
  143. else
  144. let cmd = ['sh', '-c', a:cmd. shellescape(a:file)]
  145. endif
  146. let options = {'buf': '', 'file': a:file}
  147. if has_key(s:po_jobs, a:file)
  148. if job_status(get(s:po_jobs, a:file)) == 'run'
  149. return
  150. elseif has_key(s:po_jobs, a:file)
  151. call remove(s:po_jobs, a:file)
  152. endif
  153. endif
  154. let id = job_start(cmd, {
  155. \ 'err_io': 'out',
  156. \ 'out_cb': function('s:on_stdout', options),
  157. \ 'close_cb': function('s:on_exit_po', options)})
  158. let s:po_jobs[a:file] = id
  159. endfunction
  160. function! airline#async#vim_vcs_clean(cmd, file, vcs)
  161. if g:airline#init#is_windows && &shell =~ 'cmd\|powershell'
  162. let cmd = a:cmd
  163. else
  164. let cmd = [&shell, &shellcmdflag, a:cmd]
  165. endif
  166. let options = {'buf': '', 'vcs': a:vcs, 'file': a:file}
  167. let jobs = get(s:clean_jobs, a:vcs, {})
  168. if has_key(jobs, a:file)
  169. if job_status(get(jobs, a:file)) == 'run'
  170. return
  171. elseif has_key(jobs, a:file)
  172. " still running
  173. return
  174. " jobs dict should be cleaned on exit, so not needed here
  175. " call remove(jobs, a:file)
  176. endif
  177. endif
  178. let id = job_start(cmd, {
  179. \ 'err_io': 'null',
  180. \ 'out_cb': function('s:on_stdout', options),
  181. \ 'close_cb': function('s:on_exit_clean', options)})
  182. call s:set_clean_jobs_variable(a:vcs, a:file, id)
  183. endfunction
  184. function! airline#async#vim_vcs_untracked(config, file)
  185. if g:airline#init#is_windows && &shell =~ 'cmd\|powershell'
  186. let cmd = a:config['cmd'] . shellescape(a:file)
  187. else
  188. let cmd = [&shell, &shellcmdflag, a:config['cmd'] . shellescape(a:file)]
  189. endif
  190. let options = {'cfg': a:config, 'buf': '', 'file': a:file}
  191. if has_key(s:untracked_jobs, a:file)
  192. if job_status(get(s:untracked_jobs, a:file)) == 'run'
  193. return
  194. elseif has_key(s:untracked_jobs, a:file)
  195. call remove(s:untracked_jobs, a:file)
  196. endif
  197. endif
  198. let id = job_start(cmd, {
  199. \ 'err_io': 'out',
  200. \ 'out_cb': function('s:on_stdout', options),
  201. \ 'close_cb': function('s:on_exit_untracked', options)})
  202. let s:untracked_jobs[a:file] = id
  203. endfunction
  204. elseif has("nvim")
  205. " NVim specific functions
  206. function! s:nvim_output_handler(job_id, data, event) dict
  207. if a:event == 'stdout' || a:event == 'stderr'
  208. let self.buf .= join(a:data)
  209. endif
  210. endfunction
  211. function! s:nvim_untracked_job_handler(job_id, data, event) dict
  212. if a:event == 'exit'
  213. call s:untracked_output(self, self.buf)
  214. if has_key(s:untracked_jobs, self.file)
  215. call remove(s:untracked_jobs, self.file)
  216. endif
  217. endif
  218. endfunction
  219. function! s:nvim_mq_job_handler(job_id, data, event) dict
  220. if a:event == 'exit'
  221. call airline#async#mq_output(self.buf, self.file)
  222. endif
  223. endfunction
  224. function! s:nvim_po_job_handler(job_id, data, event) dict
  225. if a:event == 'exit'
  226. call s:po_output(self.buf, self.file)
  227. call airline#extensions#po#shorten()
  228. endif
  229. endfunction
  230. function! airline#async#nvim_get_mq_async(cmd, file)
  231. let config = {
  232. \ 'buf': '',
  233. \ 'file': a:file,
  234. \ 'cwd': s:valid_dir(fnamemodify(a:file, ':p:h')),
  235. \ 'on_stdout': function('s:nvim_output_handler'),
  236. \ 'on_stderr': function('s:nvim_output_handler'),
  237. \ 'on_exit': function('s:nvim_mq_job_handler')
  238. \ }
  239. if g:airline#init#is_windows && &shell =~ 'cmd\|powershell'
  240. let cmd = a:cmd
  241. else
  242. let cmd = [&shell, &shellcmdflag, a:cmd]
  243. endif
  244. if has_key(s:mq_jobs, a:file)
  245. call remove(s:mq_jobs, a:file)
  246. endif
  247. let id = jobstart(cmd, config)
  248. let s:mq_jobs[a:file] = id
  249. endfunction
  250. function! airline#async#nvim_get_msgfmt_stat(cmd, file)
  251. let config = {
  252. \ 'buf': '',
  253. \ 'file': a:file,
  254. \ 'cwd': s:valid_dir(fnamemodify(a:file, ':p:h')),
  255. \ 'on_stdout': function('s:nvim_output_handler'),
  256. \ 'on_stderr': function('s:nvim_output_handler'),
  257. \ 'on_exit': function('s:nvim_po_job_handler')
  258. \ }
  259. if g:airline#init#is_windows && &shell =~ 'cmd\|powershell'
  260. " no msgfmt on windows?
  261. return
  262. else
  263. let cmd = [&shell, &shellcmdflag, a:cmd. shellescape(a:file)]
  264. endif
  265. if has_key(s:po_jobs, a:file)
  266. call remove(s:po_jobs, a:file)
  267. endif
  268. let id = jobstart(cmd, config)
  269. let s:po_jobs[a:file] = id
  270. endfunction
  271. function! airline#async#nvim_vcs_clean(cmd, file, vcs)
  272. let config = {
  273. \ 'buf': '',
  274. \ 'vcs': a:vcs,
  275. \ 'file': a:file,
  276. \ 'cwd': s:valid_dir(fnamemodify(a:file, ':p:h')),
  277. \ 'on_stdout': function('s:nvim_output_handler'),
  278. \ 'on_stderr': function('s:nvim_output_handler'),
  279. \ 'on_exit': function('s:on_exit_clean')}
  280. if g:airline#init#is_windows && &shell =~ 'cmd\|powershell'
  281. let cmd = a:cmd
  282. else
  283. let cmd = [&shell, &shellcmdflag, a:cmd]
  284. endif
  285. if !has_key(s:clean_jobs, a:vcs)
  286. let s:clean_jobs[a:vcs] = {}
  287. endif
  288. if has_key(s:clean_jobs[a:vcs], a:file)
  289. " still running
  290. return
  291. " jobs dict should be cleaned on exit, so not needed here
  292. " call remove(s:clean_jobs[a:vcs], a:file)
  293. endif
  294. let id = jobstart(cmd, config)
  295. call s:set_clean_jobs_variable(a:vcs, a:file, id)
  296. endfunction
  297. endif
  298. " Should work in either Vim pre 8 or Nvim
  299. function! airline#async#nvim_vcs_untracked(cfg, file, vcs)
  300. let cmd = a:cfg.cmd . shellescape(a:file)
  301. let id = -1
  302. let config = {
  303. \ 'buf': '',
  304. \ 'vcs': a:vcs,
  305. \ 'cfg': a:cfg,
  306. \ 'file': a:file,
  307. \ 'cwd': s:valid_dir(fnamemodify(a:file, ':p:h'))
  308. \ }
  309. if has("nvim")
  310. call extend(config, {
  311. \ 'on_stdout': function('s:nvim_output_handler'),
  312. \ 'on_exit': function('s:nvim_untracked_job_handler')})
  313. if has_key(s:untracked_jobs, config.file)
  314. " still running
  315. return
  316. endif
  317. try
  318. let id = jobstart(cmd, config)
  319. catch
  320. " catch-all, jobstart() failed, fall back to system()
  321. let id=-1
  322. endtry
  323. let s:untracked_jobs[a:file] = id
  324. endif
  325. " vim without job feature or nvim jobstart failed
  326. if id < 1
  327. let output=system(cmd)
  328. call s:untracked_output(config, output)
  329. call airline#extensions#branch#update_untracked_config(a:file, a:vcs)
  330. endif
  331. endfunction
  332. function! airline#async#vim7_vcs_clean(cmd, file, vcs)
  333. " Vim pre 8, fallback using system()
  334. " don't want to to see error messages
  335. if g:airline#init#is_windows && &shell =~ 'cmd'
  336. let cmd = a:cmd .' 2>nul'
  337. elseif g:airline#init#is_windows && &shell =~ 'powerline'
  338. let cmd = a:cmd .' 2> $null'
  339. else
  340. let cmd = a:cmd .' 2>/dev/null'
  341. endif
  342. let output=system(cmd)
  343. call s:set_clean_variables(a:file, a:vcs, !empty(output))
  344. endfunction