jobcontrol.vim 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. " s:jobs is a global reference to all jobs started with Spawn() or with the
  2. " internal function s:spawn
  3. let s:jobs = {}
  4. " s:handlers is a global event handlers for all jobs started with Spawn() or
  5. " with the internal function s:spawn
  6. let s:handlers = {}
  7. " Spawn is a wrapper around s:spawn. It can be executed by other files and
  8. " scripts if needed. Desc defines the description for printing the status
  9. " during the job execution (useful for statusline integration).
  10. function! go#jobcontrol#Spawn(bang, desc, for, args) abort
  11. " autowrite is not enabled for jobs
  12. call go#cmd#autowrite()
  13. let job = s:spawn(a:bang, a:desc, a:for, a:args)
  14. return job.id
  15. endfunction
  16. " AddHandler adds a on_exit callback handler and returns the id.
  17. function! go#jobcontrol#AddHandler(handler) abort
  18. let i = len(s:handlers)
  19. while has_key(s:handlers, string(i))
  20. let i += 1
  21. break
  22. endwhile
  23. let s:handlers[string(i)] = a:handler
  24. return string(i)
  25. endfunction
  26. " RemoveHandler removes a callback handler by id.
  27. function! go#jobcontrol#RemoveHandler(id) abort
  28. unlet s:handlers[a:id]
  29. endfunction
  30. " spawn spawns a go subcommand with the name and arguments with jobstart. Once a
  31. " job is started a reference will be stored inside s:jobs. The job is started
  32. " inside the current files folder.
  33. function! s:spawn(bang, desc, for, args) abort
  34. let status_type = a:args[0]
  35. let status_dir = expand('%:p:h')
  36. let started_at = reltime()
  37. call go#statusline#Update(status_dir, {
  38. \ 'desc': "current status",
  39. \ 'type': status_type,
  40. \ 'state': "started",
  41. \})
  42. let job = {
  43. \ 'desc': a:desc,
  44. \ 'bang': a:bang,
  45. \ 'winnr': winnr(),
  46. \ 'importpath': go#package#ImportPath(),
  47. \ 'state': "RUNNING",
  48. \ 'stderr' : [],
  49. \ 'stdout' : [],
  50. \ 'on_stdout': function('s:on_stdout'),
  51. \ 'on_stderr': function('s:on_stderr'),
  52. \ 'on_exit' : function('s:on_exit'),
  53. \ 'status_type' : status_type,
  54. \ 'status_dir' : status_dir,
  55. \ 'started_at' : started_at,
  56. \ 'for' : a:for,
  57. \ 'errorformat': &errorformat,
  58. \ }
  59. " execute go build in the files directory
  60. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
  61. " cleanup previous jobs for this file
  62. for jb in values(s:jobs)
  63. if jb.importpath == job.importpath
  64. unlet s:jobs[jb.id]
  65. endif
  66. endfor
  67. let dir = getcwd()
  68. let jobdir = fnameescape(expand("%:p:h"))
  69. execute cd . jobdir
  70. " append the subcommand, such as 'build'
  71. let argv = ['go'] + a:args
  72. " run, forrest, run!
  73. let id = jobstart(argv, job)
  74. let job.id = id
  75. let job.dir = jobdir
  76. let s:jobs[id] = job
  77. execute cd . fnameescape(dir)
  78. return job
  79. endfunction
  80. " on_exit is the exit handler for jobstart(). It handles cleaning up the job
  81. " references and also displaying errors in the quickfix window collected by
  82. " on_stderr handler. If there are no errors and a quickfix window is open,
  83. " it'll be closed.
  84. function! s:on_exit(job_id, exit_status, event) dict abort
  85. let status = {
  86. \ 'desc': 'last status',
  87. \ 'type': self.status_type,
  88. \ 'state': "success",
  89. \ }
  90. if a:exit_status
  91. let status.state = "failed"
  92. endif
  93. let elapsed_time = reltimestr(reltime(self.started_at))
  94. " strip whitespace
  95. let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '')
  96. let status.state .= printf(" (%ss)", elapsed_time)
  97. call go#statusline#Update(self.status_dir, status)
  98. let std_combined = self.stderr + self.stdout
  99. let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
  100. let dir = getcwd()
  101. execute cd self.dir
  102. call s:callback_handlers_on_exit(s:jobs[a:job_id], a:exit_status, std_combined)
  103. let l:listtype = go#list#Type(self.for)
  104. if a:exit_status == 0
  105. call go#list#Clean(l:listtype)
  106. let self.state = "SUCCESS"
  107. if get(g:, 'go_echo_command_info', 1)
  108. call go#util#EchoSuccess("[" . self.status_type . "] SUCCESS")
  109. endif
  110. execute cd . fnameescape(dir)
  111. return
  112. endif
  113. let self.state = "FAILED"
  114. if get(g:, 'go_echo_command_info', 1)
  115. call go#util#EchoError("[" . self.status_type . "] FAILED")
  116. endif
  117. " parse the errors relative to self.jobdir
  118. call go#list#ParseFormat(l:listtype, self.errorformat, std_combined, self.for)
  119. let errors = go#list#Get(l:listtype)
  120. execute cd . fnameescape(dir)
  121. if !len(errors)
  122. " failed to parse errors, output the original content
  123. call go#util#EchoError(std_combined[0])
  124. return
  125. endif
  126. " if we are still in the same windows show the list
  127. if self.winnr == winnr()
  128. call go#list#Window(l:listtype, len(errors))
  129. if !empty(errors) && !self.bang
  130. call go#list#JumpToFirst(l:listtype)
  131. endif
  132. endif
  133. endfunction
  134. " callback_handlers_on_exit runs all handlers for job on exit event.
  135. function! s:callback_handlers_on_exit(job, exit_status, data) abort
  136. if empty(s:handlers)
  137. return
  138. endif
  139. for s:handler in values(s:handlers)
  140. call s:handler(a:job, a:exit_status, a:data)
  141. endfor
  142. endfunction
  143. " on_stdout is the stdout handler for jobstart(). It collects the output of
  144. " stderr and stores them to the jobs internal stdout list.
  145. function! s:on_stdout(job_id, data, event) dict abort
  146. call extend(self.stdout, a:data)
  147. endfunction
  148. " on_stderr is the stderr handler for jobstart(). It collects the output of
  149. " stderr and stores them to the jobs internal stderr list.
  150. function! s:on_stderr(job_id, data, event) dict abort
  151. call extend(self.stderr, a:data)
  152. endfunction
  153. " vim: sw=2 ts=2 et