-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
System.Job: Add System.Job and System.Process.Job
Backport System.Job and System.Process.Job from https://github.com/lambdalisue/vital-System-Job To fix travis for Neovim, the following are also performed - Use 'trusty' instead to upgrade cmake newer - Add requirements for build Neovim - Fix test scripts - Remove unnecessary flags to reduce dependencies - --enable-perlinterp - --enable-rubyinterp
- Loading branch information
1 parent
7e9936c
commit 0eaaca1
Showing
18 changed files
with
1,111 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
let s:t_list = type([]) | ||
|
||
|
||
" NOTE: | ||
" Attributes of {options} dictionary start from double underscore (__) are | ||
" used internally so no custom attributes shall start from that. | ||
if has('nvim') | ||
function! s:start(args, ...) abort | ||
call s:_validate_args(a:args) | ||
" Build options for jobstart | ||
let options = get(a:000, 0, {}) | ||
let job = extend(copy(options), s:job) | ||
if has_key(options, 'on_exit') | ||
let job.__on_exit = options.on_exit | ||
endif | ||
" Start job and return a job instance | ||
let job.__job = jobstart(a:args, job) | ||
let job.__status = job.__job > 0 ? 'run' : 'dead' | ||
let job.args = a:args | ||
return job | ||
endfunction | ||
|
||
|
||
" Instance ------------------------------------------------------------------- | ||
let s:job = {} | ||
|
||
function! s:job.id() abort | ||
return self.__job | ||
endfunction | ||
|
||
function! s:job.status() abort | ||
return self.__status | ||
endfunction | ||
|
||
function! s:job.send(data) abort | ||
return jobsend(self.__job, a:data) | ||
endfunction | ||
|
||
function! s:job.stop() abort | ||
try | ||
call jobstop(self.__job) | ||
let self.__status = 'dead' | ||
catch /^Vim\%((\a\+)\)\=:E900/ | ||
" NOTE: | ||
" Vim does not raise exception even the job has already closed so fail | ||
" silently for 'E900: Invalid job id' exception | ||
endtry | ||
endfunction | ||
|
||
function! s:job.wait(...) abort | ||
let timeout = get(a:000, 0, v:null) | ||
if timeout is# v:null | ||
return jobwait([self.__job])[0] | ||
else | ||
return jobwait([self.__job], timeout)[0] | ||
endif | ||
endfunction | ||
|
||
function! s:job.on_exit(job, msg, event) abort | ||
" Update job status | ||
let self.__status = 'dead' | ||
" Call user specified callback if exists | ||
if has_key(self, '__on_exit') | ||
call call(self.__on_exit, [a:job, a:msg, a:event], self) | ||
endif | ||
endfunction | ||
else | ||
function! s:start(args, ...) abort | ||
call s:_validate_args(a:args) | ||
let job = extend(copy(s:job), get(a:000, 0, {})) | ||
let job_options = { | ||
\ 'mode': 'raw', | ||
\ 'timeout': 10000, | ||
\ 'out_cb': function('s:_on_msg_cb', ['stdout', job]), | ||
\ 'err_cb': function('s:_on_msg_cb', ['stderr', job]), | ||
\ 'exit_cb': function('s:_on_exit_cb', [job]), | ||
\} | ||
let job.__job = job_start(a:args, job_options) | ||
let job.__channel = job_getchannel(job.__job) | ||
let job.args = a:args | ||
return job | ||
endfunction | ||
|
||
function! s:_on_msg_cb(name, job, channel, msg) abort | ||
let cb_name = 'on_' . a:name | ||
if has_key(a:job, cb_name) | ||
call call(a:job[cb_name], [a:channel, split(a:msg, '\r\?\n', 1), a:name]) | ||
endif | ||
endfunction | ||
|
||
function! s:_on_exit_cb(job, job8, exitval) abort | ||
" There might be data remain so read channel and call corresponding | ||
" callbacks to mimic 'on_exit' of Neovim | ||
call s:_read_channel_and_call_callback(a:job, 'stdout', {}) | ||
call s:_read_channel_and_call_callback(a:job, 'stderr', {'part': 'err'}) | ||
if has_key(a:job, 'on_exit') | ||
call call(a:job.on_exit, [a:job8, a:exitval, 'exit']) | ||
endif | ||
endfunction | ||
|
||
function! s:_read_channel_and_call_callback(job, name, options) abort | ||
let status = ch_status(a:job.__channel, a:options) | ||
while status ==# 'open' || status ==# 'buffered' | ||
if status ==# 'buffered' | ||
let msg = ch_read(a:job.__channel, a:options) | ||
call s:_on_msg_cb(a:name, a:job, a:job.__channel, msg) | ||
endif | ||
" Without sleep, Vim would hung | ||
sleep 1m | ||
let status = ch_status(a:job.__channel, a:options) | ||
endwhile | ||
endfunction | ||
|
||
" Instance ------------------------------------------------------------------- | ||
let s:job = {} | ||
|
||
function! s:job.id() abort | ||
return str2nr(matchstr(string(self.__job), '^process \zs\d\+\ze')) | ||
endfunction | ||
|
||
" NOTE: | ||
" On Unix a non-existing command results in "dead" instead | ||
" So returns "dead" instead of "fail" even in non Unix. | ||
function! s:job.status() abort | ||
let status = job_status(self.__job) | ||
return status ==# 'fail' ? 'dead' : status | ||
endfunction | ||
|
||
" NOTE: | ||
" A Null character (\0) is used as a terminator of a string in Vim. | ||
" Neovim can send \0 by using \n splitted list but in Vim. | ||
" So replace all \n in \n splitted list to '' | ||
function! s:job.send(data) abort | ||
let data = type(a:data) == s:t_list | ||
\ ? join(map(a:data, 'substitute(v:val, "\n", '''', ''g'')'), "\n") | ||
\ : a:data | ||
return ch_sendraw(self.__channel, data) | ||
endfunction | ||
|
||
function! s:job.stop() abort | ||
return job_stop(self.__job) | ||
endfunction | ||
|
||
function! s:job.wait(...) abort | ||
if !has('patch-8.0.0027') | ||
throw 'vital: System.Job: Vim 8.0.0026 and earlier is not supported.' | ||
endif | ||
let timeout = get(a:000, 0, v:null) | ||
let timeout = timeout is# v:null ? v:null : timeout / 1000.0 | ||
let start_time = reltime() | ||
try | ||
while timeout is# v:null || timeout > reltimefloat(reltime(start_time)) | ||
let status = self.status() | ||
if status ==# 'fail' | ||
return -3 | ||
elseif status ==# 'dead' | ||
let info = job_info(self.__job) | ||
return info.exitval | ||
endif | ||
" Without sleep, Vim hung. | ||
sleep 1m | ||
endwhile | ||
catch /^Vim:Interrupt$/ | ||
call self.stop() | ||
return 1 | ||
endtry | ||
return -1 | ||
endfunction | ||
endif | ||
|
||
|
||
" Note: | ||
" A string {args} is not permitted while Vim/Neovim treat that a bit | ||
" differently and makes thing complicated. | ||
" Note: | ||
" Vim does not raise E902 on Unix system even the prog is not found so use a | ||
" custom exception instead to make the method compatible. | ||
function! s:_validate_args(args) abort | ||
if type(a:args) != s:t_list | ||
throw 'vital: System.Job: Argument requires to be a List instance.' | ||
elseif len(a:args) == 0 | ||
throw 'vital: System.Job: Argument vector must have at least one item.' | ||
endif | ||
let prog = a:args[0] | ||
if !executable(prog) | ||
throw printf('vital: System.Job: "%s" is not an executable', prog) | ||
endif | ||
endfunction |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
function! s:_vital_loaded(V) abort | ||
let s:Job = a:V.import('System.Job') | ||
let s:Prelude = a:V.import('Prelude') | ||
let s:String = a:V.import('Data.String') | ||
endfunction | ||
|
||
function! s:_vital_depends() abort | ||
return ['System.Job', 'Prelude', 'Data.String'] | ||
endfunction | ||
|
||
function! s:is_available() abort | ||
if has('nvim') | ||
return 1 | ||
elseif has('patch-8.0.0027') | ||
return 1 | ||
endif | ||
return 0 | ||
endfunction | ||
|
||
function! s:is_supported(options) abort | ||
if get(a:options, 'background') && ( | ||
\ s:Prelude.is_string(get(a:options, 'input')) || | ||
\ get(a:options, 'timeout') | ||
\) | ||
return 0 | ||
endif | ||
return 1 | ||
endfunction | ||
|
||
function! s:execute(args, options) abort | ||
let cmdline = join(a:args) | ||
if a:options.debug > 0 | ||
echomsg printf( | ||
\ 'vital: System.Process.Job: %s', | ||
\ cmdline | ||
\) | ||
endif | ||
let stream = copy(s:stream) | ||
let stream.timeout = get(a:options, 'timeout') | ||
let stream._content = [] | ||
let job = s:Job.start(a:args, stream) | ||
if a:options.background | ||
return { | ||
\ 'status': 0, | ||
\ 'job': job, | ||
\ 'output': '', | ||
\} | ||
else | ||
let status = job.wait(a:options.timeout == 0 ? v:null : a:options.timeout) | ||
" Follow vimproc's status for backward compatibility | ||
let status = status == -1 ? 15 : status | ||
return { | ||
\ 'job': job, | ||
\ 'status': status, | ||
\ 'output': join(stream._content, "\n"), | ||
\} | ||
endif | ||
endfunction | ||
|
||
|
||
" Stream --------------------------------------------------------------------- | ||
let s:stream = {} | ||
|
||
function! s:stream.on_stdout(job, msg, event) abort | ||
let leading = get(self._content, -1, '') | ||
silent! call remove(self._content, -1) | ||
call extend(self._content, [leading . get(a:msg, 0, '')] + a:msg[1:]) | ||
endfunction | ||
|
||
function! s:stream.on_stderr(job, msg, event) abort | ||
let leading = get(self._content, -1, '') | ||
silent! call remove(self._content, -1) | ||
call extend(self._content, [leading . get(a:msg, 0, '')] + a:msg[1:]) | ||
endfunction |
Oops, something went wrong.