task.vim 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. " ============================================================================
  2. " Description: Manage long running tasks.
  3. " Author: Qiming Zhao <chemzqm@gmail.com>
  4. " Licence: Anti 966 licence
  5. " Version: 0.1
  6. " Last Modified: Dec 12, 2020
  7. " ============================================================================
  8. scriptencoding utf-8
  9. let s:is_vim = !has('nvim')
  10. let s:running_task = {}
  11. " neovim emit strings that part of lines.
  12. let s:out_remain_text = {}
  13. let s:err_remain_text = {}
  14. function! coc#task#start(id, opts)
  15. if coc#task#running(a:id)
  16. call coc#task#stop(a:id)
  17. endif
  18. let cmd = [a:opts['cmd']] + get(a:opts, 'args', [])
  19. let cwd = get(a:opts, 'cwd', getcwd())
  20. let env = get(a:opts, 'env', {})
  21. " cmd args cwd pty
  22. if s:is_vim
  23. let options = {
  24. \ 'cwd': cwd,
  25. \ 'err_mode': 'nl',
  26. \ 'out_mode': 'nl',
  27. \ 'err_cb': {channel, message -> s:on_stderr(a:id, [message])},
  28. \ 'out_cb': {channel, message -> s:on_stdout(a:id, [message])},
  29. \ 'exit_cb': {channel, code -> s:on_exit(a:id, code)},
  30. \ 'env': env,
  31. \}
  32. if has("patch-8.1.350")
  33. let options['noblock'] = 1
  34. endif
  35. if get(a:opts, 'pty', 0)
  36. let options['pty'] = 1
  37. endif
  38. let job = job_start(cmd, options)
  39. let status = job_status(job)
  40. if status !=# 'run'
  41. echohl Error | echom 'Failed to start '.a:id.' task' | echohl None
  42. return v:false
  43. endif
  44. let s:running_task[a:id] = job
  45. else
  46. let options = {
  47. \ 'cwd': cwd,
  48. \ 'on_stderr': {channel, msgs -> s:on_stderr(a:id, msgs)},
  49. \ 'on_stdout': {channel, msgs -> s:on_stdout(a:id, msgs)},
  50. \ 'on_exit': {channel, code -> s:on_exit(a:id, code)},
  51. \ 'detach': get(a:opts, 'detach', 0),
  52. \}
  53. let original = {}
  54. if !empty(env)
  55. if has('nvim-0.5.0')
  56. let options['env'] = env
  57. elseif exists('*setenv') && exists('*getenv')
  58. for key in keys(env)
  59. let original[key] = getenv(key)
  60. call setenv(key, env[key])
  61. endfor
  62. endif
  63. endif
  64. if get(a:opts, 'pty', 0)
  65. let options['pty'] = 1
  66. endif
  67. let chan_id = jobstart(cmd, options)
  68. if !empty(original)
  69. for key in keys(original)
  70. call setenv(key, original[key])
  71. endfor
  72. endif
  73. if chan_id <= 0
  74. echohl Error | echom 'Failed to start '.a:id.' task' | echohl None
  75. return v:false
  76. endif
  77. let s:running_task[a:id] = chan_id
  78. endif
  79. return v:true
  80. endfunction
  81. function! coc#task#stop(id)
  82. let job = get(s:running_task, a:id, v:null)
  83. if !job | return | endif
  84. if s:is_vim
  85. call job_stop(job, 'term')
  86. else
  87. call jobstop(job)
  88. endif
  89. sleep 50m
  90. let running = coc#task#running(a:id)
  91. if running
  92. echohl Error | echom 'job '.a:id. ' stop failed.' | echohl None
  93. endif
  94. endfunction
  95. function! s:on_exit(id, code) abort
  96. if get(g:, 'coc_vim_leaving', 0) | return | endif
  97. if has('nvim')
  98. let s:out_remain_text[a:id] = ''
  99. let s:err_remain_text[a:id] = ''
  100. endif
  101. if has_key(s:running_task, a:id)
  102. call remove(s:running_task, a:id)
  103. endif
  104. call coc#rpc#notify('TaskExit', [a:id, a:code])
  105. endfunction
  106. function! s:on_stderr(id, msgs)
  107. if get(g:, 'coc_vim_leaving', 0) | return | endif
  108. if empty(a:msgs)
  109. return
  110. endif
  111. if s:is_vim
  112. call coc#rpc#notify('TaskStderr', [a:id, a:msgs])
  113. else
  114. let remain = get(s:err_remain_text, a:id, '')
  115. let eof = (a:msgs == [''])
  116. let msgs = copy(a:msgs)
  117. if len(remain) > 0
  118. if msgs[0] == ''
  119. let msgs[0] = remain
  120. else
  121. let msgs[0] = remain . msgs[0]
  122. endif
  123. endif
  124. let last = msgs[len(msgs) - 1]
  125. let s:err_remain_text[a:id] = len(last) > 0 ? last : ''
  126. " all lines from 0 to n - 2
  127. if len(msgs) > 1
  128. call coc#rpc#notify('TaskStderr', [a:id, msgs[:len(msgs)-2]])
  129. elseif eof && len(msgs[0]) > 0
  130. call coc#rpc#notify('TaskStderr', [a:id, msgs])
  131. endif
  132. endif
  133. endfunction
  134. function! s:on_stdout(id, msgs)
  135. if empty(a:msgs)
  136. return
  137. endif
  138. if s:is_vim
  139. call coc#rpc#notify('TaskStdout', [a:id, a:msgs])
  140. else
  141. let remain = get(s:out_remain_text, a:id, '')
  142. let eof = (a:msgs == [''])
  143. let msgs = copy(a:msgs)
  144. if len(remain) > 0
  145. if msgs[0] == ''
  146. let msgs[0] = remain
  147. else
  148. let msgs[0] = remain . msgs[0]
  149. endif
  150. endif
  151. let last = msgs[len(msgs) - 1]
  152. let s:out_remain_text[a:id] = len(last) > 0 ? last : ''
  153. " all lines from 0 to n - 2
  154. if len(msgs) > 1
  155. call coc#rpc#notify('TaskStdout', [a:id, msgs[:len(msgs)-2]])
  156. elseif eof && len(msgs[0]) > 0
  157. call coc#rpc#notify('TaskStdout', [a:id, msgs])
  158. endif
  159. endif
  160. endfunction
  161. function! coc#task#running(id)
  162. if !has_key(s:running_task, a:id) == 1
  163. return v:false
  164. endif
  165. let job = s:running_task[a:id]
  166. if s:is_vim
  167. let status = job_status(job)
  168. return status ==# 'run'
  169. endif
  170. let [code] = jobwait([job], 10)
  171. return code == -1
  172. endfunction