1119 lines
29 KiB
VimL
1119 lines
29 KiB
VimL
" asyncrun.vim - Run shell commands in background and output to quickfix
|
|
"
|
|
" Maintainer: skywind3000 (at) gmail.com
|
|
" Homepage: http://www.vim.org/scripts/script.php?script_id=5431
|
|
"
|
|
" Last change: 2016.12.23
|
|
"
|
|
" Run shell command in background and output to quickfix:
|
|
" :AsyncRun[!] [options] {cmd} ...
|
|
"
|
|
" when "!" is included, auto-scroll in quickfix will be disabled
|
|
" parameters are splited by space, if a parameter contains space,
|
|
" it should be quoted or escaped as backslash + space (unix only).
|
|
"
|
|
" Parameters will be expanded if they start with '%', '#' or '<' :
|
|
" %:p - File name of current buffer with full path
|
|
" %:t - File name of current buffer without path
|
|
" %:p:h - File path of current buffer without file name
|
|
" %:e - File extension of current buffer
|
|
" %:t:r - File name of current buffer without path and extension
|
|
" % - File name relativize to current directory
|
|
" %:h:. - File path relativize to current directory
|
|
" <cwd> - Current directory
|
|
" <cword> - Current word under cursor
|
|
" <cfile> - Current file name under cursor
|
|
"
|
|
" Environment variables are set before executing:
|
|
" $VIM_FILEPATH - File name of current buffer with full path
|
|
" $VIM_FILENAME - File name of current buffer without path
|
|
" $VIM_FILEDIR - Full path of current buffer without the file name
|
|
" $VIM_FILEEXT - File extension of current buffer
|
|
" $VIM_FILENOEXT - File name of current buffer without path and extension
|
|
" $VIM_CWD - Current directory
|
|
" $VIM_RELDIR - File path relativize to current directory
|
|
" $VIM_RELNAME - File name relativize to current directory
|
|
" $VIM_CWORD - Current word under cursor
|
|
" $VIM_CFILE - Current filename under cursor
|
|
" $VIM_GUI - Is running under gui ?
|
|
" $VIM_VERSION - Value of v:version
|
|
" $VIM_MODE - Execute via 0:!, 1:makeprg, 2:system(), 3:silent
|
|
" $VIM_COLUMNS - How many columns in vim's screen
|
|
" $VIM_LINES - How many lines in vim's screen
|
|
"
|
|
" parameters also accept these environment variables wrapped by
|
|
" "$(...)", and "$(VIM_FILEDIR)" will be expanded as file directory
|
|
"
|
|
" There can be some options before [cmd]:
|
|
" -mode=0/1/2 - start mode: 0(async, default), 1(makeprg), 2(!)
|
|
" -cwd=? - initial directory, (use current directory if unset)
|
|
" -save=0/1 - non-zero to save unsaved files before executing
|
|
" -program=? - set to 'make' to use '&makeprg'
|
|
"
|
|
" All options must start with a minus and position **before** `[cmd]`.
|
|
" Since no shell command starts with a minus. So they can be
|
|
" distinguished from shell command easily without any ambiguity.
|
|
"
|
|
" Stop the running job by signal TERM:
|
|
" :AsyncStop[!]
|
|
"
|
|
" when "!" is included, job will be stopped by signal KILL
|
|
"
|
|
" Settings:
|
|
" g:asyncrun_exit - script will be executed after finished
|
|
" g:asyncrun_bell - non-zero to ring a bell after finished
|
|
" g:asyncrun_mode - 0:async(require vim 7.4.1829) 1:sync 2:shell
|
|
" g:asyncrun_encs - shell program output encoding
|
|
"
|
|
" Variables:
|
|
" g:asyncrun_code - exit code
|
|
" g:asyncrun_status - 'running', 'success' or 'failure'
|
|
"
|
|
" Requirements:
|
|
" vim 7.4.1829 is minimal version to support async mode
|
|
"
|
|
" Examples:
|
|
" :AsyncRun gcc % -o %<
|
|
" :AsyncRun make -f Mymakefile
|
|
" :AsyncRun! grep -R <cword> .
|
|
" :noremap <F7> :AsyncRun gcc % -o %< <cr>
|
|
"
|
|
" Additional:
|
|
" AsyncRun uses quickfix window to show job outputs, in order to
|
|
" see the outputs in realtime, you need open quickfix window at
|
|
" first by using :copen (see :help copen/cclose). Or use
|
|
" ':call asyncrun#quickfix_toggle(8)' to open/close it rapidly.
|
|
"
|
|
|
|
"----------------------------------------------------------------------
|
|
"- Global Settings & Variables
|
|
"----------------------------------------------------------------------
|
|
if !exists('g:asyncrun_exit')
|
|
let g:asyncrun_exit = ''
|
|
endif
|
|
|
|
if !exists('g:asyncrun_bell')
|
|
let g:asyncrun_bell = 0
|
|
endif
|
|
|
|
if !exists('g:asyncrun_stop')
|
|
let g:asyncrun_stop = ''
|
|
endif
|
|
|
|
if !exists('g:asyncrun_mode')
|
|
let g:asyncrun_mode = 0
|
|
endif
|
|
|
|
if !exists('g:asyncrun_hook')
|
|
let g:asyncrun_hook = ''
|
|
endif
|
|
|
|
if !exists('g:asyncrun_last')
|
|
let g:asyncrun_last = 0
|
|
endif
|
|
|
|
if !exists('g:asyncrun_timer')
|
|
let g:asyncrun_timer = 25
|
|
endif
|
|
|
|
if !exists('g:asyncrun_code')
|
|
let g:asyncrun_code = ''
|
|
endif
|
|
|
|
if !exists('g:asyncrun_status')
|
|
let g:asyncrun_status = ''
|
|
endif
|
|
|
|
if !exists('g:asyncrun_encs')
|
|
let g:asyncrun_encs = ''
|
|
endif
|
|
|
|
if !exists('g:asyncrun_trim')
|
|
let g:asyncrun_trim = 0
|
|
endif
|
|
|
|
if !exists('g:asyncrun_text')
|
|
let g:asyncrun_text = ''
|
|
endif
|
|
|
|
if !exists('g:asyncrun_local')
|
|
let g:asyncrun_local = 1
|
|
endif
|
|
|
|
if !exists('g:asyncrun_auto')
|
|
let g:asyncrun_auto = ''
|
|
endif
|
|
|
|
if !exists('g:asyncrun_shell')
|
|
let g:asyncrun_shell = ''
|
|
endif
|
|
|
|
if !exists('g:asyncrun_shellflag')
|
|
let g:asyncrun_shellflag = ''
|
|
endif
|
|
|
|
|
|
|
|
"----------------------------------------------------------------------
|
|
"- Internal Functions
|
|
"----------------------------------------------------------------------
|
|
|
|
" error message
|
|
function! s:ErrorMsg(msg)
|
|
echohl ErrorMsg
|
|
echom 'ERROR: '. a:msg
|
|
echohl NONE
|
|
endfunc
|
|
|
|
" show not support message
|
|
function! s:NotSupport()
|
|
let msg = "required: +timers +channel +job +reltime and vim >= 7.4.1829"
|
|
call s:ErrorMsg(msg)
|
|
endfunc
|
|
|
|
" run autocmd
|
|
function! s:AutoCmd(name)
|
|
if has('autocmd')
|
|
exec 'silent doautocmd User AsyncRun'.a:name
|
|
endif
|
|
endfunc
|
|
|
|
let s:asyncrun_windows = 0
|
|
let g:asyncrun_windows = 0
|
|
let s:asyncrun_support = 0
|
|
let g:asyncrun_support = 0
|
|
|
|
" check running in windows
|
|
if has('win32') || has('win64') || has('win95') || has('win16')
|
|
let s:asyncrun_windows = 1
|
|
let g:asyncrun_windows = 1
|
|
endif
|
|
|
|
" check has advanced mode
|
|
if (v:version >= 800 || has('patch-7.4.1829')) && (!has('nvim'))
|
|
if has('job') && has('channel') && has('timers') && has('reltime')
|
|
let s:asyncrun_support = 1
|
|
let g:asyncrun_support = 1
|
|
endif
|
|
elseif has('nvim')
|
|
let s:asyncrun_support = 1
|
|
let g:asyncrun_support = 1
|
|
endif
|
|
|
|
|
|
"----------------------------------------------------------------------
|
|
"- build in background
|
|
"----------------------------------------------------------------------
|
|
let s:async_nvim = has('nvim')? 1 : 0
|
|
let s:async_info = { 'text':"", 'post':'', 'postsave':'' }
|
|
let s:async_output = {}
|
|
let s:async_head = 0
|
|
let s:async_tail = 0
|
|
let s:async_code = 0
|
|
let s:async_state = 0
|
|
let s:async_start = 0.0
|
|
let s:async_debug = 0
|
|
let s:async_quick = 0
|
|
let s:async_scroll = 0
|
|
let s:async_hold = 0
|
|
let s:async_congest = 0
|
|
let s:async_efm = &errorformat
|
|
|
|
" check :cbottom available, cursor in quick need to hold ?
|
|
if s:async_nvim == 0
|
|
let s:async_quick = (v:version >= 800 || has('patch-7.4.1997'))? 1 : 0
|
|
let s:async_hold = (v:version >= 800 || has('patch-7.4.2100'))? 0 : 1
|
|
else
|
|
let s:async_quick = 0
|
|
let s:async_hold = 1
|
|
endif
|
|
|
|
" check if we have vim 8.0.100
|
|
if s:async_nvim == 0 && v:version >= 800
|
|
let s:async_congest = has('patch-8.0.100')? 1 : 0
|
|
let s:async_congest = 0
|
|
endif
|
|
|
|
" scroll quickfix down
|
|
function! s:AsyncRun_Job_Scroll()
|
|
if &buftype == 'quickfix'
|
|
silent exec 'normal! G'
|
|
endif
|
|
endfunc
|
|
|
|
" quickfix window cursor check
|
|
function! s:AsyncRun_Job_Cursor()
|
|
if &buftype == 'quickfix'
|
|
if s:async_hold != 0
|
|
let w:asyncrun_qfview = winsaveview()
|
|
endif
|
|
if line('.') != line('$')
|
|
let s:async_check_last = 0
|
|
endif
|
|
endif
|
|
endfunc
|
|
|
|
" find quickfix window and scroll to the bottom then return last window
|
|
function! s:AsyncRun_Job_AutoScroll()
|
|
if s:async_quick == 0
|
|
let l:winnr = winnr()
|
|
noautocmd windo call s:AsyncRun_Job_Scroll()
|
|
noautocmd silent exec ''.l:winnr.'wincmd w'
|
|
else
|
|
cbottom
|
|
endif
|
|
endfunc
|
|
|
|
" restore view in old vim/neovim
|
|
function! s:AsyncRun_Job_ViewReset()
|
|
if &buftype == 'quickfix'
|
|
if exists('w:asyncrun_qfview')
|
|
call winrestview(w:asyncrun_qfview)
|
|
unlet w:asyncrun_qfview
|
|
endif
|
|
endif
|
|
endfunc
|
|
|
|
" it will reset cursor when caddexpr is invoked
|
|
function! s:AsyncRun_Job_QuickReset()
|
|
if &buftype == 'quickfix'
|
|
call s:AsyncRun_Job_ViewReset()
|
|
else
|
|
let l:winnr = winnr()
|
|
noautocmd windo call s:AsyncRun_Job_ViewReset()
|
|
noautocmd silent! exec ''.l:winnr.'wincmd w'
|
|
endif
|
|
endfunc
|
|
|
|
" check if quickfix window can scroll now
|
|
function! s:AsyncRun_Job_CheckScroll()
|
|
if g:asyncrun_last == 0
|
|
if &buftype == 'quickfix'
|
|
if s:async_hold != 0
|
|
let w:asyncrun_qfview = winsaveview()
|
|
endif
|
|
return (line('.') == line('$'))
|
|
else
|
|
return 1
|
|
endif
|
|
elseif g:asyncrun_last == 1
|
|
let s:async_check_last = 1
|
|
let l:winnr = winnr()
|
|
noautocmd windo call s:AsyncRun_Job_Cursor()
|
|
noautocmd silent! exec ''.l:winnr.'wincmd w'
|
|
return s:async_check_last
|
|
elseif g:asyncrun_last == 2
|
|
return 1
|
|
else
|
|
if &buftype == 'quickfix'
|
|
if s:async_hold != 0
|
|
let w:asyncrun_qfview = winsaveview()
|
|
endif
|
|
return (line('.') == line('$'))
|
|
else
|
|
return (!pumvisible())
|
|
endif
|
|
endif
|
|
endfunc
|
|
|
|
" invoked on timer or finished
|
|
function! s:AsyncRun_Job_Update(count)
|
|
let l:iconv = (g:asyncrun_encs != "")? 1 : 0
|
|
let l:count = 0
|
|
let l:total = 0
|
|
let l:empty = [{'text':''}]
|
|
let l:check = s:AsyncRun_Job_CheckScroll()
|
|
let l:efm1 = &g:efm
|
|
let l:efm2 = &l:efm
|
|
if g:asyncrun_encs == &encoding
|
|
let l:iconv = 0
|
|
endif
|
|
if &g:efm != s:async_efm && g:asyncrun_local != 0
|
|
let &l:efm = s:async_efm
|
|
let &g:efm = s:async_efm
|
|
endif
|
|
let l:raw = (s:async_efm == '')? 1 : 0
|
|
while s:async_tail < s:async_head
|
|
let l:text = s:async_output[s:async_tail]
|
|
if l:iconv != 0
|
|
try
|
|
let l:text = iconv(l:text, g:asyncrun_encs, &encoding)
|
|
catch /.*/
|
|
endtry
|
|
endif
|
|
if l:text != ''
|
|
if l:raw == 0
|
|
caddexpr l:text
|
|
else
|
|
call setqflist([{'text':l:text}], 'a')
|
|
endif
|
|
elseif g:asyncrun_trim == 0
|
|
call setqflist(l:empty, 'a')
|
|
endif
|
|
let l:total += 1
|
|
unlet s:async_output[s:async_tail]
|
|
let s:async_tail += 1
|
|
let l:count += 1
|
|
if a:count > 0 && l:count >= a:count
|
|
break
|
|
endif
|
|
endwhile
|
|
if g:asyncrun_local != 0
|
|
if l:efm1 != &g:efm | let &g:efm = l:efm1 | endif
|
|
if l:efm2 != &l:efm | let &l:efm = l:efm2 | endif
|
|
endif
|
|
if s:async_scroll != 0 && l:total > 0 && l:check != 0
|
|
call s:AsyncRun_Job_AutoScroll()
|
|
elseif s:async_hold != 0
|
|
call s:AsyncRun_Job_QuickReset()
|
|
endif
|
|
return l:count
|
|
endfunc
|
|
|
|
" trigger autocmd
|
|
function! s:AsyncRun_Job_AutoCmd(mode, auto)
|
|
if !has('autocmd') | return | endif
|
|
let name = (a:auto == '')? g:asyncrun_auto : a:auto
|
|
if name !~ '^\w\+$' || name == 'NONE' || name == '<NONE>'
|
|
return
|
|
endif
|
|
if a:mode == 0
|
|
silent exec 'doautocmd QuickFixCmdPre '. name
|
|
else
|
|
silent exec 'doautocmd QuickFixCmdPost '. name
|
|
endif
|
|
endfunc
|
|
|
|
" invoked on timer
|
|
function! g:AsyncRun_Job_OnTimer(id)
|
|
let limit = (g:asyncrun_timer < 10)? 10 : g:asyncrun_timer
|
|
" check on command line window
|
|
if &ft == 'vim' && &buftype == 'nofile'
|
|
return
|
|
endif
|
|
if s:async_nvim == 0
|
|
if exists('s:async_job')
|
|
call job_status(s:async_job)
|
|
endif
|
|
endif
|
|
call s:AsyncRun_Job_Update(limit)
|
|
if and(s:async_state, 7) == 7
|
|
if s:async_head == s:async_tail
|
|
call s:AsyncRun_Job_OnFinish()
|
|
endif
|
|
endif
|
|
endfunc
|
|
|
|
" invoked on "callback" when job output
|
|
function! s:AsyncRun_Job_OnCallback(channel, text)
|
|
if !exists("s:async_job")
|
|
return
|
|
endif
|
|
if type(a:text) != 1
|
|
return
|
|
endif
|
|
let s:async_output[s:async_head] = a:text
|
|
let s:async_head += 1
|
|
if s:async_congest != 0
|
|
call s:AsyncRun_Job_Update(-1)
|
|
endif
|
|
endfunc
|
|
|
|
" because exit_cb and close_cb are disorder, we need OnFinish to guarantee
|
|
" both of then have already invoked
|
|
function! s:AsyncRun_Job_OnFinish()
|
|
" caddexpr '(OnFinish): '.a:what.' '.s:async_state
|
|
if s:async_state == 0
|
|
return -1
|
|
endif
|
|
if exists('s:async_job')
|
|
unlet s:async_job
|
|
endif
|
|
if exists('s:async_timer')
|
|
call timer_stop(s:async_timer)
|
|
unlet s:async_timer
|
|
endif
|
|
call s:AsyncRun_Job_Update(-1)
|
|
let l:current = float2nr(reltimefloat(reltime()))
|
|
let l:last = l:current - s:async_start
|
|
let l:check = s:AsyncRun_Job_CheckScroll()
|
|
if s:async_code == 0
|
|
let l:text = "[Finished in ".l:last." seconds]"
|
|
call setqflist([{'text':l:text}], 'a')
|
|
let g:asyncrun_status = "success"
|
|
else
|
|
let l:text = 'with code '.s:async_code
|
|
let l:text = "[Finished in ".l:last." seconds ".l:text."]"
|
|
call setqflist([{'text':l:text}], 'a')
|
|
let g:asyncrun_status = "failure"
|
|
endif
|
|
let s:async_state = 0
|
|
if s:async_scroll != 0 && l:check != 0
|
|
call s:AsyncRun_Job_AutoScroll()
|
|
elseif s:async_hold != 0
|
|
call s:AsyncRun_Job_QuickReset()
|
|
endif
|
|
let g:asyncrun_code = s:async_code
|
|
if g:asyncrun_bell != 0
|
|
exec "norm! \<esc>"
|
|
endif
|
|
if s:async_info.post != ''
|
|
exec s:async_info.post
|
|
let s:async_info.post = ''
|
|
endif
|
|
if g:asyncrun_exit != ""
|
|
exec g:asyncrun_exit
|
|
endif
|
|
call s:AsyncRun_Job_AutoCmd(1, s:async_info.auto)
|
|
call s:AutoCmd('Stop')
|
|
redrawstatus!
|
|
redraw
|
|
endfunc
|
|
|
|
" invoked on "close_cb" when channel closed
|
|
function! s:AsyncRun_Job_OnClose(channel)
|
|
" caddexpr "[close]"
|
|
let s:async_debug = 1
|
|
let l:limit = 128
|
|
let l:options = {'timeout':0}
|
|
while ch_status(a:channel) == 'buffered'
|
|
let l:text = ch_read(a:channel, l:options)
|
|
if l:text == '' " important when child process is killed
|
|
let l:limit -= 1
|
|
if l:limit < 0 | break | endif
|
|
else
|
|
call s:AsyncRun_Job_OnCallback(a:channel, l:text)
|
|
endif
|
|
endwhile
|
|
let s:async_debug = 0
|
|
if exists('s:async_job')
|
|
call job_status(s:async_job)
|
|
endif
|
|
let s:async_state = or(s:async_state, 4)
|
|
endfunc
|
|
|
|
" invoked on "exit_cb" when job exited
|
|
function! s:AsyncRun_Job_OnExit(job, message)
|
|
" caddexpr "[exit]: ".a:message." ".type(a:message)
|
|
let s:async_code = a:message
|
|
let s:async_state = or(s:async_state, 2)
|
|
endfunc
|
|
|
|
" invoked on neovim when stderr/stdout/exit
|
|
function! s:AsyncRun_Job_NeoVim(job_id, data, event)
|
|
if a:event == 'stdout' || a:event == 'stderr'
|
|
let l:index = 0
|
|
let l:size = len(a:data)
|
|
while l:index < l:size
|
|
let s:text = a:data[l:index]
|
|
if s:asyncrun_windows != 0
|
|
let s:text = substitute(s:text, '\r$', '', 'g')
|
|
endif
|
|
let s:async_output[s:async_head] = s:text
|
|
let s:async_head += 1
|
|
let l:index += 1
|
|
endwhile
|
|
elseif a:event == 'exit'
|
|
if type(a:data) == type(1)
|
|
let s:async_code = a:data
|
|
endif
|
|
let s:async_state = or(s:async_state, 6)
|
|
endif
|
|
endfunc
|
|
|
|
|
|
"----------------------------------------------------------------------
|
|
" AsyncRun Interface
|
|
"----------------------------------------------------------------------
|
|
|
|
" start background build
|
|
function! s:AsyncRun_Job_Start(cmd)
|
|
let l:running = 0
|
|
let l:empty = 0
|
|
if s:asyncrun_support == 0
|
|
call s:NotSupport()
|
|
return -1
|
|
endif
|
|
if exists('s:async_job')
|
|
if !has('nvim')
|
|
if job_status(s:async_job) == 'run'
|
|
let l:running = 1
|
|
endif
|
|
else
|
|
if s:async_job > 0
|
|
let l:running = 1
|
|
endif
|
|
endif
|
|
endif
|
|
if type(a:cmd) == 1
|
|
if a:cmd == '' | let l:empty = 1 | endif
|
|
elseif type(a:cmd) == 3
|
|
if a:cmd == [] | let l:empty = 1 | endif
|
|
endif
|
|
if s:async_state != 0 || l:running != 0
|
|
call s:ErrorMsg("background job is still running")
|
|
return -2
|
|
endif
|
|
if l:empty != 0
|
|
call s:ErrorMsg("empty arguments")
|
|
return -3
|
|
endif
|
|
if g:asyncrun_shell == ''
|
|
if !executable(&shell)
|
|
let l:text = "invalid config in &shell and &shellcmdflag"
|
|
call s:ErrorMsg(l:text . ", &shell must be an executable.")
|
|
return -4
|
|
endif
|
|
let l:args = [&shell, &shellcmdflag]
|
|
else
|
|
if !executable(g:asyncrun_shell)
|
|
let l:text = "invalid config in g:asyncrun_shell"
|
|
call s:ErrorMsg(l:text . ", it must be an executable.")
|
|
return -4
|
|
endif
|
|
let l:args = [g:asyncrun_shell, g:asyncrun_shellflag]
|
|
endif
|
|
let l:name = []
|
|
if type(a:cmd) == 1
|
|
let l:name = a:cmd
|
|
if s:asyncrun_windows == 0
|
|
let l:args += [a:cmd]
|
|
else
|
|
let l:tmp = fnamemodify(tempname(), ':h') . '\asyncrun.cmd'
|
|
let l:run = ['@echo off', a:cmd]
|
|
call writefile(l:run, l:tmp)
|
|
let l:args += [l:tmp]
|
|
endif
|
|
elseif type(a:cmd) == 3
|
|
if s:asyncrun_windows == 0
|
|
let l:temp = []
|
|
for l:item in a:cmd
|
|
if index(['|', '`'], l:item) < 0
|
|
let l:temp += [fnameescape(l:item)]
|
|
else
|
|
let l:temp += ['|']
|
|
endif
|
|
endfor
|
|
let l:args += [join(l:temp, ' ')]
|
|
else
|
|
let l:args += a:cmd
|
|
endif
|
|
let l:vector = []
|
|
for l:x in a:cmd
|
|
let l:vector += ['"'.l:x.'"']
|
|
endfor
|
|
let l:name = join(l:vector, ', ')
|
|
endif
|
|
let s:async_efm = &errorformat
|
|
call s:AutoCmd('Pre')
|
|
if s:async_nvim == 0
|
|
let l:options = {}
|
|
let l:options['callback'] = function('s:AsyncRun_Job_OnCallback')
|
|
let l:options['close_cb'] = function('s:AsyncRun_Job_OnClose')
|
|
let l:options['exit_cb'] = function('s:AsyncRun_Job_OnExit')
|
|
let l:options['out_io'] = 'pipe'
|
|
let l:options['err_io'] = 'out'
|
|
let l:options['in_io'] = 'null'
|
|
let l:options['out_mode'] = 'nl'
|
|
let l:options['err_mode'] = 'nl'
|
|
let l:options['stoponexit'] = 'term'
|
|
if g:asyncrun_stop != ''
|
|
let l:options['stoponexit'] = g:asyncrun_stop
|
|
endif
|
|
let s:async_job = job_start(l:args, l:options)
|
|
let l:success = (job_status(s:async_job) != 'fail')? 1 : 0
|
|
else
|
|
let l:callbacks = {'shell': 'AsyncRun'}
|
|
let l:callbacks['on_stdout'] = function('s:AsyncRun_Job_NeoVim')
|
|
let l:callbacks['on_stderr'] = function('s:AsyncRun_Job_NeoVim')
|
|
let l:callbacks['on_exit'] = function('s:AsyncRun_Job_NeoVim')
|
|
let s:async_job = jobstart(l:args, l:callbacks)
|
|
let l:success = (s:async_job > 0)? 1 : 0
|
|
endif
|
|
if l:success != 0
|
|
let s:async_output = {}
|
|
let s:async_head = 0
|
|
let s:async_tail = 0
|
|
let l:arguments = "[".l:name."]"
|
|
let l:title = ':AsyncRun '.l:name
|
|
if s:async_nvim == 0
|
|
if v:version >= 800 || has('patch-7.4.2210')
|
|
call setqflist([], ' ', {'title':l:title})
|
|
else
|
|
call setqflist([], '')
|
|
endif
|
|
else
|
|
call setqflist([], ' ', l:title)
|
|
endif
|
|
call setqflist([{'text':l:arguments}], 'a')
|
|
let s:async_start = float2nr(reltimefloat(reltime()))
|
|
let l:name = 'g:AsyncRun_Job_OnTimer'
|
|
let s:async_timer = timer_start(100, l:name, {'repeat':-1})
|
|
let s:async_state = 1
|
|
let g:asyncrun_status = "running"
|
|
let s:async_info.post = s:async_info.postsave
|
|
let s:async_info.auto = s:async_info.autosave
|
|
let s:async_info.postsave = ''
|
|
let s:async_info.autosave = ''
|
|
let g:asyncrun_text = s:async_info.text
|
|
call s:AsyncRun_Job_AutoCmd(0, s:async_info.auto)
|
|
call s:AutoCmd('Start')
|
|
redrawstatus!
|
|
else
|
|
unlet s:async_job
|
|
call s:ErrorMsg("Background job start failed '".a:cmd."'")
|
|
redrawstatus!
|
|
return -5
|
|
endif
|
|
return 0
|
|
endfunc
|
|
|
|
|
|
" stop background job
|
|
function! s:AsyncRun_Job_Stop(how)
|
|
let l:how = (a:how != '')? a:how : 'term'
|
|
if s:asyncrun_support == 0
|
|
call s:NotSupport()
|
|
return -1
|
|
endif
|
|
while s:async_head > s:async_tail
|
|
let s:async_head -= 1
|
|
unlet s:async_output[s:async_head]
|
|
endwhile
|
|
if exists('s:async_job')
|
|
if s:async_nvim == 0
|
|
if job_status(s:async_job) == 'run'
|
|
if job_stop(s:async_job, l:how)
|
|
return 0
|
|
else
|
|
return -2
|
|
endif
|
|
else
|
|
return -3
|
|
endif
|
|
else
|
|
if s:async_job > 0
|
|
call jobstop(s:async_job)
|
|
endif
|
|
endif
|
|
else
|
|
return -4
|
|
endif
|
|
return 0
|
|
endfunc
|
|
|
|
|
|
" get job status
|
|
function! s:AsyncRun_Job_Status()
|
|
if exists('s:async_job')
|
|
if s:async_nvim == 0
|
|
return job_status(s:async_job)
|
|
else
|
|
return 'run'
|
|
endif
|
|
else
|
|
return 'none'
|
|
endif
|
|
endfunc
|
|
|
|
|
|
|
|
"----------------------------------------------------------------------
|
|
" Utilities
|
|
"----------------------------------------------------------------------
|
|
|
|
" Replace string
|
|
function! s:StringReplace(text, old, new)
|
|
let l:data = split(a:text, a:old, 1)
|
|
return join(l:data, a:new)
|
|
endfunc
|
|
|
|
" Trim leading and tailing spaces
|
|
function! s:StringStrip(text)
|
|
return substitute(a:text, '^\s*\(.\{-}\)\s*$', '\1', '')
|
|
endfunc
|
|
|
|
" extract options from command
|
|
function! s:ExtractOpt(command)
|
|
let cmd = a:command
|
|
let opts = {}
|
|
while cmd =~# '^-\%(\w\+\)\%([= ]\|$\)'
|
|
let opt = matchstr(cmd, '^-\zs\w\+')
|
|
if cmd =~ '^-\w\+='
|
|
let val = matchstr(cmd, '^-\w\+=\zs\%(\\.\|\S\)*')
|
|
else
|
|
let val = (opt == 'cwd')? '' : 1
|
|
endif
|
|
let opts[opt] = substitute(val, '\\\(\s\)', '\1', 'g')
|
|
let cmd = substitute(cmd, '^-\w\+\%(=\%(\\.\|\S\)*\)\=\s*', '', '')
|
|
endwhile
|
|
let cmd = substitute(cmd, '^\s*\(.\{-}\)\s*$', '\1', '')
|
|
let cmd = substitute(cmd, '^@\s*', '', '')
|
|
let opts.cwd = get(opts, 'cwd', '')
|
|
let opts.mode = get(opts, 'mode', '')
|
|
let opts.save = get(opts, 'save', '')
|
|
let opts.program = get(opts, 'program', '')
|
|
let opts.post = get(opts, 'post', '')
|
|
let opts.text = get(opts, 'text', '')
|
|
let opts.auto = get(opts, 'auto', '')
|
|
if 0
|
|
echom 'cwd:'. opts.cwd
|
|
echom 'mode:'. opts.mode
|
|
echom 'save:'. opts.save
|
|
echom 'program:'. opts.program
|
|
echom 'command:'. cmd
|
|
endif
|
|
return [cmd, opts]
|
|
endfunc
|
|
|
|
" write script to a file and return filename
|
|
function! s:ScriptWrite(command, pause)
|
|
let l:tmp = fnamemodify(tempname(), ':h') . '\asyncrun.cmd'
|
|
if s:asyncrun_windows != 0
|
|
let l:line = ['@echo off', 'call '.a:command]
|
|
if a:pause != 0
|
|
let l:line += ['pause']
|
|
endif
|
|
else
|
|
let l:line = ['#! '.&shell]
|
|
let l:line += [a:command]
|
|
if a:pause != 0
|
|
let l:line += ['read -n1 -rsp "press any key to confinue ..."']
|
|
endif
|
|
let l:tmp = tempname()
|
|
endif
|
|
if v:version >= 700
|
|
call writefile(l:line, l:tmp)
|
|
else
|
|
exe 'redir ! > '.fnameescape(l:tmp)
|
|
for l:index in range(len(l:line))
|
|
silent echo l:line[l:index]
|
|
endfor
|
|
redir END
|
|
endif
|
|
return l:tmp
|
|
endfunc
|
|
|
|
|
|
"----------------------------------------------------------------------
|
|
" asyncrun - run
|
|
"----------------------------------------------------------------------
|
|
function! asyncrun#run(bang, opts, args)
|
|
let l:macros = {}
|
|
let l:macros['VIM_FILEPATH'] = expand("%:p")
|
|
let l:macros['VIM_FILENAME'] = expand("%:t")
|
|
let l:macros['VIM_FILEDIR'] = expand("%:p:h")
|
|
let l:macros['VIM_FILENOEXT'] = expand("%:t:r")
|
|
let l:macros['VIM_FILEEXT'] = "." . expand("%:e")
|
|
let l:macros['VIM_CWD'] = getcwd()
|
|
let l:macros['VIM_RELDIR'] = expand("%:h:.")
|
|
let l:macros['VIM_RENAME'] = expand("%:p:.")
|
|
let l:macros['VIM_CWORD'] = expand("<cword>")
|
|
let l:macros['VIM_CFILE'] = expand("<cfile>")
|
|
let l:macros['VIM_VERSION'] = ''.v:version
|
|
let l:macros['VIM_SVRNAME'] = v:servername
|
|
let l:macros['VIM_COLUMNS'] = ''.&columns
|
|
let l:macros['VIM_LINES'] = ''.&lines
|
|
let l:macros['VIM_GUI'] = has('gui_running')? 1 : 0
|
|
let l:macros['<cwd>'] = getcwd()
|
|
let l:command = s:StringStrip(a:args)
|
|
let cd = haslocaldir()? 'lcd ' : 'cd '
|
|
let l:retval = ''
|
|
|
|
" extract options
|
|
let [l:command, l:opts] = s:ExtractOpt(l:command)
|
|
|
|
" replace macros and setup environment variables
|
|
for [l:key, l:val] in items(l:macros)
|
|
let l:replace = (l:key[0] != '<')? '$('.l:key.')' : l:key
|
|
if l:key[0] != '<'
|
|
exec 'let $'.l:key.' = l:val'
|
|
endif
|
|
let l:command = s:StringReplace(l:command, l:replace, l:val)
|
|
let l:opts.cwd = s:StringReplace(l:opts.cwd, l:replace, l:val)
|
|
let l:opts.text = s:StringReplace(l:opts.text, l:replace, l:val)
|
|
endfor
|
|
|
|
" combine options
|
|
if type(a:opts) == type({})
|
|
for [l:key, l:val] in items(a:opts)
|
|
let l:opts[l:key] = l:val
|
|
endfor
|
|
endif
|
|
|
|
" check if need to save
|
|
let l:save = get(l:opts, 'save', '')
|
|
if l:save
|
|
try
|
|
if l:save == '1'
|
|
silent! update
|
|
else
|
|
silent! wall
|
|
endif
|
|
catch /.*/
|
|
endtry
|
|
endif
|
|
|
|
if a:bang == '!'
|
|
let s:async_scroll = 0
|
|
else
|
|
let s:async_scroll = 1
|
|
endif
|
|
|
|
" check mode
|
|
let l:mode = g:asyncrun_mode
|
|
|
|
if l:opts.mode != ''
|
|
let l:mode = l:opts.mode
|
|
endif
|
|
|
|
" process makeprg/grepprg in -program=?
|
|
let l:program = ""
|
|
|
|
if l:opts.program == 'make'
|
|
let l:program = &makeprg
|
|
elseif l:opts.program == 'grep'
|
|
let l:program = &grepprg
|
|
endif
|
|
|
|
if l:program != ''
|
|
if l:program =~# '\$\*'
|
|
let l:command = s:StringReplace(l:program, '\$\*', l:command)
|
|
elseif l:command != ''
|
|
let l:command = l:program . ' ' . l:command
|
|
else
|
|
let l:command = l:program
|
|
endif
|
|
let l:command = s:StringStrip(l:command)
|
|
endif
|
|
|
|
if l:command =~ '^\s*$'
|
|
echohl ErrorMsg
|
|
echom "E471: Command required"
|
|
echohl NONE
|
|
return
|
|
endif
|
|
|
|
if l:mode >= 10
|
|
let l:opts.cmd = l:command
|
|
let l:opts.mode = l:mode
|
|
if g:asyncrun_hook != ''
|
|
exec 'call '. g:asyncrun_hook .'(l:opts)'
|
|
endif
|
|
return
|
|
endif
|
|
|
|
if l:opts.cwd != ''
|
|
let l:opts.savecwd = getcwd()
|
|
try
|
|
silent exec cd . fnameescape(l:opts.cwd)
|
|
catch /.*/
|
|
echohl ErrorMsg
|
|
echom "E344: Can't find directory \"".l:opts.cwd."\" in -cwd"
|
|
echohl NONE
|
|
return
|
|
endtry
|
|
endif
|
|
|
|
if l:mode == 0 && s:asyncrun_support != 0
|
|
let s:async_info.postsave = opts.post
|
|
let s:async_info.autosave = opts.auto
|
|
let s:async_info.text = opts.text
|
|
if s:AsyncRun_Job_Start(l:command) != 0
|
|
call s:AutoCmd('Error')
|
|
endif
|
|
elseif l:mode <= 1 && has('quickfix')
|
|
call s:AutoCmd('Pre')
|
|
call s:AutoCmd('Start')
|
|
let l:makesave = &l:makeprg
|
|
let l:script = s:ScriptWrite(l:command, 0)
|
|
if s:asyncrun_windows != 0
|
|
let &l:makeprg = shellescape(l:script)
|
|
else
|
|
let &l:makeprg = 'source '. shellescape(l:script)
|
|
endif
|
|
if has('autocmd')
|
|
call s:AsyncRun_Job_AutoCmd(0, opts.auto)
|
|
exec "noautocmd make!"
|
|
call s:AsyncRun_Job_AutoCmd(1, opts.auto)
|
|
else
|
|
exec "make!"
|
|
endif
|
|
let &l:makeprg = l:makesave
|
|
if s:asyncrun_windows == 0
|
|
try | call delete(l:script) | catch | endtry
|
|
endif
|
|
let g:asyncrun_text = opts.text
|
|
if opts.post != ''
|
|
exec opts.post
|
|
endif
|
|
call s:AutoCmd('Stop')
|
|
elseif l:mode <= 2
|
|
call s:AutoCmd('Pre')
|
|
call s:AutoCmd('Start')
|
|
exec '!'. escape(l:command, '%#')
|
|
let g:asyncrun_text = opts.text
|
|
if opts.post != ''
|
|
exec opts.post
|
|
endif
|
|
call s:AutoCmd('Stop')
|
|
elseif l:mode == 3
|
|
if s:asyncrun_windows != 0 && has('python')
|
|
let l:script = s:ScriptWrite(l:command, 0)
|
|
py import subprocess, vim
|
|
py argv = {'args': vim.eval('l:script'), 'shell': True}
|
|
py argv['stdout'] = subprocess.PIPE
|
|
py argv['stderr'] = subprocess.STDOUT
|
|
py p = subprocess.Popen(**argv)
|
|
py text = p.stdout.read()
|
|
py p.stdout.close()
|
|
py p.wait()
|
|
if has('patch-7.4.145')
|
|
let l:retval = pyeval('text')
|
|
else
|
|
py text = text.replace('\\', '\\\\').replace('"', '\\"')
|
|
py text = text.replace('\n', '\\n').replace('\r', '\\r')
|
|
py vim.command('let l:retval = "%s"'%text)
|
|
endif
|
|
elseif s:asyncrun_windows != 0 && has('python3')
|
|
let l:script = s:ScriptWrite(l:command, 0)
|
|
py3 import subprocess, vim
|
|
py3 argv = {'args': vim.eval('l:script'), 'shell': True}
|
|
py3 argv['stdout'] = subprocess.PIPE
|
|
py3 argv['stderr'] = subprocess.STDOUT
|
|
py3 p = subprocess.Popen(**argv)
|
|
py3 text = p.stdout.read()
|
|
py3 p.stdout.close()
|
|
py3 p.wait()
|
|
if has('patch-7.4.145')
|
|
let l:retval = py3eval('text')
|
|
else
|
|
py3 text = text.replace('\\', '\\\\').replace('"', '\\"')
|
|
py3 text = text.replace('\n', '\\n').replace('\r', '\\r')
|
|
py3 vim.command('let l:retval = "%s"'%text)
|
|
endif
|
|
else
|
|
let l:retval = system(l:command)
|
|
endif
|
|
let g:asyncrun_text = opts.text
|
|
if opts.post != ''
|
|
exec opts.post
|
|
endif
|
|
elseif l:mode <= 5
|
|
if s:asyncrun_windows != 0 && (has('gui_running') || has('nvim'))
|
|
let l:ccc = shellescape(s:ScriptWrite(l:command, 1))
|
|
if l:mode == 4
|
|
silent exec '!start cmd /C '. l:ccc
|
|
else
|
|
silent exec '!start /b cmd /C '. l:ccc
|
|
endif
|
|
redraw
|
|
else
|
|
if l:mode == 4
|
|
exec '!' . escape(l:command, '%#')
|
|
else
|
|
call system(l:command . ' &')
|
|
endif
|
|
endif
|
|
let g:asyncrun_text = opts.text
|
|
if opts.post != ''
|
|
exec opts.post
|
|
endif
|
|
endif
|
|
|
|
if l:opts.cwd != ''
|
|
silent exec cd fnameescape(l:opts.savecwd)
|
|
endif
|
|
|
|
return l:retval
|
|
endfunc
|
|
|
|
|
|
"----------------------------------------------------------------------
|
|
" asyncrun - stop
|
|
"----------------------------------------------------------------------
|
|
function! asyncrun#stop(bang)
|
|
if a:bang == ''
|
|
return s:AsyncRun_Job_Stop('term')
|
|
else
|
|
return s:AsyncRun_Job_Stop('kill')
|
|
endif
|
|
endfunc
|
|
|
|
|
|
|
|
"----------------------------------------------------------------------
|
|
" asyncrun - status
|
|
"----------------------------------------------------------------------
|
|
function! asyncrun#status()
|
|
return s:AsyncRun_Job_Status()
|
|
endfunc
|
|
|
|
|
|
|
|
"----------------------------------------------------------------------
|
|
" asyncrun -version
|
|
"----------------------------------------------------------------------
|
|
function! asyncrun#version()
|
|
return '1.3.0'
|
|
endfunc
|
|
|
|
|
|
"----------------------------------------------------------------------
|
|
" Commands
|
|
"----------------------------------------------------------------------
|
|
command! -bang -nargs=+ -complete=file AsyncRun
|
|
\ call asyncrun#run('<bang>', '', <q-args>)
|
|
|
|
command! -bang -nargs=0 AsyncStop call asyncrun#stop('<bang>')
|
|
|
|
|
|
|
|
"----------------------------------------------------------------------
|
|
" Fast command to toggle quickfix
|
|
"----------------------------------------------------------------------
|
|
function! asyncrun#quickfix_toggle(size, ...)
|
|
let l:mode = (a:0 == 0)? 2 : (a:1)
|
|
function! s:WindowCheck(mode)
|
|
if &buftype == 'quickfix'
|
|
let s:quickfix_open = 1
|
|
return
|
|
endif
|
|
if a:mode == 0
|
|
let w:quickfix_save = winsaveview()
|
|
else
|
|
if exists('w:quickfix_save')
|
|
call winrestview(w:quickfix_save)
|
|
unlet w:quickfix_save
|
|
endif
|
|
endif
|
|
endfunc
|
|
let s:quickfix_open = 0
|
|
let l:winnr = winnr()
|
|
noautocmd windo call s:WindowCheck(0)
|
|
noautocmd silent! exec ''.l:winnr.'wincmd w'
|
|
if l:mode == 0
|
|
if s:quickfix_open != 0
|
|
silent! cclose
|
|
endif
|
|
elseif l:mode == 1
|
|
if s:quickfix_open == 0
|
|
exec 'botright copen '. ((a:size > 0)? a:size : ' ')
|
|
wincmd k
|
|
endif
|
|
elseif l:mode == 2
|
|
if s:quickfix_open == 0
|
|
exec 'botright copen '. ((a:size > 0)? a:size : ' ')
|
|
wincmd k
|
|
else
|
|
silent! cclose
|
|
endif
|
|
endif
|
|
noautocmd windo call s:WindowCheck(1)
|
|
noautocmd silent! exec ''.l:winnr.'wincmd w'
|
|
endfunc
|
|
|
|
|
|
|
|
|