"=============================================================================
" FILE: vimproc.vim
" AUTHOR:  Shougo Matsushita <Shougo.Matsu@gmail.com> (Modified)
"          Yukihiro Nakadaira <yukihiro.nakadaira at gmail.com> (Original)
" License: MIT license  {{{
"     Permission is hereby granted, free of charge, to any person obtaining
"     a copy of this software and associated documentation files (the
"     "Software"), to deal in the Software without restriction, including
"     without limitation the rights to use, copy, modify, merge, publish,
"     distribute, sublicense, and/or sell copies of the Software, and to
"     permit persons to whom the Software is furnished to do so, subject to
"     the following conditions:
"
"     The above copyright notice and this permission notice shall be included
"     in all copies or substantial portions of the Software.
"
"     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
"     OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
"     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
"     IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
"     CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
"     TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
"     SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
" }}}
"=============================================================================


if exists('g:vimproc#disable')
  finish
endif

" Saving 'cpoptions' {{{
let s:save_cpo = &cpo
set cpo&vim
" }}}

function! s:print_error(string) abort
  echohl Error | echomsg '[vimproc] ' . a:string | echohl None
endfunction

" Check 'encoding' "{{{
if &encoding =~# '^euc-jp'
  call s:print_error('Sorry, vimproc does not support this encoding environment.')
  call s:print_error('You should set ''encoding'' option to "utf-8" '
        \ .'and set ''termencoding'' option to "euc-jp".')
  finish
endif
"}}}

" Version info "{{{
let s:MAJOR_VERSION = 9
let s:MINOR_VERSION = 3
let s:VERSION_NUMBER = str2nr(printf('%2d%02d', s:MAJOR_VERSION, s:MINOR_VERSION))
let s:VERSION_STRING = printf('%d.%d', s:MAJOR_VERSION, s:MINOR_VERSION)
"}}}

" Global options definition. "{{{
" Set the default of g:vimproc_dll_path by judging OS "{{{
if vimproc#util#is_windows()
  let s:vimproc_dll_basename = has('win64') ?
        \ 'vimproc_win64.dll' : 'vimproc_win32.dll'
elseif vimproc#util#is_cygwin()
  let s:vimproc_dll_basename = 'vimproc_cygwin.dll'
  if system('uname -m') =~? 'x86_64'
    let s:vimproc_dll_basename = 'vimproc_cygwin64.dll'
  endif
elseif vimproc#util#is_mac()
  let s:vimproc_dll_basename = 'vimproc_mac.so'
elseif glob('/lib*/ld-linux*64.so.2',1) != ''
  let s:vimproc_dll_basename = 'vimproc_linux64.so'
elseif glob('/lib*/ld-linux*.so.2',1) != ''
  let s:vimproc_dll_basename = 'vimproc_linux32.so'
elseif system('uname -s') =~? 'BSD'
  let s:vimproc_dll_basename = system(
        \ 'uname -sm | tr "[:upper:]" "[:lower:]"'
        \ .' | sed -e "s/ /_/" | xargs -I "{}" echo "vimproc_{}.so"')[0 : -2]
else
  let s:vimproc_dll_basename = 'vimproc_unix.so'
endif
"}}}

call vimproc#util#set_default(
      \ 'g:vimproc#dll_path',
      \ expand('<sfile>:p:h:h') . '/lib/' . s:vimproc_dll_basename,
      \ 'g:vimproc_dll_path')
unlet s:vimproc_dll_basename

call vimproc#util#set_default(
      \'g:vimproc#download_windows_dll', 0)
call vimproc#util#set_default(
      \ 'g:vimproc#password_pattern',
      \ '\%(Enter \|Repeat \|[Oo]ld \|[Nn]ew \|login ' .
      \'\|Kerberos \|EncFS \|CVS \|UNIX \| SMB \|LDAP \|\[sudo] ' .
      \'\|^\|\n\|''s \)\%([Pp]assword\|[Pp]assphrase\)\>',
      \ 'g:vimproc_password_pattern')
call vimproc#util#set_default(
      \ 'g:vimproc#popen2_commands', {
      \     'sh' : 1, 'bash' : 1, 'zsh' : 1, 'csh' : 1, 'tcsh' : 1,
      \     'tmux' : 1, 'screen' : 1, 'su' : 1,
      \     'python' : 1, 'rhino' : 1, 'ipython' : 1, 'ipython3' : 1, 'yaourt' : 1,
      \ }, 'g:vimproc_popen2_commands')
call vimproc#util#set_default(
      \ 'g:stdinencoding', 'char')
call vimproc#util#set_default(
      \ 'g:stdoutencoding', 'char')
call vimproc#util#set_default(
      \ 'g:stderrencoding', 'char')
"}}}

" Constants {{{
function! s:define_signals() abort
  let s:signames = {}
  let xs = s:libcall('vp_get_signals', [])
  for x in xs
    let [name, val] = split(x, ':')
    let nr = str2nr(val)
    let g:vimproc#{name} = nr
    let s:signames[nr] = name
  endfor
endfunction
" }}}

let g:vimproc#dll_path =
      \ vimproc#util#iconv(
      \ vimproc#util#expand(g:vimproc#dll_path),
      \ &encoding, vimproc#util#termencoding())

" Backward compatibility.
let g:vimproc_password_pattern = g:vimproc#password_pattern

if g:vimproc#download_windows_dll && !filereadable(g:vimproc#dll_path)
      \ && vimproc#util#is_windows()
  call vimproc#util#try_download_windows_dll(s:VERSION_STRING)
endif

if !filereadable(g:vimproc#dll_path) || !has('libcall') "{{{
  function! vimproc#get_last_status() abort
    return v:shell_error
  endfunction

  function! vimproc#get_last_errmsg() abort
    return ''
  endfunction

  function! vimproc#system(...) abort
    return call('system', a:000)
  endfunction

  if !filereadable(g:vimproc#dll_path)
    call s:print_error(printf('vimproc''s DLL: "%s" is not found.
          \  Please read :help vimproc and make it.', g:vimproc#dll_path))
  else
    call s:print_error('libcall feature is disabled in this Vim.
          \  To use vimproc, you must enable libcall feature.')
  endif

  finish
endif"}}}

function! vimproc#version() abort "{{{
  return s:VERSION_NUMBER
endfunction"}}}
function! vimproc#dll_version() abort "{{{
  let [dll_version] = s:libcall('vp_dlversion', [])
  return str2nr(dll_version)
endfunction"}}}

"-----------------------------------------------------------
" API

function! vimproc#open(filename) abort "{{{
  let filename = vimproc#util#iconv(fnamemodify(a:filename, ':p'),
        \ &encoding, vimproc#util#systemencoding())

  if filename =~ '^\%(https\?\|ftp\)://'
          \ && !vimproc#host_exists(filename)
    " URI is invalid.
    call s:print_error('vimproc#open: URI "' . filename . '" is invalid.')
    return
  endif

  " Detect desktop environment.
  if vimproc#util#is_windows()
    " For URI only.
    "execute '!start rundll32 url.dll,FileProtocolHandler' filename

    call s:libcall('vp_open', [filename])
  elseif has('win32unix')
    " Cygwin.
    call vimproc#system(['cygstart', filename])
  elseif executable('xdg-open')
    " Linux.
    call vimproc#system_bg(['xdg-open', filename])
  elseif exists('$KDE_FULL_SESSION') && $KDE_FULL_SESSION ==# 'true'
    " KDE.
    call vimproc#system_bg(['kioclient', 'exec', filename])
  elseif exists('$GNOME_DESKTOP_SESSION_ID')
    " GNOME.
    call vimproc#system_bg(['gnome-open', filename])
  elseif executable('exo-open')
    " Xfce.
    call vimproc#system_bg(['exo-open', filename])
  elseif vimproc#util#is_mac() && executable('open')
    " Mac OS.
    call vimproc#system_bg(['open', filename])
  else
    " Give up.
    call s:print_error('vimproc#open: Not supported.')
  endif
endfunction"}}}

function! vimproc#get_command_name(command, ...) abort "{{{
  let path = get(a:000, 0, $PATH)

  let cnt = a:0 < 2 ? 1 : a:2

  let files = split(substitute(vimproc#util#substitute_path_separator(
        \ vimproc#filepath#which(a:command, path, cnt)), '//', '/', 'g'), '\n')

  if cnt < 0
    return files
  endif

  let file = get(files, cnt-1, '')

  if file == ''
    throw printf(
          \ 'vimproc#get_command_name: File "%s" is not found.', a:command)
  endif

  return file
endfunction"}}}

function! s:system(cmdline, is_passwd, input, timeout, is_pty) abort "{{{
  let s:last_status = 0
  let s:last_errmsg = ''

  if empty(a:cmdline)
    return ''
  endif

  " Open pipe.
  try
    let subproc = (type(a:cmdline[0]) == type('')) ? vimproc#popen3(a:cmdline) :
          \ a:is_pty ? vimproc#ptyopen(a:cmdline):
          \ vimproc#pgroup_open(a:cmdline)
  catch
    call s:print_error(v:exception)
    let s:last_status = 1
    let s:last_errmsg = v:exception
    return ''
  endtry

  let outbuf = []
  let errbuf = []

  try
    if a:input != ''
      " Write input.
      call subproc.stdin.write(a:input)
    endif

    if a:timeout > 0 && has('reltime') && v:version >= 702
      let start = reltime()
      let deadline = a:timeout
      let timeout = a:timeout / 2
    else
      let start = 0
      let deadline = 0
      let timeout = s:read_timeout
    endif

    if !a:is_passwd
      call subproc.stdin.close()
    endif

    while !subproc.stdout.eof || !subproc.stderr.eof
      if deadline "{{{
        " Check timeout.
        let tick = reltimestr(reltime(start))
        let elapse = str2nr(tick[:-8] . tick[-6:-4], 10)
        if deadline <= elapse && !subproc.stdout.eof
          " Kill process.
          throw 'vimproc: vimproc#system(): Timeout.'
        endif
        let timeout = (deadline - elapse) / 2
      endif"}}}

      if !subproc.stdout.eof "{{{
        let out = subproc.stdout.read(-1, timeout)

        if a:is_passwd && out =~# g:vimproc_password_pattern
          redraw
          echo out

          " Password input.
          set imsearch=0
          let in = vimproc#util#iconv(inputsecret('Input Secret : ')."\<NL>",
                \ &encoding, vimproc#util#termencoding())

          call subproc.stdin.write(in)
        else
          let outbuf += [out]
        endif
      endif"}}}

      if !subproc.stderr.eof "{{{
        let out = subproc.stderr.read(-1, timeout)

        if a:is_passwd && out =~# g:vimproc_password_pattern
          redraw
          echo out

          " Password input.
          set imsearch=0
          let in = vimproc#util#iconv(inputsecret('Input Secret : ') . "\<NL>",
                \ &encoding, vimproc#util#termencoding())

          call subproc.stdin.write(in)
        else
          let outbuf += [out]
          let errbuf += [out]
        endif
      endif"}}}
    endwhile
  catch
    call subproc.kill(g:vimproc#SIGTERM)

    if v:exception !~ '^Vim:Interrupt'
      call s:print_error(v:throwpoint)
      call s:print_error(v:exception)
    endif
  finally
    let output = join(outbuf, '')
    let s:last_errmsg = join(errbuf, '')

    call subproc.waitpid()
  endtry

  " Newline convert.
  if vimproc#util#is_mac()
    let output = substitute(output, '\r\n\@!', '\n', 'g')
  elseif has('win32') || has('win64')
    let output = substitute(output, '\r\n', '\n', 'g')
  endif

  return output
endfunction"}}}
function! vimproc#system(cmdline, ...) abort "{{{
  if type(a:cmdline) == type('')
    if a:cmdline =~ '&\s*$'
      let cmdline = substitute(a:cmdline, '&\s*$', '', '')
      return vimproc#system_bg(cmdline)
    endif

    let args = vimproc#parser#parse_statements(a:cmdline)
    for arg in args
      let arg.statement = vimproc#parser#parse_pipe(arg.statement)
    endfor
  else
    let args = [{
          \ 'statement' : [ {
          \ 'fd' : { 'stdin' : '', 'stdout' : '', 'stderr' : '' },
          \ 'args' : a:cmdline
          \  }],
          \ 'condition' : 'always',
          \ 'cwd' : getcwd(),
          \ }]
  endif

  let timeout = get(a:000, 1, 0)
  let input = get(a:000, 0, '')

  return s:system(args, 0, input, timeout, 0)
endfunction"}}}
function! vimproc#system2(...) abort "{{{
  if empty(a:000)
    return ''
  endif

  if len(a:0) > 1
    let args = deepcopy(a:000)
    let args[1] = vimproc#util#iconv(
          \ args[1], &encoding, vimproc#util#stdinencoding())
  else
    let args = a:000
  endif
  let output = call('vimproc#system', args)

  " This function converts application encoding to &encoding.
  let output = vimproc#util#iconv(
        \ output, vimproc#util#stdoutencoding(), &encoding)
  let s:last_errmsg = vimproc#util#iconv(
        \ s:last_errmsg, vimproc#util#stderrencoding(), &encoding)

  return output
endfunction"}}}
function! vimproc#system_passwd(cmdline, ...) abort "{{{
  if type(a:cmdline) == type('')
    let args = vimproc#parser#parse_pipe(a:cmdline)
  else
    let args = [{ 'fd' : { 'stdin' : '', 'stdout' : '', 'stderr' : '' },
          \ 'args' : a:cmdline }]
  endif

  let timeout = a:0 >= 2 ? a:2 : 0
  let input = a:0 >= 1 ? a:1 : ''

  let lang_save = $LANG
  try
    let $LANG = 'C'

    return s:system(args, 1, input, timeout, 1)
  finally
    let $LANG = lang_save
  endtry
endfunction"}}}
function! vimproc#system_bg(cmdline) abort "{{{
  " Open pipe.
  if type(a:cmdline) == type('')
    if a:cmdline =~ '&\s*$'
      let cmdline = substitute(a:cmdline, '&\s*$', '', '')
      return vimproc#system_bg(cmdline)
    endif

    let args = vimproc#parser#parse_statements(a:cmdline)
    for arg in args
      let arg.statement = vimproc#parser#parse_pipe(arg.statement)
    endfor
  else
    let args = [{
          \ 'statement' : [ {
          \ 'fd' : { 'stdin' : '', 'stdout' : '', 'stderr' : '' },
          \ 'args' : a:cmdline
          \  }],
          \ 'condition' : 'always',
          \ 'cwd' : getcwd(),
          \ }]
  endif

  let subproc = vimproc#pgroup_open(args)
  if empty(subproc)
    " Not supported path error.
    return ''
  endif

  " Close handles.
  call s:close_all(subproc)

  let s:bg_processes[subproc.pid] = subproc.pid

  return ''
endfunction"}}}
function! vimproc#system_gui(cmdline) abort "{{{
  return vimproc#system_bg(a:cmdline)
endfunction"}}}

function! vimproc#get_last_status() abort "{{{
  return s:last_status
endfunction"}}}
function! vimproc#get_last_errmsg() abort "{{{
  return substitute(vimproc#util#iconv(s:last_errmsg,
        \ vimproc#util#stderrencoding(), &encoding), '\n$', '', '')
endfunction"}}}

function! vimproc#shellescape(string) abort "{{{
  return string(a:string)
endfunction"}}}

function! vimproc#fopen(path, ...) abort "{{{
  let flags = get(a:000, 0, 'r')
  let mode = get(a:000, 1, 0644)
  let fd = s:vp_file_open((s:is_null_device(a:path)
        \ ? s:null_device : a:path), flags, mode)
  let proc = s:fdopen(fd, 'vp_file_close', 'vp_file_read', 'vp_file_write')
  return proc
endfunction"}}}

function! vimproc#popen2(args, ...) abort "{{{
  let args = type(a:args) == type('') ?
        \ vimproc#parser#split_args(a:args) :
        \ a:args
  let is_pty = get(a:000, 0, 0)

  return s:plineopen(2, [{
        \ 'args' : args,
        \ 'fd' : { 'stdin' : '', 'stdout' : '', 'stderr' : '' },
        \ }], is_pty)
endfunction"}}}
function! vimproc#popen3(args, ...) abort "{{{
  let args = type(a:args) == type('') ?
        \ vimproc#parser#split_args(a:args) :
        \ a:args
  let is_pty = get(a:000, 0, 0)

  return s:plineopen(3, [{
        \ 'args' : args,
        \ 'fd' : { 'stdin' : '', 'stdout' : '', 'stderr' : '' },
        \ }], is_pty)
endfunction"}}}

function! vimproc#plineopen2(commands, ...) abort "{{{
  let commands = type(a:commands) == type('') ?
        \ vimproc#parser#parse_pipe(a:commands) :
        \ a:commands
  let is_pty = get(a:000, 0, 0)

  return s:plineopen(2, commands, is_pty)
endfunction"}}}
function! vimproc#plineopen3(commands, ...) abort "{{{
  let commands = type(a:commands) == type('') ?
        \ vimproc#parser#parse_pipe(a:commands) :
        \ a:commands
  let is_pty = get(a:000, 0, 0)

  return s:plineopen(3, commands, is_pty)
endfunction"}}}
function! s:plineopen(npipe, commands, is_pty) abort "{{{
  let pid_list = []
  let stdin_list = []
  let stdout_list = []
  let stderr_list = []
  let npipe = a:npipe

  " Open input.
  let hstdin = (empty(a:commands) || a:commands[0].fd.stdin == '')?
        \ 0 : vimproc#fopen(a:commands[0].fd.stdin, 'r').fd

  let is_pty = !vimproc#util#is_windows() && a:is_pty

  let cnt = 0
  for command in a:commands
    if is_pty && command.fd.stdout == '' && cnt == 0
          \ && len(a:commands) != 1
      " pty_open() use pipe.
      let hstdout = 1
    else
      if command.fd.stdout =~ '^>'
        let mode = 'a'
        let command.fd.stdout = command.fd.stdout[1:]
      else
        let mode = 'w'
      endif

      let hstdout = s:is_pseudo_device(command.fd.stdout) ?
            \ 0 : vimproc#fopen(command.fd.stdout, mode).fd
    endif

    if is_pty && command.fd.stderr == '' && cnt == 0
          \ && len(a:commands) != 1
      " pty_open() use pipe.
      let hstderr = 1
    else
      if command.fd.stderr =~ '^>'
        let mode = 'a'
        let command.fd.stderr = command.fd.stderr[1:]
      else
        let mode = 'w'
      endif
      let hstderr = s:is_pseudo_device(command.fd.stderr) ?
            \ 0 : vimproc#fopen(command.fd.stderr, mode).fd
    endif

    if command.fd.stderr ==# '/dev/stdout'
      let npipe = 2
    endif

    let args = s:convert_args(command.args)
    let command_name = fnamemodify(args[0], ':t:r')
    let pty_npipe = cnt == 0
          \ && hstdin == 0 && hstdout == 0 && hstderr == 0
          \ && exists('g:vimproc#popen2_commands')
          \ && get(g:vimproc#popen2_commands, command_name, 0) != 0 ?
          \ 2 : npipe

    if is_pty && (cnt == 0 || cnt == len(a:commands)-1)
      " Use pty_open().
      let pipe = s:vp_pty_open(pty_npipe,
            \ s:get_winwidth(), winheight(0),
            \ hstdin, hstdout, hstderr, args)
    else
      let pipe = s:vp_pipe_open(pty_npipe,
            \ hstdin, hstdout, hstderr, args)
    endif

    if len(pipe) == 4
      let [pid, fd_stdin, fd_stdout, fd_stderr] = pipe
      let stderr = s:fdopen(fd_stderr,
            \ 'vp_pipe_close', 'vp_pipe_read', 'vp_pipe_write')
    else
      let [pid, fd_stdin, fd_stdout] = pipe
      let stderr = s:closed_fdopen(
            \ 'vp_pipe_close', 'vp_pipe_read', 'vp_pipe_write')
    endif

    call add(pid_list, pid)
    let stdin = s:fdopen(fd_stdin,
          \ 'vp_pipe_close', 'vp_pipe_read', 'vp_pipe_write')
    let stdin.is_pty = is_pty
          \ && (cnt == 0 || cnt == len(a:commands)-1)
          \ && hstdin == 0
    call add(stdin_list, stdin)
    let stdout = s:fdopen(fd_stdout,
          \ 'vp_pipe_close', 'vp_pipe_read', 'vp_pipe_write')
    let stdout.is_pty = is_pty
          \ && (cnt == 0 || cnt == len(a:commands)-1)
          \ && hstdout == 0
    call add(stdout_list, stdout)
    let stderr.is_pty = is_pty
          \ && (cnt == 0 || cnt == len(a:commands)-1)
          \ && hstderr == 0
    call add(stderr_list, stderr)

    let hstdin = stdout_list[-1].fd
    let cnt += 1
  endfor

  let proc = {}
  let proc.pid_list = pid_list
  let proc.pid = pid_list[-1]
  let proc.stdin = s:fdopen_pipes(stdin_list,
        \ 'vp_pipes_close', 'read_pipes', 'write_pipes')
  let proc.stdout = s:fdopen_pipes(stdout_list,
        \ 'vp_pipes_close', 'read_pipes', 'write_pipes')
  let proc.stderr = s:fdopen_pipes(stderr_list,
        \ 'vp_pipes_close', 'read_pipes', 'write_pipes')
  let proc.get_winsize = s:funcref('vp_get_winsize')
  let proc.set_winsize = s:funcref('vp_set_winsize')
  let proc.kill = s:funcref('vp_kill')
  let proc.waitpid = s:funcref('vp_waitpid')
  let proc.checkpid = s:funcref('vp_checkpid')
  let proc.is_valid = 1
  let proc.is_pty = is_pty
  if a:is_pty
    let proc.ttyname = ''
    let proc.width = winwidth(0) - &l:numberwidth - &l:foldcolumn
    let proc.height = winheight(0)
    let proc.get_winsize = s:funcref('vp_get_winsize')
    let proc.set_winsize = s:funcref('vp_set_winsize')
  endif

  return proc
endfunction"}}}

let s:null_device = vimproc#util#is_windows() ? 'NUL' : '/dev/null'

function! s:is_null_device(filename) abort
  return a:filename ==# '/dev/null'
endfunction

function! s:is_pseudo_device(filename) abort "{{{
  if vimproc#util#is_windows() && (
    \    a:filename ==# '/dev/stdin'
    \ || a:filename ==# '/dev/stdout'
    \ || a:filename ==# '/dev/stderr')
    return 1
  endif

  return a:filename == ''
        \ || a:filename ==# '/dev/clip'
        \ || a:filename ==# '/dev/quickfix'
endfunction"}}}

function! vimproc#pgroup_open(statements, ...) abort "{{{
  if type(a:statements) == type('')
    let statements =
          \ vimproc#parser#parse_statements(a:statements)
    for statement in statements
      let statement.statement =
            \ vimproc#parser#parse_pipe(statement.statement)
    endfor
  else
    let statements = a:statements
  endif

  let is_pty = get(a:000, 0, 0)
  let npipe = get(a:000, 1, 3)

  return s:pgroup_open(statements, is_pty && !vimproc#util#is_windows(), npipe)
endfunction"}}}

function! s:pgroup_open(statements, is_pty, npipe) abort "{{{
  let proc = {}

  let cwd = getcwd()
  try
    call vimproc#util#cd(a:statements[0].cwd)

    let proc.current_proc =
          \ vimproc#plineopen{a:npipe}(a:statements[0].statement, a:is_pty)
  finally
    call vimproc#util#cd(cwd)
  endtry

  let proc.pid = proc.current_proc.pid
  let proc.pid_list = proc.current_proc.pid_list
  let proc.condition = a:statements[0].condition
  let proc.statements = a:statements[1:]
  let proc.stdin = s:fdopen_pgroup(proc, proc.current_proc.stdin,
        \ 'vp_pgroup_close', 'read_pgroup', 'write_pgroup')
  let proc.stdout = s:fdopen_pgroup(proc, proc.current_proc.stdout,
        \ 'vp_pgroup_close', 'read_pgroup', 'write_pgroup')
  let proc.stderr = s:fdopen_pgroup(proc, proc.current_proc.stderr,
        \ 'vp_pgroup_close', 'read_pgroup', 'write_pgroup')
  let proc.kill = s:funcref('vp_pgroup_kill')
  let proc.waitpid = s:funcref('vp_pgroup_waitpid')
  let proc.is_valid = 1
  let proc.is_pty = 0
  " echomsg expand('<sfile>')
  " echomsg 'open:' string(map(copy(proc.current_proc.stdin.fd), 'v:val.fd'))
  " echomsg 'open:' string(map(copy(proc.current_proc.stdout.fd), 'v:val.fd'))
  " echomsg 'open:' string(map(copy(proc.current_proc.stderr.fd), 'v:val.fd'))

  return proc
endfunction"}}}

function! vimproc#ptyopen(commands, ...) abort "{{{
  let commands = type(a:commands) == type('') ?
        \ vimproc#parser#parse_pipe(a:commands) :
        \ a:commands
  let npipe = get(a:000, 0, 3)

  return s:plineopen(npipe, commands, !vimproc#util#is_windows())
endfunction"}}}

function! vimproc#socket_open(host, port) abort "{{{
  if !vimproc#host_exists(a:host)
    throw printf('vimproc: host "%s" does not exist', a:host)
  endif

  let fd = s:vp_socket_open(a:host, a:port)
  return s:fdopen(fd, 'vp_socket_close', 'vp_socket_read', 'vp_socket_write')
endfunction"}}}

function! vimproc#host_exists(host) abort "{{{
  let rval = s:vp_host_exists(
        \ substitute(substitute(a:host, '^\a\+://', '', ''), '/.*$', '', ''))
  return 0 + rval
endfunction"}}}

function! vimproc#kill(pid, sig) abort "{{{
  if a:sig == 0 && vimproc#util#is_windows()
    " Use waitpid().
    let cond = s:waitpid(a:pid, 1)[0]
    if cond ==# 'error'
      let s:last_errmsg = 'waitpid error'
    endif
    return cond !=# 'run'
  endif

  try
    let [ret] = s:libcall('vp_kill', [a:pid, a:sig])
  catch
    let s:last_errmsg = v:exception
    return 1
  endtry

  return ret
endfunction"}}}

function! vimproc#decode_signal(signal) abort "{{{
  return get(s:signames, a:signal, 'UNKNOWN')
endfunction"}}}

function! vimproc#write(filename, string, ...) abort "{{{
  if a:string == ''
    return
  endif

  let mode = get(a:000, 0,
        \ a:filename =~ '^>' ? 'a' : 'w')

  let filename = a:filename =~ '^>' ?
        \ a:filename[1:] : a:filename

  if s:is_null_device(filename)
    " Nothing.
  elseif filename ==# '/dev/clip'
    " Write to clipboard.

    if mode =~ 'a'
      let @+ .= a:string
    else
      let @+ = a:string
    endif
  elseif filename ==# '/dev/quickfix'
    " Write to quickfix.
    let qflist = getqflist()

    for str in split(a:string, '\n\|\r\n')
      if str =~ '^.\+:.\+:.\+$'
        let line = split(str[2:], ':')
        let filename = str[:1] . line[0]

        if len(line) >= 3 && line[1] =~ '^\d\+$'
          call add(qflist, {
                \ 'filename' : filename,
                \ 'lnum' : line[1],
                \ 'text' : join(line[2:], ':'),
                \ })
        else
          call add(qflist, {
                \ 'text' : str,
                \ })
        endif
      endif
    endfor

    call setqflist(qflist)
  else
    " Write file.
    let hfile = vimproc#fopen(filename, mode)
    call hfile.write(a:string)
    call hfile.close()
  endif
endfunction"}}}

function! vimproc#readdir(dirname) abort "{{{
  let dirname = vimproc#util#expand(a:dirname)
  if dirname == ''
    let dirname = getcwd()
  endif
  let dirname = substitute(dirname, '.\zs/$', '', '')

  if !vimproc#util#is_windows()
    let dirname = substitute(dirname, '//', '/', 'g')
  endif

  if !isdirectory(dirname)
    return []
  endif

  let dirname = vimproc#util#iconv(dirname, &encoding,
        \ vimproc#util#systemencoding())

  try
    let files = s:libcall('vp_readdir', [dirname])
  catch /vp_readdir/
    return []
  endtry

  call map(filter(files, 'v:val !~ "/\\.\\.\\?$"'), 'vimproc#util#iconv(
        \ v:val, vimproc#util#systemencoding(), &encoding)')
  if vimproc#util#is_windows()
    call map(files, 'vimproc#util#substitute_path_separator(v:val)')
  endif
  call map(files, "substitute(v:val, '/\\./', '/', 'g')")

  return files
endfunction"}}}

function! vimproc#delete_trash(filename) abort "{{{
  if !vimproc#util#is_windows()
    call s:print_error('Not implemented in this platform.')
    return
  endif

  let filename = a:filename

  if !filewritable(filename) && !isdirectory(filename)
    return 1
  endif

  " Substitute path separator to "/".
  let filename = substitute(
        \ fnamemodify(filename, ':p'), '/', '\\', 'g')

  " Delete last /.
  if filename =~ '[^:][/\\]$'
    " Delete last /.
    let filename = filename[: -2]
  endif

  " Encoding conversion.
  let filename = vimproc#util#iconv(filename,
        \ &encoding, vimproc#util#systemencoding())

  let [ret] = s:libcall('vp_delete_trash', [filename])

  return str2nr(ret)
endfunction"}}}

function! vimproc#test_readdir(dirname) abort "{{{
  let start = reltime()
  call split(glob(a:dirname.'/*'), '\n')
  echomsg reltimestr(reltime(start))

  let start = reltime()
  call vimproc#readdir(a:dirname)
  echomsg reltimestr(reltime(start))
endfunction"}}}

function! s:close_all(self) abort "{{{
  if has_key(a:self, 'stdin')
    call a:self.stdin.close()
  endif
  if has_key(a:self, 'stdout')
    call a:self.stdout.close()
  endif
  if has_key(a:self, 'stderr')
    call a:self.stderr.close()
  endif
endfunction"}}}
function! s:close() dict "{{{
  if self.is_valid
    call self.f_close()
  endif

  let self.is_valid = 0
  let self.eof = 1
  let self.__eof = 1
endfunction"}}}
function! s:read(...) dict "{{{
  if self.__eof
    let self.eof = 1
    return ''
  endif

  let maxsize = get(a:000, 0, -1)
  let timeout = get(a:000, 1, s:read_timeout)
  let buf = []
  let eof = 0

  while maxsize != 0 && !eof
    let [out, eof] = self.f_read(maxsize,
          \ (timeout < s:read_timeout ? timeout : s:read_timeout))
    if out ==# ''
      let timeout -= s:read_timeout
      if timeout <= 0
        break
      endif
    else
      let buf += [out]
      let maxsize -= len(out)
      let timeout = 0
    endif
  endwhile

  let self.eof = eof
  let self.__eof = eof

  return join(buf, '')
endfunction"}}}
function! s:read_lines(...) dict "{{{
  if self.__eof
    return []
  endif

  let lines = self.buffer[:-2]
  let res = get(self.buffer, -1, '')

  let out = call(self.read, a:000, self)
  if out !=# ''
    let outs = split(out, '\r*\n\|\r', 1)
    let res .= outs[0]
    if len(outs) > 1
      let lines += [substitute(res, '\r*$', '', '')] + outs[1:-2]
      let res = outs[-1]
    endif
  endif

  if self.__eof || out ==# ''
    if res !=# ''
      let lines += [res]
    endif
    let self.buffer = []
  else
    let self.buffer = [res]
  endif

  return lines
endfunction"}}}
function! s:read_line(...) dict "{{{
  let line = ''
  if !self.__eof && len(self.buffer) <= 1
    let lines = call(self.read_lines, a:000, self)
    let self.buffer = lines[1:] + self.buffer
    let line = get(lines, 0, '')
  elseif !empty(self.buffer)
    let [line; self.buffer] = self.buffer
  endif
  let self.eof = self.__eof && empty(self.buffer)
  return line
endfunction"}}}

function! s:write(str, ...) dict "{{{
  let timeout = get(a:000, 0, s:write_timeout)
  return self.f_write(a:str, timeout)
endfunction"}}}

function! s:fdopen(fd, f_close, f_read, f_write) abort "{{{
  return {
        \ 'fd' : a:fd,
        \ 'eof' : 0, '__eof' : 0, 'is_valid' : 1, 'buffer' : [],
        \ 'f_close' : s:funcref(a:f_close), 'f_read' : s:funcref(a:f_read), 'f_write' : s:funcref(a:f_write),
        \ 'close' : s:funcref('close'), 'read' : s:funcref('read'), 'write' : s:funcref('write'),
        \ 'read_line' : s:funcref('read_line'), 'read_lines' : s:funcref('read_lines'),
        \}
endfunction"}}}
function! s:closed_fdopen(f_close, f_read, f_write) abort "{{{
  return {
        \ 'fd' : -1,
        \ 'eof' : 1, '__eof' : 1, 'is_valid' : 0, 'buffer' : [],
        \ 'f_close' : s:funcref(a:f_close), 'f_read' : s:funcref(a:f_read), 'f_write' : s:funcref(a:f_write),
        \ 'close' : s:funcref('close'), 'read' : s:funcref('read'), 'write' : s:funcref('write'),
        \ 'read_line' : s:funcref('read_line'), 'read_lines' : s:funcref('read_lines'),
        \}
endfunction"}}}
function! s:fdopen_pty(fd_stdin, fd_stdout, f_close, f_read, f_write) abort "{{{
  return {
        \ 'eof' : 0, '__eof' : 0, 'is_valid' : 1, 'buffer' : [],
        \ 'fd_stdin' : a:fd_stdin, 'fd_stdout' : a:fd_stdout,
        \ 'f_close' : s:funcref(a:f_close), 'f_read' : s:funcref(a:f_read), 'f_write' : s:funcref(a:f_write),
        \ 'close' : s:funcref('close'), 'read' : s:funcref('read'), 'write' : s:funcref('write'),
        \ 'read_line' : s:funcref('read_line'), 'read_lines' : s:funcref('read_lines'),
        \}
endfunction"}}}
function! s:fdopen_pipes(fd, f_close, f_read, f_write) abort "{{{
  return {
        \ 'eof' : 0, '__eof' : 0, 'is_valid' : 1, 'buffer' : [],
        \ 'fd' : a:fd,
        \ 'f_close' : s:funcref(a:f_close),
        \ 'close' : s:funcref('close'), 'read' : s:funcref(a:f_read), 'write' : s:funcref(a:f_write),
        \ 'read_line' : s:funcref('read_line'), 'read_lines' : s:funcref('read_lines'),
        \}
endfunction"}}}
function! s:fdopen_pgroup(proc, fd, f_close, f_read, f_write) abort "{{{
  return {
        \ 'eof' : 0, '__eof' : 0, 'is_valid' : 1, 'buffer' : [],
        \ 'proc' : a:proc, 'fd' : a:fd,
        \ 'f_close' : s:funcref(a:f_close),
        \ 'close' : s:funcref('close'), 'read' : s:funcref(a:f_read), 'write' : s:funcref(a:f_write),
        \ 'read_line' : s:funcref('read_line'), 'read_lines' : s:funcref('read_lines'),
        \}
endfunction"}}}

function! s:garbage_collect(is_force) abort "{{{
  for pid in values(s:bg_processes)
    " Check processes.
    try
      let [cond, _] = s:libcall('vp_waitpid', [pid])
      " echomsg string([pid, cond, _])
      if cond !=# 'run' || a:is_force
        if cond !=# 'exit'
          " Kill process.
          call vimproc#kill(pid, g:vimproc#SIGTERM)
        endif

        if vimproc#util#is_windows()
          call s:libcall('vp_close_handle', [pid])
        endif
        call remove(s:bg_processes, pid)
      endif
    catch
      " Ignore error.
    endtry
  endfor
endfunction"}}}

" For debug API.
function! vimproc#_get_bg_processes() abort "{{{
  return s:bg_processes
endfunction"}}}

"-----------------------------------------------------------
" UTILS

function! s:str2hd(str) abort
  return join(map(range(len(a:str)),
        \ 'printf("%02X", char2nr(a:str[v:val]))'), '')
endfunction

function! s:hd2str(hd) abort
  " a:hd is a list because to avoid copying the value.
  return get(s:libcall('vp_decode', [a:hd[0]]), 0, '')
endfunction

function! s:hd2str_lua(hd) abort
  let ret = []
  lua << EOF
do
  local ret = vim.eval('ret')
  local hd = vim.eval('a:hd[0]')
  local len = string.len(hd)
  local s = {}
  for i = 1, len, 2 do
    table.insert(s, string.char(tonumber(string.sub(hd, i, i+1), 16)))
  end

  ret:add(table.concat(s))
end
EOF
  return ret[0]
endfunction

function! s:str2list(str) abort
  return map(range(len(a:str)), 'char2nr(a:str[v:val])')
endfunction

function! s:list2str(lis) abort
  return s:hd2str(s:list2hd([a:lis]))
endfunction

function! s:hd2list(hd) abort
  return map(split(a:hd, '..\zs'), 'str2nr(v:val, 16)')
endfunction

function! s:list2hd(lis) abort
  return join(map(a:lis, 'printf("%02X", v:val)'), '')
endfunction

function! s:convert_args(args) abort "{{{
  if empty(a:args)
    return []
  endif

  let args = map(copy(a:args), 'vimproc#util#iconv(
        \ v:val, &encoding, vimproc#util#systemencoding())')

  if vimproc#util#is_windows() && !executable(a:args[0])
    " Search from internal commands.
    let internal_commands = [
          \ 'copy', 'date', 'del', 'dir', 'echo', 'erase', 'for', 'ftype',
          \ 'if', 'md', 'mkdir', 'move', 'path', 'rd', 'ren', 'rename',
          \ 'rmdir', 'start', 'time', 'type', 'ver', 'vol']
    let index = index(internal_commands, a:args[0], 0, 1)
    if index >= 0
      " Use cmd.exe
      return ['cmd', '/c', args[0]] + args[1:]
    endif
  endif

  let command_name = vimproc#get_command_name(a:args[0])

  return map(vimproc#analyze_shebang(command_name), 'vimproc#util#iconv(
        \ v:val, &encoding, vimproc#util#systemencoding())') + args[1:]
endfunction"}}}

function! vimproc#analyze_shebang(filename) abort "{{{
  if !filereadable(a:filename) ||
        \ getfsize(a:filename) > 100000 ||
        \ (vimproc#util#is_windows() &&
        \ '.'.fnamemodify(a:filename, ':e') !~?
        \   '^'.substitute($PATHEXT, ';', '$\\|^', 'g').'$')
      " Maybe a binary file.
    return [a:filename]
  endif

  let lines = readfile(a:filename, '', 1)
  if empty(lines) || lines[0] !~ '^#!.\+'
    " Shebang not found.
    return [a:filename]
  endif

  " Get shebang line.
  let shebang = split(matchstr(lines[0], '^#!\zs.\+'))

  " Convert command name.
  if vimproc#util#is_windows()
        \ && shebang[0] =~ '^/'
    let shebang[0] = vimproc#get_command_name(
          \ fnamemodify(shebang[0], ':t'))
  endif

  return shebang + [a:filename]
endfunction"}}}

"-----------------------------------------------------------
" LOW LEVEL API

augroup vimproc
  autocmd VimLeave * call s:finalize()
  autocmd CursorHold,BufWritePost * call s:garbage_collect(0)
augroup END

" Initialize.
let s:lasterr = []
let s:read_timeout = 100
let s:write_timeout = 100
let s:bg_processes = {}

if vimproc#util#has_lua()
  function! s:split(str, sep) abort
    let result = []
    lua << EOF
    do
      local result = vim.eval('result')
      local str = vim.eval('a:str')
      local sep = vim.eval('a:sep')
      local last

      if string.find(str, sep, 1, true) == nil then
        result:add(str)
      else
        for part, pos in string.gmatch(str,
            '(.-)' .. sep .. '()') do
          result:add(part)
          last = pos
        end

        result:add(string.sub(str, last))
      end
    end
EOF

    return result
  endfunction
else
  function! s:split(str, sep) abort
    let [result, pos] = [[], 0]
    while 1
      let tmp = stridx(a:str, a:sep, pos)
      if tmp == -1
        call add(result, strpart(a:str, pos))
        break
      endif
      call add(result, strpart(a:str, pos, tmp - pos))
      let pos = tmp + 1
    endwhile

    return result
  endfunction
endif

" Encode a 32-bit integer into a 5-byte string.
function! s:encode_size(n) abort
  " Set each bit7 to 1 in order to avoid NUL byte.
  return printf("%c%c%c%c%c",
    \ ((a:n / 0x10000000) % 0x80) + 0x80,
    \ ((a:n / 0x200000) % 0x80) + 0x80,
    \ ((a:n / 0x4000) % 0x80) + 0x80,
    \ ((a:n / 0x80) % 0x80) + 0x80,
    \ ( a:n % 0x80) + 0x80)
endfunction

" Decode a 32-bit integer from a 5-byte string.
function! s:decode_size(str, off) abort
  return
    \ (char2nr(a:str[a:off + 0]) - 0x80) * 0x10000000 +
    \ (char2nr(a:str[a:off + 1]) - 0x80) * 0x200000 +
    \ (char2nr(a:str[a:off + 2]) - 0x80) * 0x4000 +
    \ (char2nr(a:str[a:off + 3]) - 0x80) * 0x80 +
    \ (char2nr(a:str[a:off + 4]) - 0x80)
endfunction

" Encode a list into a string.
function! s:encode_list(arr) abort
  " End Of Value
  let EOV = "\xFF"
  " EOV, encoded size0, data0, EOV, encoded size1, data1, EOV, ...
  return empty(a:arr) ? '' :
    \ (EOV . join(map(copy(a:arr), 's:encode_size(strlen(v:val)) . v:val'), EOV) . EOV)
endfunction

" Decode a list from a string.
function! s:decode_list(str) abort
  let err = 0
  " End Of Value
  let EOV = "\xFF"
  if a:str[0] != EOV
    let err = 1
    return [[a:str], err]
  endif
  let arr = []
  let slen = strlen(a:str)
  let off = 1
  while slen - off >= 5
    let size = s:decode_size(a:str, off)
    let arr += [a:str[off + 5 : off + 5 + size - 1]]
    let off += 5 + size + 1
  endwhile
  return [arr, err]
endfunction

function! s:libcall(func, args) abort "{{{
  let stack_buf = libcall(g:vimproc#dll_path, a:func, s:encode_list(a:args))
  if empty(stack_buf)
    return []
  endif
  let [result, err] = s:decode_list(stack_buf)
  if err
    let s:lasterr = result
    let msg = vimproc#util#iconv(string(result),
          \ vimproc#util#systemencoding(), &encoding)

    throw printf('vimproc: %s: %s', a:func, msg)
  endif
  return result
endfunction"}}}

" args[0]: fd, args[1]: count, args[2]: timeout
function! s:libcall_raw_read(func, args) abort "{{{
  let [err, hd] = s:libcall(a:func, a:args)
  return [hd, err]
endfunction "}}}

" args[0]: fd, args[1]: data, args[2]: timeout
function! s:libcall_raw_write(func, args) abort "{{{
  return s:libcall(a:func, [a:args[0], a:args[2], a:args[1]])
endfunction "}}}

function! s:SID_PREFIX() abort
  if !exists('s:sid_prefix')
    let s:sid_prefix = matchstr(expand('<sfile>'),
          \ '<SNR>\d\+_\zeSID_PREFIX$')
  endif
  return s:sid_prefix
endfunction

" Get funcref.
function! s:funcref(funcname) abort
  return function(s:SID_PREFIX().a:funcname)
endfunction

function! s:finalize() abort
  call s:garbage_collect(1)

  if exists('s:dll_handle')
    call s:vp_dlclose(s:dll_handle)
  endif
endfunction

function! s:vp_dlopen(path) abort
  let [handle] = s:libcall('vp_dlopen', [a:path])
  return handle
endfunction

function! s:vp_dlclose(handle) abort
  call s:libcall('vp_dlclose', [a:handle])
endfunction

function! s:vp_file_open(path, flags, mode) abort
  let [fd] = s:libcall('vp_file_open', [a:path, a:flags, a:mode])
  return fd
endfunction

function! s:vp_file_close() dict
  if self.fd != 0
    call s:libcall('vp_file_close', [self.fd])
    let self.fd = 0
  endif
endfunction

function! s:vp_file_read(number, timeout) dict
  let [hd, eof] = s:libcall_raw_read('vp_file_read', [self.fd, a:number, a:timeout])
  return [hd, eof]
endfunction

function! s:vp_file_write(hd, timeout) dict
  let [nleft] = s:libcall_raw_write('vp_file_write', [self.fd, a:hd, a:timeout])
  return nleft
endfunction

function! s:quote_arg(arg) abort
  return (a:arg == '' || a:arg =~ '[ "]') ?
        \ '"' . substitute(a:arg, '"', '\\"', 'g') . '"' : a:arg
endfunction

function! s:vp_pipe_open(npipe, hstdin, hstdout, hstderr, argv) abort "{{{
  try
    if vimproc#util#is_windows()
      let cmdline = s:quote_arg(substitute(a:argv[0], '/', '\', 'g'))
      for arg in a:argv[1:]
        let cmdline .= ' ' . s:quote_arg(arg)
      endfor
      let [pid; fdlist] = s:libcall('vp_pipe_open',
            \ [a:npipe, a:hstdin, a:hstdout, a:hstderr, cmdline])
    else
      let [pid; fdlist] = s:libcall('vp_pipe_open',
            \ [a:npipe, a:hstdin, a:hstdout, a:hstderr, len(a:argv)] + a:argv)
    endif
  catch
    call s:print_error(v:throwpoint)
    call s:print_error(v:exception)
    call s:print_error(
          \ 'Error occurred in calling s:vp_pipe_open()')
    call s:print_error(printf(
          \ 'a:argv = %s', string(a:argv)))
    call s:print_error(printf(
          \ 'original a:argv = %s', vimproc#util#iconv(
          \   string(a:argv), vimproc#util#systemencoding(), &encoding)))
  endtry

  if a:npipe != len(fdlist)
    call s:print_error(printf(
          \ 'a:npipe = %d, a:argv = %s', a:npipe, string(a:argv)))
    call s:print_error(printf(
          \ 'pid = %d, fdlist = %s', pid, string(fdlist)))
    echoerr 'Bug behavior is detected!: ' . pid
  endif

  return [pid] + fdlist
endfunction"}}}

function! s:vp_pipe_close() dict
  " echomsg 'close:'.self.fd
  if self.fd != 0
    call s:libcall('vp_pipe_close', [self.fd])
    let self.fd = 0
  endif
endfunction

function! s:vp_pipes_close() dict
  for fd in self.fd
    try
      call fd.close()
    catch /vimproc: vp_pipe_close: /
      " Ignore error.
    endtry
  endfor
endfunction

function! s:vp_pgroup_close() dict
  call self.fd.close()
endfunction

function! s:vp_pipe_read(number, timeout) dict
  if self.fd == 0
    return ['', 1]
  endif

  let [hd, eof] = s:libcall_raw_read('vp_pipe_read', [self.fd, a:number, a:timeout])
  return [hd, eof]
endfunction

function! s:vp_pipe_write(hd, timeout) dict
  if self.fd == 0
    return 0
  endif

  let [nleft] = s:libcall_raw_write('vp_pipe_write', [self.fd, a:hd, a:timeout])
  return nleft
endfunction

function! s:read_pipes(...) dict "{{{
  if type(self.fd[-1]) != type({})
    let self.eof = 1
    return ''
  endif

  let number = get(a:000, 0, -1)
  let timeout = get(a:000, 1, s:read_timeout)

  let output = self.fd[-1].read(number, timeout)
  let self.eof = self.fd[-1].eof

  return output
endfunction"}}}

function! s:write_pipes(str, ...) dict "{{{
  let timeout = get(a:000, 0, s:write_timeout)

  if self.fd[0].eof
    return 0
  endif

  " Write data.
  let nleft = self.fd[0].write(a:str, timeout)
  let self.eof = self.fd[0].eof

  return nleft
endfunction"}}}

function! s:read_pgroup(...) dict "{{{
  let number = get(a:000, 0, -1)
  let timeout = get(a:000, 1, s:read_timeout)

  let output = ''

  if !self.fd.eof
    let output = self.fd.read(number, timeout)
  endif

  if self.proc.current_proc.stdout.eof
        \ && self.proc.current_proc.stderr.eof
    " Get status.
    let [cond, status] = self.proc.current_proc.waitpid()

    if empty(self.proc.statements)
          \ || (self.proc.condition ==# 'true' && status)
          \ || (self.proc.condition ==# 'false' && !status)
      let self.proc.statements = []

      " Caching status.
      let self.proc.cond = cond
      let self.proc.status = status
      if has_key(self.proc.current_proc, 'pipe_status')
        let self.proc.pipe_status = self.proc.current_proc.pipe_status
      endif
    else
      " Initialize next statement.

      let cwd = getcwd()
      try
        call vimproc#util#cd(self.proc.statements[0].cwd)

        let proc = vimproc#plineopen3(
              \ self.proc.statements[0].statement)
      finally
        call vimproc#util#cd(cwd)
      endtry
      let self.proc.current_proc = proc

      let self.pid = proc.pid
      let self.pid_list = proc.pid_list
      let self.proc.pid = proc.pid
      let self.proc.pid_list = proc.pid_list
      let self.proc.condition = self.proc.statements[0].condition
      let self.proc.statements = self.proc.statements[1:]

      let self.proc.stdin = s:fdopen_pgroup(
            \ self.proc, proc.stdin,
            \ 'vp_pgroup_close', 'read_pgroup', 'write_pgroup')
      let self.proc.stdout = s:fdopen_pgroup(
            \ self.proc, proc.stdout,
            \ 'vp_pgroup_close', 'read_pgroup', 'write_pgroup')
      let self.proc.stderr = s:fdopen_pgroup(
            \ self.proc, proc.stderr,
            \ 'vp_pgroup_close', 'read_pgroup', 'write_pgroup')
    endif
  endif

  if self.proc.current_proc.stdout.eof
    let self.proc.stdout.eof = 1
    let self.proc.stdout.__eof = 1
  endif

  if self.proc.current_proc.stderr.eof
    let self.proc.stderr.eof = 1
    let self.proc.stderr.__eof = 1
  endif

  return output
endfunction"}}}

function! s:write_pgroup(str, ...) dict "{{{
  let timeout = get(a:000, 0, s:write_timeout)

  let nleft = 0
  if !self.fd.eof
    " Write data.
    let nleft = self.fd.write(a:str, timeout)
  endif

  return nleft
endfunction"}}}

function! s:vp_pty_open(npipe, width, height, hstdin, hstdout, hstderr, argv) abort
  let [pid; fdlist] = s:libcall('vp_pty_open',
        \ [a:npipe, a:width, a:height,
        \  a:hstdin, a:hstdout, a:hstderr, len(a:argv)] + a:argv)
  return [pid] + fdlist
endfunction

function! s:vp_pty_close() dict
  call s:libcall('vp_pty_close', [self.fd])
endfunction

function! s:vp_pty_read(number, timeout) dict
  let [hd, eof] = s:libcall_raw_read('vp_pty_read', [self.fd, a:number, a:timeout])
  return [hd, eof]
endfunction

function! s:vp_pty_write(hd, timeout) dict
  let [nleft] = s:libcall_raw_write('vp_pty_write', [self.fd, a:hd, a:timeout])
  return nleft
endfunction

function! s:vp_get_winsize() dict
  let [width, height] = [s:get_winwidth(), winheight(0)]

  if !vimproc#util#is_windows()
    for pid in self.pid_list
      let [width, height] = s:libcall('vp_pty_get_winsize', [pid])
    endfor
  endif

  return [width, height]
endfunction

function! s:vp_set_winsize(width, height) dict
  if vimproc#util#is_windows() || !self.is_valid
        \ || (abs(a:width - self.width) < 3 && abs(a:height - self.height) < 3)
        \ || !self.is_pty
    return
  endif

  let self.width = a:width
  let self.height = a:height

  try
    if self.stdin.eof == 0 && self.stdin.fd[-1].is_pty
      call s:libcall('vp_pty_set_winsize',
            \ [self.stdin.fd[-1].fd, a:width-5, a:height])
    endif
    if self.stdout.eof == 0 && self.stdout.fd[0].is_pty
      call s:libcall('vp_pty_set_winsize',
            \ [self.stdout.fd[0].fd, a:width-5, a:height])
    endif
    if self.stderr.eof == 0 && self.stderr.fd[0].is_pty
      call s:libcall('vp_pty_set_winsize',
            \ [self.stderr.fd[0].fd, a:width-5, a:height])
    endif
  catch
    return
  endtry

  " Send SIGWINCH = 28 signal.
  for pid in self.pid_list
    call vimproc#kill(pid, g:vimproc#SIGWINCH)
  endfor
endfunction

function! s:vp_kill(...) dict
  let sig = get(a:000, 0, g:vimproc#SIGTERM)
  if sig != 0
    call s:close_all(self)
    let self.is_valid = 0
  endif

  let ret = 0
  for pid in get(self, 'pid_list', [self.pid])
    call s:waitpid(pid, 1)
    let ret = vimproc#kill(pid, sig)
  endfor

  return ret
endfunction

function! s:vp_pgroup_kill(...) dict
  let sig = get(a:000, 0, g:vimproc#SIGTERM)
  if sig != 0
    call s:close_all(self)
    let self.is_valid = 0
  endif

  if self.pid == 0
    " Ignore.
    return
  endif

  return self.current_proc.kill(sig)
endfunction

function! s:waitpid(pid, ...) abort
  let nohang = a:0 ? a:1 : 0
  try
    while 1
      let [cond, status] = s:libcall('vp_waitpid', [a:pid])
      " echomsg string([a:pid, cond, status])
      if cond !=# 'run' || nohang
        break
      endif
    endwhile

    if cond ==# 'run'
      " Add process list.
      let s:bg_processes[a:pid] = a:pid
      let [cond, status] = ['exit', '0']
    elseif vimproc#util#is_windows()
      call s:libcall('vp_close_handle', [a:pid])
    endif

    let s:last_status = str2nr(status)
  catch /No child processes/
    let [cond, status] = ['exit', '0']
    let s:last_status = 0
  catch
    let [cond, status] = ['error', '0']
    let s:last_status = -1
  endtry

  return [cond, str2nr(status)]
endfunction

function! s:vp_checkpid() dict
  try
    let [cond, status] = s:libcall('vp_waitpid', [self.pid])
    if cond !=# 'run'
      let [self.cond, self.status] = [cond, status]
    endif
  catch /waitpid() error:\|vp_waitpid:/
    let [cond, status] = ['error', '0']
  endtry

  return [cond, str2nr(status)]
endfunction

function! s:vp_waitpid(...) dict
  let nohang = a:0 ? a:1 : 0
  call s:close_all(self)

  let self.is_valid = 0

  if has_key(self, 'cond') && has_key(self, 'status')
    " Use cache.
    let [cond, status] = [self.cond, self.status]
  else
    let [cond, status] = s:waitpid(self.pid, nohang)
  endif

  if cond ==# 'exit'
    let self.pid = 0
  endif

  if has_key(self, 'pid_list')
    if !has_key(self, 'pipe_status')
      let self.pipe_status = repeat([['run', 0]], len(self.pid_list))
    endif
    let self.pipe_status[:] = map(self.pipe_status[:-2],
          \ 'v:val[0] !=# "run" ? v:val : s:waitpid(self.pid_list[v:key], nohang)')
          \ + [[cond, status]]
  endif

  return [cond, status]
endfunction

function! s:vp_pgroup_waitpid() dict
  call s:close_all(self)

  let self.is_valid = 0

  if !has_key(self, 'cond') ||
        \ !has_key(self, 'status')
    return s:waitpid(self.pid)
  endif

  return [self.cond, self.status]
endfunction

function! s:vp_socket_open(host, port) abort
  let [socket] = s:libcall('vp_socket_open', [a:host, a:port])
  return socket
endfunction

function! s:vp_socket_close() dict
  call s:libcall('vp_socket_close', [self.fd])
  let self.is_valid = 0
endfunction

function! s:vp_socket_read(number, timeout) dict
  let [hd, eof] = s:libcall_raw_read('vp_socket_read',
        \ [self.fd, a:number, a:timeout])
  return [hd, eof]
endfunction

function! s:vp_socket_write(hd, timeout) dict
  let [nleft] = s:libcall_raw_write('vp_socket_write',
        \ [self.fd, a:hd, a:timeout])
  return nleft
endfunction

function! s:vp_host_exists(host) abort
  let [rval] = s:libcall('vp_host_exists', [a:host])
  return rval
endfunction

function! s:get_winwidth() abort
  return winwidth(0) - &l:numberwidth - &l:foldcolumn
endfunction

" Initialize.
if !exists('s:dll_handle')
  let s:dll_handle = s:vp_dlopen(g:vimproc#dll_path)
  let s:last_status = 0
  let s:last_errmsg = ''
  call s:define_signals()
endif

" vimproc dll version check. "{{{
try
  if vimproc#dll_version() != vimproc#version()
    call s:print_error(printf('Your vimproc binary version is "%d",'.
          \ ' but vimproc version is "%d".',
          \ vimproc#dll_version(), vimproc#version()))
    if g:vimproc#download_windows_dll && vimproc#util#is_windows()
      if vimproc#util#try_update_windows_dll(s:VERSION_STRING)
        call s:print_error('DLL automatically update succeeded.')
        call s:print_error('Please restart Vim.')
      endif
    endif
  endif
catch
  call s:print_error(v:throwpoint)
  call s:print_error(v:exception)
  call s:print_error('Your vimproc binary is not compatible with this vimproc!')
  call s:print_error('Please re-compile it.')
endtry
"}}}

" Restore 'cpoptions' {{{
let &cpo = s:save_cpo
unlet s:save_cpo
" }}}

" __END__
" vim:foldmethod=marker:fen:sw=2:sts=2
