1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-24 06:20:05 +08:00
SpaceVim/bundle/vim-dispatch/autoload/dispatch.vim

1400 lines
42 KiB
VimL
Vendored

" Location: autoload/dispatch.vim
if exists('g:autoloaded_dispatch')
finish
endif
let g:autoloaded_dispatch = 1
" Section: Utility
function! dispatch#tempname() abort
let temp = tempname()
if has('win32')
return fnamemodify(fnamemodify(temp, ':h'), ':p').fnamemodify(temp, ':t')
endif
return temp
endfunction
function! dispatch#uniq(list) abort
let i = 0
let seen = {}
while i < len(a:list)
if (a:list[i] ==# '' && exists('empty')) || has_key(seen,a:list[i])
call remove(a:list,i)
elseif a:list[i] ==# ''
let i += 1
let empty = 1
else
let seen[a:list[i]] = 1
let i += 1
endif
endwhile
return a:list
endfunction
function! dispatch#fnameescape(file) abort
if exists('*fnameescape')
return fnameescape(a:file)
elseif a:file ==# '-'
return '\-'
else
return substitute(escape(a:file, " \t\n*?[{`$\\%#'\"|!<"), '^[+>]', '\\&', '')
endif
endfunction
function! dispatch#shellescape(...) abort
let args = []
for arg in a:000
if arg =~# '^[A-Za-z0-9_/.-]\+$'
let args += [arg]
elseif &shell =~# 'c\@<!sh'
let args += [substitute(shellescape(arg), '\\\n', '\n', 'g')]
else
let args += [shellescape(arg)]
endif
endfor
return join(args, ' ')
endfunction
let s:var = '\%(<\%(cword\|cWORD\|cexpr\|cfile\|sfile\|slnum\|afile\|abuf\|amatch' . (has('clientserver') ? '\|client' : '') . '\)>\|%\|#<\=\d\+\|##\=\)'
function! dispatch#escape(string) abort
return substitute(a:string, s:var, '\\&', 'g')
endfunction
function! dispatch#bang(string) abort
return '!' . substitute(a:string, '!\|' . s:var, '\\&', 'g')
endfunction
function! s:expand(expr, dispatch_opts) abort
if a:expr =~# '^\\\+`[-+]\=='
return a:expr[1:-1]
elseif a:expr =~# '^`='
sandbox let v = eval(a:expr[2:-2])
return v
elseif a:expr =~# '^`[-+]='
return ''
endif
call extend(l:, a:dispatch_opts)
sandbox let v = expand(substitute(a:expr, ':S$', '', ''))
if has('win32') && a:expr =~# '^\\[%#]' && v =~# '^\\[%#]'
let v = v[1:-1]
endif
if a:expr =~# ':S$'
let v = shellescape(v)
endif
if len(v) && len(expand(matchstr(a:expr, '^[%#][^:]*\%(:p:h\)\=\|^[^:]\+')))
return v
else
return a:expr
endif
endfunction
let s:flags = '<\=\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)*\%(:S\)\='
let s:expandable = '\C\\*\%(`[+-]\==[^`]*`\|' . s:var . s:flags . '\)'
function! dispatch#expand(string, ...) abort
let opts = {}
if a:0 && a:1 > 0
let opts['l#'] = a:1
let opts._l = a:1
endif
let lnum = v:lnum
try
let v:lnum = get(opts, 'l#', 0)
let string = substitute(a:string, s:expandable, '\=s:expand(submatch(0), opts)', 'g')
finally
let v:lnum = lnum
endtry
return string
endfunction
function! s:command_lnum(string, lnum) abort
return a:lnum > 0 ? substitute(a:string, '^:[%0]\=\ze\a', ':' . a:lnum, '') : a:string
endfunction
function! s:build_make(program, args) abort
if a:program =~# '\$\*'
return substitute(a:program, '\$\*', a:args, 'g')
elseif empty(a:args)
return a:program
else
return a:program . ' ' . a:args
endif
endfunction
function! s:efm_query(key, format) abort
let matches = []
let efm = ',' . a:format
let pattern = '\c,%\\&\(' . substitute(
\ type(a:key) == type('') ? a:key : join(a:key, '\|'), '_', '_\=', 'g') .
\ '\)=\(\%(\\.\|[^\,]\)*\)'
let pos = 0
while 1
let match = matchlist(efm, pattern, pos)
let pos = matchend(efm, pattern, pos)
if pos < 0
return matches
endif
call add(matches, [match[1], substitute(match[2], '\\\ze[\,]', '', 'g')])
endwhile
endfunction
function! s:efm_literal(key, format, ...) abort
let subs = {'%': '%', 'f': '%:S'}
for [key, raw] in s:efm_query(a:key, a:format)
let value = substitute(raw, '%\(.\)', '\=get(subs,submatch(1),"\030")', 'g')
if len(value) && value !~# "\030"
return value
endif
endfor
return ''
endfunction
function! s:efm_to_regexp(pattern) abort
return '\c^\%(' . substitute(a:pattern,
\ '\(%\=\)\([%*\\.#^$[~]\)',
\ '\=empty(submatch(1)) ? "\\".submatch(2) : submatch(2)==#"#"?"*":submatch(2)',
\ 'g') . '\)$'
endfunction
function! s:efm_regexps(key, ...) abort
let matches = s:efm_query(a:key, a:0 ? a:1 : &errorformat)
call map(matches, '[v:val[0], s:efm_to_regexp(v:val[1])]')
return matches
endfunction
function! s:escape_path(path) abort
return substitute(dispatch#fnameescape(a:path), '^\\\~', '\~', '')
endfunction
function! dispatch#dir_opt(...) abort
let dir = fnamemodify(a:0 ? a:1 : getcwd(), ':p:~:s?[^:]\zs[\\/]$??')
return '-dir=' . s:escape_path(dir) . ' '
endfunction
function! s:cd_command() abort
return exists('*haslocaldir') && haslocaldir() ? 'lcd' : exists(':tcd') && haslocaldir(-1) ? 'tcd' : 'cd'
endfunction
function! dispatch#cd_helper(dir) abort
let back = s:cd_command() . ' ' . dispatch#fnameescape(getcwd())
return 'let g:dispatch_back = '.string(back).'|lcd '.dispatch#fnameescape(a:dir)
endfunction
function! s:wrapcd(dir, cmd) abort
if a:dir ==# getcwd()
return a:cmd
endif
return 'try|execute dispatch#cd_helper('.string(a:dir).')|execute '.string(a:cmd).'|finally|execute remove(g:, "dispatch_back")|endtry'
endfunction
function! dispatch#slash() abort
return !exists("+shellslash") || &shellslash ? '/' : '\'
endfunction
function! dispatch#shellpipe(file) abort
if &shellpipe =~# '%s'
return ' ' . printf(&shellpipe, dispatch#shellescape(a:file))
else
return ' ' . &shellpipe . ' ' . dispatch#shellescape(a:file)
endif
endfunction
function! dispatch#vim_executable() abort
if !exists('s:vim')
if has('win32')
let roots = [fnamemodify($VIMRUNTIME, ':8') . dispatch#slash(),
\ fnamemodify($VIM, ':8') . dispatch#slash()]
elseif has('gui_macvim')
let roots = [fnamemodify($VIM, ':h:h') . '/MacOS/']
else
let roots = [fnamemodify($VIM, ':h:h') . '/bin/']
endif
for root in roots
if executable(root . v:progname)
let s:vim = root . v:progname
break
endif
endfor
if !exists('s:vim')
if executable(v:progname)
let s:vim = v:progname
else
let s:vim = 'vim'
endif
endif
endif
return s:vim
endfunction
function! dispatch#has_callback() abort
if has('clientserver') && !empty(v:servername)
return 1
elseif !exists('*job_start') && !exists('*jobstart') || !get(g:, 'dispatch_fifo_callback', 1)
return 0
endif
if !exists('s:has_temp_fifo')
let fifo = tempname()
call system('mkfifo ' . dispatch#shellescape(fifo))
let s:has_temp_fifo = (getftype(fifo) ==# 'fifo')
call delete(fifo)
endif
return s:has_temp_fifo
endfunction
function! dispatch#callback(request) abort
let request = s:request(a:request)
if !has_key(request, 'id') || !dispatch#has_callback()
return ''
endif
if has('clientserver') && !empty(v:servername)
return dispatch#shellescape(dispatch#vim_executable()) .
\ ' --servername ' . dispatch#shellescape(v:servername) .
\ ' --remote-expr "' . 'DispatchComplete(' . request.id . ')' . '"'
endif
call system('mkfifo ' . dispatch#shellescape(request.file . '.callback'))
let cmd = ['head', '-1', request.file . '.callback']
let Cb = { ... -> dispatch#complete(request.id) }
if exists('*job_start')
call job_start(cmd, {'exit_cb': Cb})
elseif exists('*jobstart')
call jobstart(cmd, {'on_exit': Cb})
else
return ''
endif
return 'echo > ' . request.file . '.callback'
endfunction
function! dispatch#autowrite() abort
if &autowrite || &autowriteall
try
if &confirm
let reconfirm = 1
setglobal noconfirm
endif
silent! wall
finally
if exists('reconfirm')
setglobal confirm
endif
endtry
endif
return ''
endfunction
function! dispatch#status_var() abort
if &shellxquote ==# '"'
return '%ERRORLEVEL%'
elseif &shell =~# 'csh\|fish'
return '$status'
else
return '$?'
endif
endfunction
function! s:subshell(cmds) abort
if &shell =~# 'fish'
return 'begin; ' . a:cmds . '; end'
else
return '(' . a:cmds . ')'
endif
endfunction
function! dispatch#prepare_start(request, ...) abort
let status = dispatch#status_var()
let exec = 'echo ' . (&shell =~# 'fish' ? '%self' : '$$') . ' > ' . a:request.file . '.pid; '
if executable('perl')
let exec .= 'sync; perl -e "select(undef,undef,undef,0.1)" 2>/dev/null; '
else
let exec .= 'sleep 1; '
endif
let exec .= a:0 ? a:1 : a:request.expanded
let wait = a:0 > 1 ? a:2 : get(a:request, 'wait', 'error')
let pause = s:subshell("printf '\e[1m--- Press ENTER to continue ---\e[0m\\n'; exec head -1")
if wait ==# 'always'
let exec .= '; ' . pause
elseif wait !=# 'never' && wait !=# 'make'
let exec .= "; test ".status." = 0 -o ".status." = 130" .
\ (&shell =~# 'fish' ? '; or ' : ' || ') . pause
endif
if wait !=# 'make'
let exec .= '; touch ' .a:request.file . '.complete'
endif
let callback = dispatch#callback(a:request)
return exec . (empty(callback) ? '' : '; ' . callback)
endfunction
function! dispatch#prepare_make(request, ...) abort
let exec = a:0 ? a:1 : s:subshell(a:request.expanded . '; echo ' .
\ dispatch#status_var() . ' > ' . a:request.file . '.complete') .
\ dispatch#shellpipe(a:request.file)
return dispatch#prepare_start(a:request, exec, 'make')
endfunction
function! dispatch#set_title(request) abort
return dispatch#shellescape('printf',
\ '\033]1;%s\007\033]2;%s\007',
\ a:request.title,
\ a:request.expanded)
endfunction
function! dispatch#isolate(request, keep, ...) abort
let keep = ['SHELL', 'HOME'] + a:keep
let command = ['cd ' . shellescape(getcwd())]
for line in split(system('env'), "\n")
let var = matchstr(line, '^\w\+\ze=')
if !empty(var) && var !~# '^\%(_\|SHLVL\|PWD\|VIM\|VIMRUNTIME\|MYG\=VIMRC\)$' && index(keep, var) < 0
if &shell =~# 'csh'
let command += split('setenv '.var.' '.shellescape(eval('$'.var)), "\n")
else
let command += split('export '.var.'='.dispatch#shellescape(eval('$'.var)), "\n")
endif
endif
endfor
let command += a:000
let temp = type(a:request) == type({}) ? a:request.file . '.dispatch' : dispatch#tempname()
call writefile(command, temp)
return 'env -i ' . join(map(copy(keep), 'v:val."=". dispatch#shellescape(eval("$".v:val))." "'), '') . &shell . ' ' . temp
endfunction
function! s:current_compiler(...) abort
return get((empty(&l:makeprg) ? g: : b:), 'current_compiler', a:0 ? a:1 : '')
endfunction
function! s:set_current_compiler(name) abort
if empty(a:name)
unlet! b:current_compiler
else
let b:current_compiler = a:name
endif
endfunction
function! s:postfix(request) abort
let pid = dispatch#pid(a:request)
return '(' . a:request.handler.'/'.(!empty(pid) ? pid : '?') . ')'
endfunction
function! s:echo_truncated(left, right) abort
if exists('v:echospace')
let max_len = (&cmdheight - 1) * &columns + v:echospace
else
let max_len = &cmdheight * &columns - 1
let last_has_status = (&laststatus == 2 || (&laststatus == 1 && winnr('$') != 1))
if &ruler && !last_has_status
if empty(&rulerformat)
" Default ruler is 17 chars wide.
let max_len -= 17
elseif exists('g:rulerwidth')
" User specified width of custom ruler.
let max_len -= g:rulerwidth
else
" Don't know width of custom ruler, make a conservative guess.
let max_len -= &columns / 2
endif
let max_len -= 1
endif
if &showcmd
let max_len -= 10
if !&ruler || last_has_status
let max_len -= 1
endif
endif
endif
let msg = a:left . a:right
if len(substitute(msg, '.', '.', 'g')) > max_len
let msg = a:left . '<' . matchstr(a:right, '\v.{'.(max_len - len(substitute(a:left, '.', '.', 'g')) - 1).'}$')
endif
echo msg
endfunction
function! s:dispatch(request) abort
for handler in g:dispatch_handlers
if get(g:, 'dispatch_no_' . handler . '_' . get(a:request, 'action')) ||
\ get(g:, 'dispatch_no_' . handler . '_' . (get(a:request, 'action') ==# 'start' ? 'spawn' : 'dispatch'))
continue
endif
let response = call('dispatch#'.handler.'#handle', [a:request])
if !empty(response)
let a:request.handler = handler
" Display command, avoiding hit-enter prompt.
redraw
let msg = ':!'
let suffix = s:postfix(a:request)
let cmd = a:request.expanded . ' ' . suffix
call s:echo_truncated(':!', a:request.expanded . ' ' . s:postfix(a:request))
return response
endif
endfor
return 0
endfunction
function! s:extract_opts(command, ...) abort
let command = a:command
let opts = {}
while command =~# '^\%(-\|++\)\%(\w\+\)\%([= ]\|$\)'
let opt = matchstr(command, '\zs\w\+')
if command =~# '^\%(-\|++\)\w\+='
let val = matchstr(command, '\w\+=\zs\%(\\.\|\S\)*')
else
let val = 1
endif
if opt ==# 'dir' || opt ==# 'directory'
let opts.directory = fnamemodify(expand(val), ':p:s?[^:]\zs[\\/]$??')
elseif index(['compiler', 'title', 'wait'], opt) >= 0
let opts[opt] = substitute(val, '\\\(\s\)', '\1', 'g')
endif
let command = substitute(command, '^\%(-\|++\)\w\+\%(=\%(\\.\|\S\)*\)\=\s*', '', '')
endwhile
return [command, extend(opts, a:0 ? a:1 : {})]
endfunction
function! s:default_args(args, count, ...) abort
let args = matchstr(a:args, '\s\+\zs.*')
let format = a:0 ? a:1 : &errorformat
if a:count >= 0
let prefix = s:efm_literal('buffer', format, a:count)
if len(prefix)
let args = prefix . substitute(args, '^\ze.', ' ', '')
endif
endif
if empty(args)
let args = s:efm_literal('default', format, a:count)
endif
return args
endfunction
function! s:make_focus(count) abort
return s:build_make(&makeprg, s:default_args('', a:count))
endfunction
" Section: :Start, :Spawn
function! s:focus(count) abort
if type(get(b:, 'dispatch')) == type('')
return b:dispatch
else
return s:make_focus(a:count)
endif
endfunction
function! dispatch#spawn_command(bang, command, count, mods, ...) abort
let [command, opts] = s:extract_opts(a:command)
if empty(command) && a:count >= 0
let command = s:focus(a:count)
call extend(opts, {'wait': 'always'}, 'keep')
let [command, opts] = s:extract_opts(command, opts)
endif
let opts.background = a:bang
let opts.mods = a:mods ==# '<mods>' ? '' : a:mods
call dispatch#spawn(command, opts, a:count)
return ''
endfunction
function! s:doautocmd(event) abort
if v:version >= 704 || (v:version == 703 && has('patch442'))
return 'doautocmd <nomodeline> ' . a:event
elseif &modelines == 0 || !&modeline
return 'doautocmd ' . a:event
else
return 'try|set modelines=0|doautocmd ' . a:event . '|finally|set modelines=' . &modelines . '|endtry'
endif
endfunction
function! s:compiler_getcwd() abort
try
exe s:doautocmd('QuickFixCmdPre dispatch-make-complete')
return getcwd()
finally
exe s:doautocmd('QuickFixCmdPost dispatch-make-complete')
endtry
endfunction
function! s:parse_start(command, count) abort
let [command, opts] = s:extract_opts(a:command)
if empty(command) && a:count >= 0
let command = s:focus(a:count)
call extend(opts, {'wait': 'always'}, 'keep')
let [command, opts] = s:extract_opts(command, opts)
endif
if empty(command) && type(get(b:, 'start')) == type('')
let command = b:start
let [command, opts] = s:extract_opts(command, opts)
elseif empty(command) && len(s:efm_literal(['start', 'default_start'], &errorformat))
let task = s:efm_literal(['start', 'default_start'], &errorformat)
let command = &makeprg . ' ' . task
if !has_key(opts, 'title') && task =~# '^\w\S\{,19\}$'
let opts.title = s:current_compiler('make') . ' ' . task
endif
if !has_key(opts, 'directory')
let opts.directory = s:compiler_getcwd()
endif
endif
return [command, opts]
endfunction
function! dispatch#start_command(bang, command, count, mods, ...) abort
let [command, opts] = s:parse_start(a:command, a:count)
let opts.background = get(opts, 'background') || a:bang
let opts.mods = a:mods ==# '<mods>' ? '' : a:mods
if command =~# '^:\S'
unlet! g:dispatch_last_start
return s:wrapcd(get(opts, 'directory', getcwd()),
\ substitute(command, '\>', get(opts, 'background', 0) ? '!' : '', ''))
endif
call dispatch#start(command, opts, a:count)
return ''
endfunction
if type(get(g:, 'DISPATCH_STARTS')) != type({})
unlet! g:DISPATCH_STARTS
let g:DISPATCH_STARTS = {}
endif
function! dispatch#start(command, ...) abort
return dispatch#spawn(a:command, extend({'manage': 1}, a:0 ? a:1 : {}), a:0 > 1 ? a:2 : -1)
endfunction
function! dispatch#spawn(command, ...) abort
let command = empty(a:command) ? &shell : a:command
let request = extend({
\ 'action': 'start',
\ 'background': 0,
\ 'command': command,
\ 'directory': getcwd(),
\ 'title': '',
\ 'mods': '',
\ }, a:0 ? a:1 : {})
if empty(a:command)
call extend(request, {'wait': 'never'}, 'keep')
endif
let g:dispatch_last_start = request
if empty(request.title)
let request.title = substitute(fnamemodify(matchstr(request.command, '\%(\\.\|\S\)\+'), ':t:r'), '\\\(\s\)', '\1', 'g')
endif
let cd = s:cd_command()
try
if request.directory !=# getcwd()
let cwd = getcwd()
execute cd dispatch#fnameescape(request.directory)
endif
let request.expanded = dispatch#expand(request.command, a:0 > 1 ? a:2 : -1)
if get(request, 'manage')
let key = request.directory."\t".substitute(request.expanded, '\s*$', '', '')
let i = 0
while i < len(get(g:DISPATCH_STARTS, key, []))
let [handler, pid] = split(g:DISPATCH_STARTS[key][i], '[@/]')
if !s:running(pid, handler)
call remove(g:DISPATCH_STARTS[key], i)
continue
endif
try
if request.background || dispatch#{handler}#activate(pid)
let request.handler = handler
let request.pid = pid
return request
endif
catch
endtry
let i += 1
endwhile
endif
call dispatch#autowrite()
let request.file = dispatch#tempname()
let s:files[request.file] = request
if exists('cwd')
execute cd dispatch#fnameescape(request.directory)
endif
if s:dispatch(request)
if get(request, 'manage')
if !has_key(g:DISPATCH_STARTS, key)
let g:DISPATCH_STARTS[key] = []
endif
call add(g:DISPATCH_STARTS[key], request.handler.'/'.dispatch#pid(request))
endif
else
let request.handler = 'sync'
execute dispatch#bang(request.expanded)
endif
finally
if exists('cwd')
execute cd dispatch#fnameescape(cwd)
endif
endtry
return request
endfunction
" Section: :Dispatch, :Make
let g:dispatch_compilers = get(g:, 'dispatch_compilers', {})
function! s:compiler_split(args) abort
let remove = keys(filter(copy(g:dispatch_compilers), 'empty(v:val)'))
let pattern = '\%('.join(map(remove, 'substitute(escape(v:val, ".*^$~[]\\"), "\\w\\zs$", " ", "")'), '\s*\|').'\)'
let args = substitute(a:args, '\s\+', ' ', 'g')
let prefix = matchstr(args, '^\s*'.pattern.'*')
let args = substitute(args, '^\s*'.pattern.'*', '', '')
let rtp = escape(&runtimepath, ' ')
for [command, plugin] in items(g:dispatch_compilers)
if strpart(args.' ', 0, len(command)+1) ==# command.' ' && !empty(plugin)
\ && !empty(findfile('compiler/'.plugin.'.vim', rtp))
return [plugin, prefix, command, args[len(command) : -1]]
endif
endfor
let program = matchstr(args, '\S\+')
let rest = matchstr(args, '\s.*')
if fnamemodify(program, ':t') ==# 'make'
return ['make', prefix, program, rest]
endif
let plugins = map(reverse(split(globpath(rtp, 'compiler/*.vim'), "\n")), '[fnamemodify(v:val, ":t:r"), readfile(v:val)]')
for [plugin, lines] in plugins
for line in lines
let full = substitute(substitute(
\ matchstr(line, '\<CompilerSet\s\+makeprg=\zs\a\%(\\.\|[^[:space:]"]\)*'),
\ '\\\(.\)', '\1', 'g'),
\ ' \=["'']\=\%(%\|\$\*\|--\w\@!\).*', '', '')
if !empty(full) && strpart(args.' ', 0, len(full)+1) ==# full.' '
return [plugin, prefix, full, args[len(full) : -1]]
endif
endfor
endfor
for [plugin, lines] in plugins
for line in lines
if matchstr(line, '\<CompilerSet\s\+makeprg=\zs[[:alnum:]_.-]\+') ==# fnamemodify(program, ':t')
return [plugin, prefix, program, rest]
endif
endfor
endfor
return ['', prefix, program, rest]
endfunction
function! dispatch#compiler_for_program(args) abort
return get(s:compiler_split(a:args), 0, '')
endfunction
function! dispatch#compiler_options(compiler) abort
let current_compiler = get(b:, 'current_compiler', '')
let makeprg = &l:makeprg
let efm = &l:efm
if empty(a:compiler)
return {}
endif
try
if a:compiler ==# 'make'
if &makeprg !=# 'make'
setlocal errorformat<
endif
return {'program': 'make', 'format': &errorformat}
endif
let &l:makeprg = ''
try
execute 'compiler' dispatch#fnameescape(a:compiler)
catch /^Vim(compiler):E666:/
return {}
endtry
let options = {'format': &errorformat}
if !empty(&l:makeprg)
let options.program = &l:makeprg
endif
return options
finally
let &l:makeprg = makeprg
let &l:efm = efm
call s:set_current_compiler(current_compiler)
endtry
endfunction
function! s:completion_filter(results, query) abort
if type(get(g:, 'completion_filter')) == type({})
return g:completion_filter.Apply(a:results, a:query)
else
return filter(a:results, 'strpart(v:val, 0, len(a:query)) ==# a:query')
endif
endfunction
function! s:file_complete(A) abort
return map(split(glob(substitute(a:A, '.\@<=\ze[\\/]\|$', '*', 'g')), "\n"),
\ 'dispatch#fnameescape(isdirectory(v:val) ? v:val . dispatch#slash() : v:val)')
endfunction
function! s:compiler_complete(format, compiler, A, L, P) abort
let compiler = empty(a:compiler) ? 'make' : a:compiler
let fn = s:efm_literal('completion', a:format)
if empty(fn)
let fn = s:efm_literal('complete', a:format)
endif
if empty(fn)
for file in findfile('compiler/'.compiler.'.vim', escape(&runtimepath, ' '), -1)
for line in readfile(file)
let fn = matchstr(line, '\C-complete=\zscustom\%(list\)\=,\%(s:\)\@!\S\+')
if !empty(fn)
break
endif
endfor
endfor
endif
let fn = substitute(fn, '\C^custom\%(list\)\=,', '', '')
if fn =~# '[#A-Z]' && exists('*' . fn)
let results = call(fn, [a:A, a:L, a:P])
elseif exists('*CompilerComplete_' . compiler)
let results = call('CompilerComplete_' . compiler, [a:A, a:L, a:P])
else
let results = -1
endif
if type(results) == type([])
return results
elseif type(results) != type('')
unlet! results
let results = join(s:file_complete(a:A), "\n")
endif
return s:completion_filter(split(results, "\n"), a:A)
endfunction
function! dispatch#command_complete(A, L, P) abort
let L = strpart(a:L, 0, a:P)
let args = matchstr(L, '\s\zs.*')
let [cmd, opts] = s:extract_opts(args)
let P = a:P + len(cmd) - len(L)
let len = matchend(cmd, '\S\+\s')
if len >= 0 && P >= 0
let cd = s:cd_command()
try
if get(opts, 'directory', getcwd()) !=# getcwd()
let cwd = getcwd()
execute cd dispatch#fnameescape(opts.directory)
endif
if has_key(opts, 'compiler')
let compiler = opts.compiler
let efm = get(dispatch#compiler_options(compiler), 'format', '')
elseif cmd !~# '^--\S\@!'
let compiler = dispatch#compiler_for_program(cmd)
let efm = get(dispatch#compiler_options(compiler), 'format', '')
else
let compiler = s:current_compiler()
let efm = &errorformat
endif
return s:compiler_complete(efm, compiler, a:A, 'Make '.strpart(cmd, len), P+5)
finally
if exists('cwd')
execute cd dispatch#fnameescape(cwd)
endif
endtry
elseif a:A =~# '^-dir='
let results = map(filter(s:file_complete(a:A[5:-1]), 'isdirectory(v:val)'), '"-dir=".v:val')
elseif a:A =~# '^-compiler='
let results = map(reverse(split(globpath(escape(&runtimepath, ' '), 'compiler/*.vim'), "\n")), '"-compiler=".fnamemodify(v:val, ":t:r")')
elseif a:A =~# '^-'
let as = {'dir': 'directory'}
let results = filter(['-compiler=', '-dir='],
\ '!has_key(opts, get(as, v:val[1:-2], v:val[1:-2]))')
elseif a:A =~# '^:' && exists('*getcompletion')
let matches = matchlist(a:A, '^:\([.$]\|\d\+\)\=\(\a.*\)')
if len(matches)
let results = map(getcompletion(matches[2], 'command'), '":".matches[1].v:val')
else
let results = []
endif
elseif a:A =~# '[\/]'
let results = s:file_complete(a:A)
else
let results = []
for dir in split($PATH, has('win32') ? ';' : ':')
let results += map(split(glob(dir.'/'.substitute(a:A, '.', '*&', 'g').'*'), "\n"), 'v:val[strlen(dir)+1 : -1]')
endfor
endif
return s:completion_filter(sort(dispatch#uniq(results)), a:A)
endfunction
function! dispatch#make_complete(A, L, P) abort
try
exe s:doautocmd('QuickFixCmdPre dispatch-make-complete')
return s:compiler_complete(&errorformat, s:current_compiler(), a:A, a:L, a:P)
finally
exe s:doautocmd('QuickFixCmdPost dispatch-make-complete')
endtry
endfunction
if !exists('s:makes')
let s:makes = []
let s:files = {}
endif
function! dispatch#compile_command(bang, args, count, mods, ...) abort
let [args, request] = s:extract_opts(a:args, {'mods': a:mods ==# '<mods>' ? '' : a:mods})
if empty(args)
let default_dispatch = 1
if type(get(b:, 'dispatch')) == type('')
unlet! default_dispatch
let args = b:dispatch
endif
for vars in a:count < 0 ? [g:, t:, w:, b:] : []
if type(get(vars, 'Dispatch')) == type('')
unlet! default_dispatch
let args = vars.Dispatch
endif
endfor
let [args, request] = s:extract_opts(args, request)
endif
if empty(args)
let args = '--'
endif
if args =~# '^!'
return 'Start' . (a:bang ? '!' : '') . ' ' . args[1:-1]
endif
if args =~# '^:\S'
call dispatch#autowrite()
let args = s:command_lnum(args, a:count)
return s:wrapcd(get(request, 'directory', getcwd()),
\ substitute(args[1:-1], '\>', (a:bang ? '!' : ''), ''))
endif
let executable = matchstr(args, '\S\+')
call extend(request, {
\ 'action': 'make',
\ 'background': a:bang,
\ 'format': '%+I%.%#'
\ }, 'keep')
if executable ==# '_' || executable ==# '--'
if !empty(get(request, 'compiler', ''))
let compiler_options = dispatch#compiler_options(request.compiler)
if !has_key(compiler_options, 'format')
return 'compiler ' . dispatch#fnameescape(request.compiler)
endif
call extend(request, compiler_options)
else
let request.compiler = s:current_compiler()
let request.program = &makeprg
let request.format = &errorformat
endif
let request.args = s:default_args(args, exists('default_dispatch') && a:count < 0 ? 0 : a:count, request.format)
let request.command = s:build_make(get(request, 'program', get(request, 'compiler', '--')), request.args)
else
let [compiler, prefix, program, rest] = s:compiler_split(args)
let request.compiler = get(request, 'compiler', compiler)
if !empty(request.compiler)
let compiler_options = dispatch#compiler_options(request.compiler)
if !has_key(compiler_options, 'format')
return 'compiler ' . dispatch#fnameescape(request.compiler)
endif
call extend(request, compiler_options)
if request.compiler ==# compiler
let request.program = prefix . program
let request.args = rest[1:-1]
endif
endif
let request.command = args
endif
let request.format = substitute(request.format, ',%-G%\.%#\%($\|,\@=\)', '', '')
for [key, regexp] in s:efm_regexps(['terminal', 'force_start', 'force_spawn'], request.format)
if has_key(request, 'args') && request.args =~# regexp
let title = request.compiler
if regexp =~# '\\\@<!\\ze'
let title .= ' ' . matchstr(request.args, regexp)
endif
let title = get(request, 'title', title)
return (key =~? 'spawn' ? 'Spawn' : 'Start') . (a:bang ? '!' : '') .
\ ' -title=' . escape(title, '\ ') .
\ ' ' . request.command
endif
endfor
if empty(request.compiler)
unlet request.compiler
endif
let request.title = get(request, 'title', get(request, 'compiler', 'make'))
call dispatch#autowrite()
if winnr('$') > 1
cclose
endif
let request.file = dispatch#tempname()
let &errorfile = request.file
let lnum = v:lnum
let efm = &l:efm
let makeprg = &l:makeprg
let compiler = get(b:, 'current_compiler', '')
let after = ''
let cd = s:cd_command()
try
call s:set_current_compiler(get(request, 'compiler', ''))
let v:lnum = a:count > 0 ? a:count : 0
let &l:efm = request.format
let &l:makeprg = request.command
exe s:doautocmd('QuickFixCmdPre dispatch-make')
let request.directory = get(request, 'directory', getcwd())
if request.directory !=# getcwd()
let cwd = getcwd()
execute cd dispatch#fnameescape(request.directory)
endif
let request.expanded = dispatch#expand(request.command, a:count)
call extend(s:makes, [request])
let request.id = len(s:makes)
let s:files[request.file] = request
call writefile([], request.file)
if exists(':chistory')
let result = s:dispatch(request)
else
let result = 0
endif
if result
if !get(request, 'background')
call s:cgetfile(request, '')
if result is 2
exe 'botright copen' get(g:, 'dispatch_quickfix_height', '')
wincmd p
endif
endif
else
let request.handler = 'sync'
let after = 'call dispatch#complete('.request.id.',0)'
redraw!
let sp = dispatch#shellpipe(request.file)
let dest = request.file . '.complete'
if !exists(':chistory') && request.background
echohl WarningMsg
echo "Asynchronous dispatch requires Vim 8 or higher\n"
echohl NONE
endif
if &shellxquote ==# '"'
silent execute dispatch#bang(request.expanded . ' ' . sp . ' & echo %ERRORLEVEL% > ' . dest)
else
silent execute dispatch#bang('(' . request.expanded . '; echo ' .
\ dispatch#status_var() . ' > ' . dest . ')' . ' ' . sp)
endif
redraw!
endif
finally
exe s:doautocmd('QuickFixCmdPost dispatch-make')
let v:lnum = lnum
let &l:efm = efm
let &l:makeprg = makeprg
call s:set_current_compiler(compiler)
if exists('cwd')
execute cd dispatch#fnameescape(cwd)
endif
endtry
execute after
return ''
endfunction
" Section: :FocusDispatch
function! dispatch#focus(...) abort
let haslnum = a:0 && a:1 >= 0
if exists('b:Dispatch') && !haslnum
let [what, why] = [b:Dispatch, 'Buffer local focus']
elseif exists('w:Dispatch') && !haslnum
let [what, why] = [w:Dispatch, 'Window local focus']
elseif exists('t:Dispatch') && !haslnum
let [what, why] = [t:Dispatch, 'Tab local focus']
elseif exists('g:Dispatch') && !haslnum
let [what, why] = [g:Dispatch, 'Global focus']
elseif exists('b:dispatch')
let [what, why] = [b:dispatch, 'Buffer default']
else
let [what, why] = ['--', (len(&l:makeprg) ? 'Buffer' : 'Global') . ' default']
let default = 1
endif
if what =~# '^--\S\@!'
let args = s:default_args(what[2:-1], haslnum ? a:1 : exists('default') ? 0 : -1)
let what = len(args) ? '-- ' . args : args
endif
if haslnum
let [what, opts] = s:extract_opts(what)
if what =~# '^:'
let what = s:command_lnum(what, a:1)
else
let what = dispatch#expand(what, a:1)
endif
if a:0 > 1
return [what, extend(opts, a:2)]
endif
if has_key(opts, 'compiler') && opts.compiler !=# dispatch#compiler_for_program(what)
let what = '-compiler=' . opts.compiler . ' ' . what
endif
if has_key(opts, 'directory') && opts.directory !=# getcwd()
let what = '-dir=' .
\ s:escape_path(fnamemodify(opts.directory, ':~:.')) .
\ ' ' . what
endif
endif
if what =~# '^--\S\@!'
return [':Make' . what[2:-1], why]
elseif what =~# '^!'
return [':Start ' . what[1:-1], why]
elseif what =~# '^:\S'
return [what, why]
else
return [':Dispatch ' . what, why]
endif
endfunction
function! dispatch#focus_command(bang, args, count, ...) abort
let [args, opts] = s:extract_opts(a:args)
if args =~# '^:[.$%]Dispatch$'
let [args, opts] = dispatch#focus(line(a:args[1]), opts)
elseif args =~# '^:\d*Dispatch$'
let [args, opts] = dispatch#focus(+matchstr(a:args, '\d\+'), opts)
elseif args =~# '^--\S\@!' && !has_key(opts, 'compiler')
let args = s:default_args(args, -1)
let args = s:build_make(&makeprg, args)
let args = dispatch#expand(args, 0)
else
let args = args =~# '^:' ? args : dispatch#expand(args, -1)
endif
let args = dispatch#escape(args)
if has_key(opts, 'compiler')
let args = '-compiler=' . opts.compiler . ' ' . args
endif
if has_key(opts, 'directory')
let args = dispatch#dir_opt(opts.directory) . args
endif
if empty(a:args) && a:bang
unlet! b:Dispatch w:Dispatch t:Dispatch g:Dispatch
let [what, why] = dispatch#focus(a:count)
echo 'Reverted default to ' . what
elseif empty(a:args)
let [what, why] = dispatch#focus(a:count)
echo a:count < 0 ? printf('%s is %s', why, what) : what
elseif a:count >= 0
let b:Dispatch = args
let [what, why] = dispatch#focus()
echo 'Set buffer local focus to ' . what
elseif a:0 && a:1 =~# '\<tab\>'
let t:Dispatch = args
unlet! b:Dispatch w:Dispatch
let [what, why] = dispatch#focus(a:count)
echo 'Set tab local focus to ' . what
elseif a:bang || a:0 && a:1 =~# '\<vert\>'
let w:Dispatch = args
unlet! b:Dispatch
let [what, why] = dispatch#focus(a:count)
echo 'Set window local focus to ' . what
else
let g:Dispatch = args
unlet! b:Dispatch w:Dispatch t:Dispatch
let [what, why] = dispatch#focus(a:count)
echo 'Set global focus to ' . what
endif
return ''
endfunction
function! dispatch#make_focus(count) abort
let cmd = s:make_focus(a:count)
if a:count >= 0
return dispatch#expand(cmd, a:count)
else
return cmd
endif
endfunction
function! dispatch#spawn_focus(count) abort
if a:count < 0
return &shell
else
return dispatch#expand(s:focus(a:count), a:count)
endif
endfunction
function! dispatch#start_focus(count) abort
let [command, opts] = s:parse_start('', a:count)
if a:count >= 0
let command = dispatch#expand(command, a:count)
endif
if empty(command)
let command = &shell
endif
if get(opts, 'wait', 'error') !=# 'error'
let command = '-wait=' . escape(opts.wait, '\ ') . ' ' . command
endif
if has_key(opts, 'title')
let command = '-title=' . escape(opts.title, '\ ') . ' ' . command
endif
if has_key(opts, 'directory') && opts.directory !=# getcwd()
let command = '-dir=' .
\ s:escape_path(fnamemodify(opts.directory, ':~:.')) . ' ' .
\ command
endif
return command
endfunction
" Section: Requests
function! s:file(request) abort
if type(a:request) == type('')
return a:request
elseif type(a:request) == type({})
return get(a:request, 'file', '')
else
return get(get(s:makes, a:request-1, {}), 'file', '')
endif
endfunction
function! s:request(request) abort
if type(a:request) == type({})
return a:request
elseif type(a:request) == type(0) && a:request >= 0
return get(s:makes, a:request-1, {})
elseif type(a:request) == type('') && a:request =~# '^\w\+/\d\+$'
let i = len(s:makes)
while i
let i -= 1
if get(s:makes[i], 'handler') . '/' . dispatch#pid(s:makes[i]) ==# a:request
return s:makes[i]
endif
endwhile
return {}
elseif type(a:request) == type('') && !empty(a:request)
let id = matchstr(a:request, '^:noautocmd cgetfile \zs.*\|^:Dispatch.*(\zs\w\+/\d\+\ze)$')
if empty(id)
return get(s:files, a:request, {})
else
return s:request(id)
endif
else
return {}
endif
endfunction
function! dispatch#request(...) abort
return s:request(a:0 ? a:1 : 0)
endfunction
function! s:running(pid, ...) abort
if empty(a:pid)
return 0
elseif a:0 && exists('*dispatch#'.a:1.'#running')
return dispatch#{a:1}#running(a:pid)
elseif has('win32')
let tasklist_cmd = 'tasklist /fi "pid eq '.a:pid.'"'
if &shellxquote ==# '"'
let tasklist_cmd = substitute(tasklist_cmd, '"', "'", "g")
endif
return system(tasklist_cmd) =~# '==='
else
call system('kill -0 '.a:pid)
return !v:shell_error
endif
endfunction
function! dispatch#pid(request) abort
let request = s:request(a:request)
if !has_key(request, 'pid')
if has('win32') && !executable('wmic')
let request.pid = 0
return 0
endif
let file = request.file
for i in range(50)
if getfsize(file.'.pid') > 0 || filereadable(file.'.complete')
break
endif
sleep 10m
endfor
try
let request.pid = +readfile(file.'.pid')[0]
catch
let request.pid = 0
endtry
endif
return request.pid
endfunction
function! dispatch#completed(request) abort
return get(s:request(a:request), 'completed', 0)
endfunction
function! dispatch#complete(file, ...) abort
if !dispatch#completed(a:file)
let request = s:request(a:file)
let request.completed = 1
try
let status = readfile(request.file . '.complete', 1)[0]
catch
let status = -1
call writefile([-1], request.file . '.complete')
endtry
if !a:0
silent doautocmd ShellCmdPost
endif
if !request.background && !get(request, 'aborted')
call s:cwindow(request, 0, status, '', 'make')
redraw!
endif
if has_key(request, 'aborted')
echohl DispatchAbortedMsg
let label = 'Aborted:'
elseif status > 0
echohl DispatchFailureMsg
let label = 'Failure:'
elseif status == 0
echohl DispatchSuccessMsg
let label = 'Success:'
else
echohl DispatchCompleteMsg
let label = 'Complete:'
endif
call s:echo_truncated(label . ' !', request.expanded . ' ' . s:postfix(request))
echohl NONE
if !a:0
checktime
endif
endif
return ''
endfunction
" Section: :AbortDispatch
function! dispatch#abort_command(bang, query, ...) abort
if !exists(':chistory')
return 'echoerr ' .string('Asynchronous dispatch requires Vim 8 or higher')
endif
let i = len(s:makes) - 1
while i >= 0
let request = s:makes[i]
if strpart(request.command, 0, len(a:query)) ==# a:query
break
endif
let i -= 1
endwhile
if i < 0
return 'echomsg '.string('No running dispatch found')
endif
let request.aborted = 1
let pid = dispatch#pid(request)
if !pid
return 'echoerr '.string('No pid file')
endif
if exists('*dispatch#'.get(request, 'handler').'#kill')
call dispatch#{request.handler}#kill(pid, a:bang)
elseif has('win32')
call system('taskkill /PID ' . (a:bang ? '/F ' : '') . pid)
else
call system('kill -' . (a:bang ? 'KILL' : 'HUP') . ' ' . pid)
endif
return 'call dispatch#complete('.request.id.')'
endfunction
" Section: Quickfix window
function! dispatch#copen(bang, mods, ...) abort
if empty(s:makes)
return 'echoerr ' . string('No dispatches yet')
endif
let request = dispatch#request()
if !dispatch#completed(request) && filereadable(request.file . '.complete')
let request.completed = 1
endif
call s:cwindow(request, a:bang, -2, a:mods ==# '<mods>' ? '' : a:mods, 'cgetfile')
endfunction
function! s:is_quickfix(...) abort
let nr = a:0 ? a:1 : winnr()
return getwinvar(nr, '&buftype') ==# 'quickfix' && empty(getloclist(nr))
endfunction
function! s:cgetfile(request, event, ...) abort
let request = s:request(a:request)
if !has_key(request, 'handler')
throw 'Bad request ' . string(request)
endif
let efm = &l:efm
let makeprg = &l:makeprg
let compiler = get(b:, 'current_compiler', '')
let cd = s:cd_command()
let dir = getcwd()
try
call s:set_current_compiler(get(request, 'compiler', ''))
exe cd dispatch#fnameescape(request.directory)
if a:0 && a:1
let &l:efm = '%+G%.%#'
else
let &l:efm = request.format
endif
let &l:makeprg = dispatch#escape(request.expanded)
let title = ':Dispatch '.dispatch#escape(request.expanded) . ' ' . s:postfix(request)
if len(a:event)
exe s:doautocmd('QuickFixCmdPre ' . a:event)
endif
if exists(':chistory') && get(getqflist({'title': 1}), 'title', '') ==# title
call setqflist([], 'r')
execute 'noautocmd caddfile' dispatch#fnameescape(request.file)
else
execute 'noautocmd cgetfile' dispatch#fnameescape(request.file)
endif
if exists(':chistory')
call setqflist([], 'r', {'title': title})
endif
if len(a:event)
exe s:doautocmd('QuickFixCmdPost ' . a:event)
endif
finally
exe cd dispatch#fnameescape(dir)
let &l:efm = efm
let &l:makeprg = makeprg
call s:set_current_compiler(compiler)
endtry
endfunction
function! s:cwindow(request, all, copen, mods, event) abort
call s:cgetfile(a:request, a:event, a:all)
let height = get(g:, 'dispatch_quickfix_height', 10)
if height <= 0
return
endif
let was_qf = s:is_quickfix()
let mods = a:mods
if mods !~# 'aboveleft\|belowright\|leftabove\|rightbelow\|topleft\|botright'
let mods = 'botright ' . mods
endif
if a:copen
execute (mods) 'copen' height
elseif !was_qf || winnr('$') > 1
execute (mods) 'cwindow' height
endif
if !was_qf && s:is_quickfix() && a:copen !=# -2
wincmd p
endif
endfunction
function! dispatch#quickfix_init() abort
let request = s:request(w:quickfix_title)
if empty(request)
return
endif
let w:quickfix_title = ':Dispatch ' . dispatch#escape(request.expanded) .
\ ' ' . s:postfix(request)
let b:dispatch = dispatch#dir_opt(request.directory) .
\ dispatch#escape(request.expanded)
if has_key(request, 'compiler')
let b:dispatch = '-compiler=' . request.compiler . ' ' . b:dispatch
endif
if has_key(request, 'program')
let &l:efm = request.format
let &l:makeprg = request.program
if has_key(request, 'compiler')
let b:current_compiler = request.compiler
else
unlet! b:current_compiler
endif
endif
exe 'lcd' dispatch#fnameescape(request.directory)
endfunction
" Section: End