256 lines
8.4 KiB
VimL
256 lines
8.4 KiB
VimL
" ==============================================================================
|
|
" Location: autoload/cmake/system.vim
|
|
" Description: System abstraction layer
|
|
" ==============================================================================
|
|
|
|
let s:system = {}
|
|
|
|
let s:stdout_partial_line = {}
|
|
|
|
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
|
" Private functions
|
|
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
|
|
|
function! s:ManipulateCommand(command) abort
|
|
let l:ret_command = []
|
|
for l:arg in a:command
|
|
" Remove double quotes around argument that are quoted. For instance,
|
|
" '-G "Unix Makefiles"' results in '-G Unix Makefiles'.
|
|
let l:quotes_regex = '\m\C\(^\|[^"\\]\)"\([^"]\|$\)'
|
|
let l:arg = substitute(l:arg, l:quotes_regex, '\1\2', 'g')
|
|
" Split arguments that are composed of an option (short '-O' or long
|
|
" '--option') and a follow-up string, where the option and the string
|
|
" are separated by a space.
|
|
let l:split_regex = '\m\C^\(-\w\|--\w\+\)\s\(.\+\)'
|
|
let l:match_list = matchlist(l:arg, l:split_regex)
|
|
if len(l:match_list) > 0
|
|
call add(l:ret_command, l:match_list[1])
|
|
call add(l:ret_command, l:match_list[2])
|
|
else
|
|
call add(l:ret_command, l:arg)
|
|
endif
|
|
endfor
|
|
return l:ret_command
|
|
endfunction
|
|
|
|
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
|
" Public functions
|
|
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
|
|
|
" Generate escaped path string from list of components.
|
|
"
|
|
" Params:
|
|
" components : List
|
|
" list of path components (strings)
|
|
" relative : Boolean
|
|
" whether to have the path relative to the current directory or absolute
|
|
"
|
|
" Returns:
|
|
" String
|
|
" escaped path string with appropriate path separators
|
|
"
|
|
function! s:system.Path(components, relative) abort
|
|
let l:components = a:components
|
|
let l:separator = has('win32') ? '\' : '/'
|
|
" Join path components and get absolute path.
|
|
let l:path = join(l:components, l:separator)
|
|
let l:path = simplify(l:path)
|
|
let l:path = fnamemodify(l:path, ':p')
|
|
" If path ends with separator, remove separator from path.
|
|
if match(l:path, '\m\C\' . l:separator . '$') != -1
|
|
let l:path = fnamemodify(l:path, ':h')
|
|
endif
|
|
" Reduce to relative path if requested.
|
|
if a:relative
|
|
" For some reason, reducing the path to relative returns an empty string
|
|
" if the path happens to be the same as CWD. Thus, only reduce the path
|
|
" to relative when it is not CWD, otherwise just return '.'.
|
|
if l:path ==# getcwd()
|
|
let l:path = '.'
|
|
else
|
|
let l:path = fnamemodify(l:path, ':.')
|
|
endif
|
|
endif
|
|
" Simplify and escape path.
|
|
let l:path = simplify(l:path)
|
|
let l:path = fnameescape(l:path)
|
|
return l:path
|
|
endfunction
|
|
|
|
" Run arbitrary job in the background.
|
|
"
|
|
" Params:
|
|
" command : List
|
|
" the command to be run, as a list of command and arguments
|
|
" wait : Boolean
|
|
" whether to wait for completion
|
|
" stdout_cb : Funcref
|
|
" stdout callback (can be v:null), which should take a variable number
|
|
" of arguments, and from which s:system.ExtractStdoutCallbackData(a:000)
|
|
" can be called to retrieve the stdout string
|
|
" exit_cb : Funcref
|
|
" exit callback (can be v:null), which should take a variable number of
|
|
" arguments, and from which s:system.ExtractExitCallbackData(a:000) can
|
|
" be called to retrieve the exit code
|
|
" pty : Boolean
|
|
" whether to allocate a pseudo terminal for the job
|
|
"
|
|
" Return:
|
|
" Number
|
|
" job id
|
|
"
|
|
function! s:system.JobRun(command, wait, stdout_cb, exit_cb, pty) abort
|
|
let l:options = {}
|
|
let l:options['pty'] = a:pty
|
|
let l:command = s:ManipulateCommand(a:command)
|
|
if has('nvim')
|
|
if a:stdout_cb isnot# v:null
|
|
let l:options['on_stdout'] = a:stdout_cb
|
|
endif
|
|
if a:exit_cb isnot# v:null
|
|
let l:options['on_exit'] = a:exit_cb
|
|
endif
|
|
" In some cases, the PTY in MS-Windows (ConPTY) uses ANSI escape
|
|
" sequences to move the cursor position (ESC[<n>;<m>H) rather than
|
|
" inseting newline characters. Setting the width of the PTY to be very
|
|
" large and the height to be as small as possible (but larger than 1)
|
|
" seems to circumvent this problem. Hacky, but it seems to work.
|
|
if has('win32')
|
|
let l:options['width'] = 10000
|
|
let l:options['height'] = 2
|
|
endif
|
|
let l:job_id = jobstart(l:command, l:options)
|
|
else
|
|
if a:stdout_cb isnot# v:null
|
|
let l:options['out_cb'] = a:stdout_cb
|
|
endif
|
|
if a:exit_cb isnot# v:null
|
|
let l:options['exit_cb'] = a:exit_cb
|
|
endif
|
|
let l:job_id = job_start(l:command, l:options)
|
|
endif
|
|
" Wait for job to complete, if requested.
|
|
if a:wait
|
|
call l:self.JobWait(l:job_id)
|
|
endif
|
|
return l:job_id
|
|
endfunction
|
|
|
|
" Wait for job to complete.
|
|
"
|
|
" Params:
|
|
" job_id : Number
|
|
" job id
|
|
"
|
|
function! s:system.JobWait(job_id) abort
|
|
if has('nvim')
|
|
call jobwait([a:job_id])
|
|
else
|
|
while job_status(a:job_id) ==# 'run'
|
|
execute 'sleep 5m'
|
|
endwhile
|
|
endif
|
|
endfunction
|
|
|
|
" Wait for job's channel to be closed.
|
|
"
|
|
" Params:
|
|
" job_id : Number
|
|
" job id
|
|
"
|
|
function! s:system.ChannelWait(job_id) abort
|
|
" Only makes sense in Vim currently.
|
|
if !has('nvim')
|
|
let l:chan_id = job_getchannel(a:job_id)
|
|
while ch_status(l:chan_id, {'part': 'out'}) !=# 'closed'
|
|
execute 'sleep 5m'
|
|
endwhile
|
|
endif
|
|
endfunction
|
|
|
|
" Stop job.
|
|
"
|
|
" Params:
|
|
" job_id : Number
|
|
" job id
|
|
"
|
|
function! s:system.JobStop(job_id) abort
|
|
try
|
|
if has('nvim')
|
|
call jobstop(a:job_id)
|
|
else
|
|
call job_stop(a:job_id)
|
|
endif
|
|
catch /.*/
|
|
endtry
|
|
endfunction
|
|
|
|
" Extract data from a job's stdout callback.
|
|
"
|
|
" Params:
|
|
" cb_arglist : List
|
|
" variable-size list of arguments as passed to the callback, which will
|
|
" differ between Neovim and Vim
|
|
"
|
|
" Returns:
|
|
" List
|
|
" stdout data, as a list of strings
|
|
"
|
|
function! s:system.ExtractStdoutCallbackData(cb_arglist) abort
|
|
let l:channel = a:cb_arglist[0]
|
|
let l:data = a:cb_arglist[1]
|
|
if has('nvim')
|
|
let l:eof = (l:data == [''])
|
|
" In Neovim, remove all the CR characters, which are returned when a
|
|
" pseudo terminal is allocated for the job.
|
|
call map(l:data, {_, val -> substitute(val, '\m\C\r', '', 'g')})
|
|
" The first and the last lines may be partial lines, thus they need to
|
|
" be joined on consecutive iterations. See :help channel-lines.
|
|
" When this function is called for the first time for a particular
|
|
" channel, allocate an empty partial line for that channel.
|
|
if !has_key(s:stdout_partial_line, l:channel)
|
|
let s:stdout_partial_line[l:channel] = ''
|
|
endif
|
|
" Append first entry of output list to partial line.
|
|
let s:stdout_partial_line[l:channel] .= remove(l:data, 0)
|
|
" If output list contains more entries, they are all complete lines
|
|
" except for the last entry. Return the saved partial line (which is now
|
|
" complete) and all the complete lines from the list, and save a new
|
|
" partial line (the last entry of the list).
|
|
if len(l:data) > 0
|
|
call insert(l:data, s:stdout_partial_line[l:channel])
|
|
let s:stdout_partial_line[l:channel] = remove(l:data, -1)
|
|
endif
|
|
" At the end of the stream of a channel, remove the dictionary entry for
|
|
" that channel.
|
|
if l:eof
|
|
call remove(s:stdout_partial_line, l:channel)
|
|
endif
|
|
else
|
|
" In Vim, l:data is a string, so we transform it to a list (consisting
|
|
" of a single element).
|
|
let l:data = [l:data]
|
|
endif
|
|
return l:data
|
|
endfunction
|
|
|
|
" Extract data from a system's exit callback.
|
|
"
|
|
" Params:
|
|
" cb_arglist : List
|
|
" variable-size list of arguments as passed to the callback, which will
|
|
" differ between Neovim and Vim
|
|
"
|
|
" Returns:
|
|
" Number
|
|
" exit code
|
|
"
|
|
function! s:system.ExtractExitCallbackData(cb_arglist) abort
|
|
return a:cb_arglist[1]
|
|
endfunction
|
|
|
|
" Get system 'object'.
|
|
"
|
|
function! cmake#system#Get() abort
|
|
return s:system
|
|
endfunction
|