Skip to content

Commit

Permalink
System.Job: Add System.Job and System.Process.Job
Browse files Browse the repository at this point in the history
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
lambdalisue committed Jul 1, 2017
1 parent 7e9936c commit 0eaaca1
Show file tree
Hide file tree
Showing 18 changed files with 1,111 additions and 30 deletions.
40 changes: 32 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
language: generic
dist: trusty
sudo: false
git:
depth: 10
Expand All @@ -11,36 +12,59 @@ matrix:
env: VIM_VERSION=v8.0.0000
- os: linux
env: VIM_VERSION=master
- os: linux
env: VIM_VERSION=nvim
- os: osx
- os: osx
env: VIM_VERSION=nvim

addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-trusty-4.0
packages:
- language-pack-ja
- vim
- libperl-dev
- python-dev
- python3-dev
- liblua5.1-0-dev
- lua5.1
# For Neovim
- autoconf
- automake
- apport
- build-essential
- clang-4.0
- cmake
- cscope
- g++-5-multilib
- g++-multilib
- gcc-5-multilib
- gcc-multilib
- gdb
- language-pack-tr
- libc6-dev-i386
- libtool
- llvm-4.0-dev
- locales
- pkg-config
- unzip
- valgrind
- xclip

install:
- bash scripts/install-vim.sh
- export PATH=$HOME/vim/bin:$PATH

before_script:
- git clone --depth 1 --branch v1.5.3 --single-branch https://github.com/thinca/vim-themis /tmp/vim-themis
- git clone --depth 1 https://github.com/Shougo/vimproc.vim /tmp/vimproc
- (cd /tmp/vimproc && make)

script:
- uname -a
- which -a vim
- vim --cmd version --cmd quit
- vim --cmd "try | helptags doc/ | catch | cquit | endtry" --cmd quit
# - /tmp/vim-themis/bin/themis --runtimepath /tmp/vimproc --reporter dot
- /tmp/vim-themis/bin/themis --runtimepath /tmp/vimproc --exclude ConcurrentProcess --reporter dot
- ruby scripts/check-changelog.rb
- bash scripts/run-tests.sh

notifications:
webhooks:
urls:
Expand Down
188 changes: 188 additions & 0 deletions autoload/vital/__vital__/System/Job.vim
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
74 changes: 74 additions & 0 deletions autoload/vital/__vital__/System/Process/Job.vim
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
Loading

0 comments on commit 0eaaca1

Please sign in to comment.