2020-06-13 14:06:35 +08:00
" vim: ts=4 sw=4 et
scriptencoding utf -8
if ! exists ( 's:make_id' )
let s :make_id = 0
endif
" A map of make_id to options, e.g. cwd when jobs where started.
if ! exists ( 's:make_info' )
let s :make_info = {}
endif
if ! exists ( 's:job_id' )
let s :job_id = 1
endif
if ! exists ( 's:jobs' )
let s :jobs = {}
endif
if ! exists ( 's:map_job_ids' )
let s :map_job_ids = {}
endif
" Errors by [maker_type][bufnr][lnum]
let s :current_errors = {'project' : {}, 'file' : {}}
if ! has ( 'nvim' )
let s :kill_vim_timers = {}
endif
" A list of references to keep when profiling.
" Workaround for https://github.com/vim/vim/issues/2350, where
" https://github.com/blueyed/vader.vim/commit/e66d91dea is not enough.
if v :profiling
let s :hack_keep_refs_for_profiling = []
endif
" Can Neovim buffer output?
let s :nvim_can_buffer_output = has ( 'nvim-0.3.0' ) ? 1 : 0
" Private function to access script-local variables during tests.
function ! neomake #_get_s ( ) abort
return s :
endfunction
" Sentinels.
let s :unset_list = []
let s :unset_dict = {}
let s :unset = {}
let s :can_use_env_in_job_opts = has ( 'patch-8.0.0902' ) && has ( 'patch-8.0.1832' )
let s :is_testing = exists ( 'g:neomake_test_messages' )
let s :async = has ( 'nvim' )
\ | | has ( 'channel' ) && has ( 'job' ) && has ( 'patch-8.0.0027' )
function ! neomake #has_async_support ( ) abort
return s :async
endfunction
if v :version > = 704 | | ( v :version = = 703 && has ( 'patch1058' ) )
function ! s :function ( name ) abort
return function ( a :name )
endfunction
else
" Older Vim does not handle s: function references across files.
function ! s :function ( name ) abort
return function ( substitute ( a :name , '^s:' , matchstr ( expand ( '<sfile>' ) , '.*\zs<SNR>\d\+_' ) , '' ) )
endfunction
endif
function ! s :sort_jobs ( a , b ) abort
return a :a .id - a :b .id
endfunction
function ! neomake #GetJobs ( ...) abort
if empty ( s :jobs )
return []
endif
let jobs = copy ( values ( s :jobs ) )
if a :0
call filter ( jobs , 'index(a:1, v:val.id) != -1' )
endif
return sort ( jobs , function ( 's:sort_jobs' ) )
endfunction
function ! neomake #GetJob ( job_id ) abort
return s :jobs [a :job_id ]
endfunction
" Not documented, only used in tests for now.
function ! neomake #GetStatus ( ) abort
return {
\ 'last_make_id' : s :make_id ,
\ 'make_info' : s :make_info ,
\ 'action_queue' : g :neomake #action_queue #_s .action_queue ,
\ }
endfunction
" neomake#GetMakeOptions: not documented, only used internally for now.
" More lax when not being used in tests to avoid errors, but fail during tests.
if s :is_testing
function ! neomake #GetMakeOptions ( ...) abort
let make_id = a :0 ? a :1 : s :make_id
try
let r = s :make_info [make_id ]
catch
let msg = printf ( 'GetMakeOptions failed: %s (in %s)' , v :exception , v :throwpoint )
call vader #log ( msg )
let g :neomake_test_errors + = [msg ]
return {'verbosity' : 3 }
endtry
return r
endfunction
else
function ! neomake #GetMakeOptions ( ...) abort
let make_id = a :0 ? a :1 : s :make_id
if ! has_key ( s :make_info , make_id )
call neomake #log #warning ( 'warning: missing make_info key: ' .make_id .'.' )
return {'verbosity' : get ( g :, 'neomake_verbose' , 1 ) }
endif
return s :make_info [make_id ]
endfunction
endif
function ! neomake #ListJobs ( ) abort
if ! s :async
echom 'This Vim version has no support for jobs.'
return
endif
let jobs = neomake #GetJobs ( )
if empty ( jobs )
return
endif
echom 'make_id | job_id | name/maker'
for jobinfo in jobs
let desc = ! empty ( jobinfo .maker .name ) && jobinfo .name ! = jobinfo .maker .name
\ ? jobinfo .name . ' (' .jobinfo .maker .name .')'
\ : jobinfo .name
echom printf ( '%7d | %6d | %s' , jobinfo .make_id , jobinfo .id , desc )
endfor
endfunction
function ! neomake #CancelMake ( ...) abort
let make_id = a :0 ? a :1 : s :make_id
if ! has_key ( s :make_info , make_id )
call neomake #log #error ( 'CancelMake: make not found: ' .make_id .'.' )
return 0
endif
let bang = a :0 > 1 ? a :1 : 0
let make_info = s :make_info [make_id ]
call neomake #log #debug ( 'Canceling make.' , make_info )
let make_info .canceled = 1
let jobs = filter ( copy ( values ( s :jobs ) ) , 'v:val.make_id == make_id' )
call s :abort_next_makers ( make_id )
for job in jobs
call neomake #CancelJob ( job .id , bang )
endfor
call neomake #action_queue #clean ( make_info )
" Ensure that make info gets cleaned really, e.g. if there were no jobs yet.
if has_key ( s :make_info , make_id )
call s :clean_make_info ( make_info , bang )
endif
return 1
endfunction
function ! neomake #CancelAllMakes ( ...) abort
let bang = a :0 ? a :1 : 0
for make_id in keys ( s :make_info )
call neomake #CancelMake ( make_id , bang )
endfor
endfunction
" Returns 1 if a job was canceled, 0 otherwise.
function ! neomake #CancelJob ( job_id , ...) abort
let job_id = type ( a :job_id ) = = type ( {}) ? a :job_id .id : + a :job_id
let remove_always = a :0 ? a :1 : 0
let jobinfo = get ( s :jobs , job_id , {})
call neomake #log #debug ( 'Canceling job.' , jobinfo )
call neomake #action_queue #clean ( empty ( jobinfo ) ? {'id' : job_id } : jobinfo )
if empty ( jobinfo )
call neomake #log #error ( 'CancelJob: job not found: ' .job_id .'.' )
return 0
endif
if get ( jobinfo , 'canceled' , 0 )
call neomake #log #info ( 'Job was canceled already.' , jobinfo )
if remove_always
call s :CleanJobinfo ( jobinfo )
endif
return 0
endif
let jobinfo .canceled = 1
let ret = 0
if get ( jobinfo , 'finished' )
call neomake #log #debug ( 'Removing already finished job.' , jobinfo )
elseif has_key ( jobinfo , 'exit_code' )
call neomake #log #debug ( 'Job exited already.' , jobinfo )
elseif has_key ( jobinfo .maker , 'get_list_entries' )
call neomake #log #debug ( 'Removing job for get_list_entries.' , jobinfo )
elseif s :async
if has ( 'nvim' )
let job = jobinfo .nvim_job
call neomake #log #debug ( printf ( 'Stopping Neovim job: %s.' , job ) , jobinfo )
else
let job = jobinfo .vim_job
call neomake #log #debug ( printf ( 'Stopping Vim job: %s.' , job ) , jobinfo )
endif
if has ( 'nvim' )
try
let ret = jobstop ( job )
catch /^Vim\%((\a\+)\)\=:\(E474\|E900\):/
call neomake #log #info ( printf (
\ 'jobstop failed: %s.' , v :exception ) , jobinfo )
endtry
else
" Use ch_status here, since job_status might be 'dead' already,
" without the exit handler being called yet.
if job_status ( job ) ! = # 'run'
call neomake #log #info (
\ 'job_stop: job was not running anymore.' , jobinfo )
else
" NOTE: might be "dead" already, but that is fine.
call job_stop ( job )
let ret = 1
if job_status ( job ) = = # 'run'
let timer = timer_start ( 1000 , function ( 's:kill_vimjob_cb' ) )
let s :kill_vim_timers [timer ] = jobinfo
endif
endif
endif
endif
if ret = = 0 | | remove_always
call s :CleanJobinfo ( jobinfo )
endif
return ret
endfunction
function ! s :kill_vimjob_cb ( timer ) abort
let jobinfo = s :kill_vim_timers [a :timer ]
let vim_job = jobinfo .vim_job
if job_status ( vim_job ) = = # 'run'
call neomake #log #debug ( 'Forcefully killing still running Vim job.' , jobinfo )
call job_stop ( vim_job , 'kill' )
endif
unlet s :kill_vim_timers [a :timer ]
endfunction
function ! neomake #CancelJobs ( bang ) abort
call neomake #log #debug ( printf ( 'Canceling %d jobs.' , len ( s :jobs ) ) )
for job in neomake #GetJobs ( )
call neomake #CancelJob ( job .id , a :bang )
endfor
endfunction
function ! s :handle_get_list_entries ( jobinfo , ...) abort
if ! a :0
return s :pcall ( 's:handle_get_list_entries' , [a :jobinfo ])
endif
let jobinfo = a :jobinfo
let jobinfo .serialize = 0
let maker = jobinfo .maker
try
let entries = maker .get_list_entries ( jobinfo )
catch /^\%(Vim\%((\a\+)\)\=:\%(E48\|E523\)\)\@!/ " everything, but E48/E523 (sandbox / not allowed here)
if v :exception = = # 'NeomakeTestsException'
throw v :exception
endif
call neomake #log #exception ( printf (
\ 'Error during get_list_entries for %s: %s.' ,
\ jobinfo .maker .name , v :exception ) , jobinfo )
call s :CleanJobinfo ( jobinfo )
return g :neomake #action_queue #processed
endtry
if type ( entries ) ! = type ( [])
call neomake #log #error ( printf ( 'The get_list_entries method for maker %s did not return a list, but: %s.' , jobinfo .maker .name , string ( entries ) [:100 ]) , jobinfo )
elseif ! empty ( entries ) && type ( entries [0 ]) ! = type ( {})
call neomake #log #error ( printf ( 'The get_list_entries method for maker %s did not return a list of dicts, but: %s.' , jobinfo .maker .name , string ( entries ) [:100 ]) , jobinfo )
else
call s :ProcessEntries ( jobinfo , entries )
endif
call s :CleanJobinfo ( jobinfo )
return g :neomake #action_queue #processed
endfunction
function ! s :MakeJob ( make_id , options ) abort
let job_id = s :job_id
let s :job_id + = 1
" Optional:
" - serialize (default: 0 for async (and get_list_entries),
" 1 for non-async)
" - serialize_abort_on_error (default: 0)
" - exit_callback (string/function, default: 0)
let jobinfo = extend ( neomake #jobinfo #new ( ) , extend ( {
\ 'id' : job_id ,
\ 'make_id' : a :make_id ,
\ 'name' : empty ( get ( a :options .maker , 'name' , '' ) ) ? 'neomake_' .job_id : a :options .maker .name ,
\ 'maker' : a :options .maker ,
\ 'bufnr' : a :options .bufnr ,
\ 'file_mode' : a :options .file_mode ,
\ 'ft' : a :options .ft ,
\ 'cwd' : s :make_info [a :make_id ].cwd ,
\ }, a :options ) )
let maker = jobinfo .maker
if has_key ( maker , 'get_list_entries' )
call neomake #log #info ( printf (
\ '%s: getting entries via get_list_entries.' ,
\ maker .name ) , jobinfo )
let s :jobs [jobinfo .id ] = jobinfo
let s :make_info [a :make_id ].active_jobs + = [jobinfo ]
call s :handle_get_list_entries ( jobinfo )
return jobinfo
endif
call extend ( jobinfo , {
\ 'output_stream' : a :options .maker .output_stream ,
\ 'buffer_output' : a :options .maker .buffer_output ,
\ }, 'keep' )
let error = ''
try
" Change to job's cwd (before args, for relative filename).
let cd_error = jobinfo .cd ( )
if ! empty ( cd_error )
throw printf ( "Neomake: %s: could not change to maker's cwd (%s): %s." ,
\ maker .name , jobinfo .cd_from_setting , cd_error )
endif
let jobinfo .argv = maker ._get_argv ( jobinfo )
call neomake #utils #hook ( 'NeomakeJobInit' , {'jobinfo' : jobinfo })
let start_msg = s :async ? 'Starting async job' : 'Starting'
if type ( jobinfo .argv ) = = type ( '' )
let start_msg .= ' [string]: ' .jobinfo .argv
else
let start_msg .= ': ' .join ( map ( copy ( jobinfo .argv ) , 'neomake#utils#shellescape(v:val)' ) )
endif
call neomake #log #info ( start_msg .'.' , jobinfo )
let cwd = jobinfo .cwd
let changed = ! empty ( jobinfo .cd_back_cmd )
if changed
call neomake #log #debug ( 'cwd: ' .cwd .' (changed).' , jobinfo )
else
call neomake #log #debug ( 'cwd: ' .cwd .'.' , jobinfo )
endif
let base_job_opts = {}
if has_key ( jobinfo , 'filename' )
if s :can_use_env_in_job_opts
let base_job_opts = {
\ 'env' : {
\ 'NEOMAKE_FILE' : jobinfo .filename
\ }}
else
let save_env_file = exists ( '$NEOMAKE_FILE' ) ? $NEOMAKE_FILE : s :unset
let $NEOMAKE_FILE = jobinfo .filename
endif
endif
" Lock maker to make sure it does not get changed accidentally, but
" only with depth=1, so that a postprocess object can change itself.
lockvar 1 maker
if s :async
if has ( 'nvim' )
if jobinfo .buffer_output
let opts = extend ( base_job_opts , {
\ 'stdout_buffered' : 1 ,
\ 'stderr_buffered' : 1 ,
\ })
if s :nvim_can_buffer_output = = 1
let opts .on_exit = function ( 's:nvim_exit_handler_buffered' )
else
call extend ( opts , {
\ 'on_stdout' : function ( 's:nvim_output_handler' ) ,
\ 'on_stderr' : function ( 's:nvim_output_handler' ) ,
\ })
let opts .on_exit = function ( 's:nvim_exit_handler' )
endif
let jobinfo .jobstart_opts = opts
else
let opts = {
\ 'on_stdout' : function ( 's:nvim_output_handler' ) ,
\ 'on_stderr' : function ( 's:nvim_output_handler' ) ,
\ 'on_exit' : function ( 's:nvim_exit_handler' ) ,
\ }
endif
if has_key ( maker , 'nvim_job_opts' )
call extend ( opts , maker .nvim_job_opts )
endif
if ! has ( 'nvim-0.3.0' )
\ && ! neomake #utils #IsRunningWindows ( )
\ && ! has_key ( opts , 'detach' )
\ && ! has_key ( opts , 'pty' )
" Always use detach to trigger setsid() with older Neovim.
let opts .detach = 1
endif
try
let job = jobstart ( jobinfo .argv , opts )
catch
let error = printf ( 'Failed to start Neovim job: %s: %s.' ,
\ string ( jobinfo .argv ) , v :exception )
endtry
if empty ( error )
if job = = 0
let error = printf ( 'Failed to start Neovim job: %s: %s.' ,
\ 'Job table is full or invalid arguments given' , string ( jobinfo .argv ) )
elseif job = = -1
let error = printf ( 'Failed to start Neovim job: %s: %s.' ,
\ 'Executable not found' , string ( jobinfo .argv ) )
else
let s :map_job_ids [job ] = jobinfo .id
let jobinfo .nvim_job = job
let s :jobs [jobinfo .id ] = jobinfo
if get ( jobinfo , 'uses_stdin' , 0 )
call jobsend ( job , s :make_info [a :make_id ].buffer_lines )
call jobclose ( job , 'stdin' )
endif
endif
endif
else
" vim-async.
let opts = extend ( base_job_opts , {
\ 'out_cb' : function ( 's:vim_output_handler_stdout' ) ,
\ 'err_cb' : function ( 's:vim_output_handler_stderr' ) ,
\ 'close_cb' : function ( 's:vim_exit_handler' ) ,
\ 'mode' : 'raw' ,
\ })
if has_key ( maker , 'vim_job_opts' )
call extend ( opts , maker .vim_job_opts )
endif
try
let job = job_start ( jobinfo .argv , opts )
" Get this as early as possible!
let channel_id = ch_info ( job ) ['id' ]
catch
" NOTE: not covered in tests. Vim seems to always return
" a job. Might be able to trigger this using custom opts?!
let error = printf ( 'Failed to start Vim job: %s: %s.' ,
\ jobinfo .argv , v :exception )
endtry
if empty ( error )
let jobinfo .vim_job = job
let s :map_job_ids [channel_id ] = jobinfo .id
let s :jobs [jobinfo .id ] = jobinfo
call neomake #log #debug ( printf ( 'Vim job: %s.' ,
\ string ( job_info ( job ) ) ) , jobinfo )
call neomake #log #debug ( printf ( 'Vim channel: %s.' ,
\ string ( ch_info ( job ) ) ) , jobinfo )
if get ( jobinfo , 'uses_stdin' , 0 )
call ch_sendraw ( job , join ( s :make_info [a :make_id ].buffer_lines , "\n" ) )
call ch_close_in ( job )
endif
endif
endif
" Bail out on errors.
if ! empty ( error )
throw 'Neomake: ' .error
endif
call neomake #utils #hook ( 'NeomakeJobStarted' , {'jobinfo' : jobinfo })
else
" vim-sync.
" Use a temporary file to capture stderr.
let stderr_file = tempname ( )
let argv = jobinfo .argv . ' 2>' .stderr_file
try
if get ( jobinfo , 'uses_stdin' , 0 )
" Pass stdin to system(), but only if non-empty.
" Otherwise it might cause E677 (vim74-trusty at least).
let stdin = join ( s :make_info [a :make_id ].buffer_lines , "\n" )
if ! empty ( stdin )
let output = system ( argv , stdin )
else
let output = system ( argv )
endif
else
let output = system ( argv )
endif
catch /^Vim(let):E484:/
throw printf ( 'Neomake: Could not run %s: %s.' , argv , v :exception )
endtry
let jobinfo .id = job_id
let s :jobs [job_id ] = jobinfo
let s :make_info [a :make_id ].active_jobs + = [jobinfo ]
call s :output_handler ( jobinfo , split ( output , '\r\?\n' , 1 ) , 'stdout' , 0 )
let stderr_output = readfile ( stderr_file )
if ! empty ( stderr_output )
call s :output_handler ( jobinfo , stderr_output , 'stderr' , 1 )
endif
call delete ( stderr_file )
call s :exit_handler ( jobinfo , v :shell_error )
return jobinfo
endif
finally
call jobinfo .cd_back ( )
2025-02-03 16:36:38 +08:00
if exists ( 'l:save_env_file' )
2020-06-13 14:06:35 +08:00
call s :restore_env ( 'NEOMAKE_FILE' , save_env_file )
endif
endtry
let s :make_info [a :make_id ].active_jobs + = [jobinfo ]
return jobinfo
endfunction
if ! s :can_use_env_in_job_opts
function ! s :restore_env ( var , value ) abort
" Cannot unlet environment vars without patch 8.0.1832.
exe printf ( 'let $%s = %s' , a :var , string ( a :value is s :unset ? '' : a :value ) )
endfunction
endif
let s :command_maker_base = copy ( g :neomake #core #command_maker_base )
" Check if a temporary file is used, and set it in s:make_info in case it is.
function ! s :command_maker_base ._get_tempfilename ( jobinfo ) abort dict
let l :Supports_stdin = neomake #utils #GetSetting ( 'supports_stdin' , self , s :unset_dict , a :jobinfo .ft , a :jobinfo .bufnr )
if Supports_stdin isnot s :unset_dict
if type ( Supports_stdin ) = = type ( function ( 'tr' ) )
let supports_stdin = call ( Supports_stdin , [a :jobinfo ], self )
else
let supports_stdin = Supports_stdin
endif
if supports_stdin
let a :jobinfo .uses_stdin = 1
return get ( self , 'tempfile_name' , '-' )
endif
endif
if has_key ( self , 'tempfile_name' )
return self .tempfile_name
endif
return self ._get_default_tempfilename ( a :jobinfo )
endfunction
function ! s :command_maker_base ._get_default_tempfilename ( jobinfo ) abort dict
let tempfile_enabled = neomake #utils #GetSetting ( 'tempfile_enabled' , self , 1 , a :jobinfo .ft , a :jobinfo .bufnr )
if ! tempfile_enabled
return ''
endif
let make_id = a :jobinfo .make_id
if ! has_key ( s :make_info [make_id ], 'tempfile_name' )
if ! exists ( 's:pid' )
let s :pid = getpid ( )
endif
let slash = neomake #utils #Slash ( )
let dir = neomake #utils #GetSetting ( 'tempfile_dir' , self , '' , a :jobinfo .ft , a :jobinfo .bufnr )
" Use absolute path internally, which is important for removal.
let orig_fname = neomake #utils #fnamemodify ( a :jobinfo .bufnr , ':p' )
if empty ( dir )
if empty ( orig_fname )
let dir = tempname ( )
else
let dir = fnamemodify ( orig_fname , ':h' )
if filewritable ( dir ) ! = 2
let dir = tempname ( )
let s :make_info [make_id ].tempfile_dir = dir
call neomake #log #debug ( 'Using temporary directory for non-writable parent directory.' )
endif
endif
if empty ( orig_fname )
let filename = 'neomaketmp.' .a :jobinfo .ft
else
let filename = fnamemodify ( orig_fname , ':t' )
\ .'@neomake_' .s :pid .'_' .make_id
let ext = fnamemodify ( orig_fname , ':e' )
if ! empty ( ext )
let filename .= '.' .ext
endif
" Use hidden files to make e.g. pytest not trying to import it.
if filename [0 ] ! = # '.'
let filename = '.' . filename
endif
endif
else
let dir = neomake #utils #ExpandArgs ( [dir ], a :jobinfo ) [0 ]
if empty ( orig_fname )
let filename = 'neomaketmp.' .a :jobinfo .ft
else
let filename = fnamemodify ( orig_fname , ':t' )
endif
endif
let temp_file = dir . slash . filename
let s :make_info [make_id ].tempfile_name = temp_file
endif
return s :make_info [make_id ].tempfile_name
endfunction
" Get the filename to use for a:jobinfo's make/buffer.
function ! s :command_maker_base ._get_fname_for_buffer ( jobinfo ) abort
let bufnr = a :jobinfo .bufnr
let bufname = bufname ( bufnr )
let temp_file = ''
if has_key ( a :jobinfo , 'uses_stdin' )
let uses_stdin = a :jobinfo .uses_stdin
if uses_stdin
let temp_file = neomake #utils #GetSetting ( 'tempfile_name' , a :jobinfo .maker , '-' , a :jobinfo .ft , bufnr )
endif
else
if empty ( bufname )
let temp_file = self ._get_tempfilename ( a :jobinfo )
if ! get ( a :jobinfo , 'uses_stdin' , 0 ) && empty ( temp_file )
throw 'Neomake: no file name.'
endif
let used_for = 'unnamed'
elseif getbufvar ( bufnr , '&modified' )
let temp_file = self ._get_tempfilename ( a :jobinfo )
if ! get ( a :jobinfo , 'uses_stdin' , 0 ) && empty ( temp_file )
throw 'Neomake: skip_job: buffer is modified, but temporary files are disabled.'
endif
let used_for = 'modified'
elseif ! filereadable ( bufname )
let temp_file = self ._get_tempfilename ( a :jobinfo )
if ! get ( a :jobinfo , 'uses_stdin' , 0 ) && empty ( temp_file )
" Using ':p' as modifier is unpredictable as per doc, but OK.
throw printf ( 'Neomake: file is not readable (%s)' , fnamemodify ( bufname , ':p' ) )
endif
let used_for = 'unreadable'
else
let bufname = fnamemodify ( bufname , ':.' )
let used_for = ''
endif
let uses_stdin = get ( a :jobinfo , 'uses_stdin' , 0 )
if ! empty ( used_for )
if uses_stdin
call neomake #log #debug ( printf (
\ 'Using stdin for %s buffer (%s).' , used_for , temp_file ) ,
\ a :jobinfo )
elseif ! empty ( temp_file )
call neomake #log #debug ( printf (
\ 'Using tempfile for %s buffer: "%s".' , used_for , temp_file ) ,
\ a :jobinfo )
endif
endif
endif
let make_info = s :make_info [a :jobinfo .make_id ]
" Handle stdin when supports_stdin sets self.tempfile_name = ''.
if uses_stdin
if ! has_key ( make_info , 'buffer_lines' )
let make_info .buffer_lines = neomake #utils #get_buffer_lines ( bufnr )
endif
let bufname = temp_file
elseif ! empty ( temp_file )
" Use relative path for args.
let bufname = fnamemodify ( temp_file , ':.' )
let temp_file = fnamemodify ( temp_file , ':p' )
if ! has_key ( make_info , 'tempfiles' )
let make_info .tempfiles = [temp_file ]
let make_info .created_dirs = s :create_dirs_for_file ( temp_file )
call neomake #utils #write_tempfile ( bufnr , temp_file )
elseif temp_file ! = # make_info .tempfiles [0 ]
call extend ( make_info .created_dirs , s :create_dirs_for_file ( temp_file ) )
call writefile ( readfile ( make_info .tempfiles [0 ], 'b' ) , temp_file , 'b' )
call add ( make_info .tempfiles , temp_file )
endif
let a :jobinfo .tempfile = temp_file
endif
let a :jobinfo .filename = bufname
return bufname
endfunction
function ! s :create_dirs_for_file ( fpath ) abort
let created_dirs = []
let last_dir = a :fpath
while 1
let temp_dir = fnamemodify ( last_dir , ':h' )
if isdirectory ( temp_dir ) | | last_dir = = # temp_dir
break
endif
call insert ( created_dirs , temp_dir )
let last_dir = temp_dir
endwhile
for dir in created_dirs
call mkdir ( dir , '' , 0700 )
endfor
return created_dirs
endfunction
function ! s :command_maker_base ._bind_args ( ) abort dict
" Resolve args, which might be a function or dictionary.
if type ( self .args ) = = type ( function ( 'tr' ) )
" Deprecated: use InitForJob
call neomake #log #warn_once ( printf ( "Please use 'InitForJob' instead of 'args' for maker %s." , self .name ) ,
\ printf ( 'deprecated-args-%s' , self .name ) )
let args = call ( self .args , [])
elseif type ( self .args ) = = type ( {})
" Deprecated: use InitForJob
call neomake #log #warn_once ( printf ( "Please use 'InitForJob' instead of 'args.fn' for maker %s." , self .name ) ,
\ printf ( 'deprecated-args-fn-%s' , self .name ) )
let args = call ( self .args .fn , [], self .args )
else
let args = copy ( self .args )
endif
let self .args = args
return self
endfunction
function ! s :command_maker_base ._get_argv ( jobinfo ) abort dict
let filename = self ._get_fname_for_args ( a :jobinfo )
let args_is_list = type ( self .args ) = = type ( [])
if args_is_list
let args = neomake #utils #ExpandArgs ( self .args , a :jobinfo )
if ! empty ( filename )
call add ( args , filename )
endif
elseif ! empty ( filename )
let args = copy ( self .args )
let args .= ( empty ( args ) ? '' : ' ' ) .neomake #utils #shellescape ( filename )
else
let args = self .args
endif
return neomake #compat #get_argv ( self .exe , args , args_is_list )
endfunction
function ! s :GetMakerForFiletype ( ft , maker_name ) abort
for config_ft in neomake #utils #get_config_fts ( a :ft )
call neomake #utils #load_ft_makers ( config_ft )
let f = 'neomake#makers#ft#' .config_ft .'#' .a :maker_name
if exists ( '*' .f )
let maker = call ( f , [])
return maker
endif
endfor
return s :unset_dict
endfunction
function ! neomake #get_maker_by_name ( maker_name , ...) abort
let for_ft = a :0 ? a :1 : 0
let ft_config = for_ft is # 0 ? &filetype : for_ft
let bufnr = bufnr ( '%' )
if a :maker_name ! ~ # '\v^\w+$'
throw printf ( 'Neomake: Invalid maker name: "%s"' , a :maker_name )
endif
let maker = neomake #utils #GetSetting ( 'maker' , {'name' : a :maker_name }, s :unset_dict , ft_config , bufnr )
if maker is # s :unset_dict
if a :maker_name = = # 'makeprg'
let maker = s :get_makeprg_maker ( )
elseif for_ft isnot # 0
let maker = s :GetMakerForFiletype ( for_ft , a :maker_name )
else
call neomake #utils #load_global_makers ( )
let f = 'neomake#makers#' .a :maker_name .'#' .a :maker_name
if exists ( '*' .f )
let maker = call ( f , [])
endif
endif
endif
if type ( maker ) ! = type ( {})
throw printf ( 'Neomake: Got non-dict for maker %s: %s' ,
\ a :maker_name , maker )
endif
if maker isnot # s :unset_dict && ! has_key ( maker , 'name' )
let maker .name = a :maker_name
endif
return maker
endfunction
function ! neomake #GetMaker ( name_or_maker , ...) abort
let for_ft = a :0 ? a :1 : 0
if type ( a :name_or_maker ) = = type ( {})
let maker = a :name_or_maker
if ! has_key ( maker , 'name' )
let maker .name = 'unnamed_maker'
endif
else
let maker = neomake #get_maker_by_name ( a :name_or_maker , for_ft )
if maker is # s :unset_dict
if ! a :0
" Check &filetype if no args where provided.
let maker = neomake #get_maker_by_name ( a :name_or_maker , &filetype )
endif
endif
if maker is # s :unset_dict
if for_ft isnot # 0
throw printf ( 'Neomake: Maker not found (for %s): %s' ,
\ ! empty ( for_ft ) ? 'filetype ' .for_ft : 'empty filetype' ,
\ a :name_or_maker )
else
throw printf ( 'Neomake: Maker not found (without filetype): %s' ,
\ a :name_or_maker )
endif
endif
endif
return neomake #create_maker_object ( maker , a :0 ? a :1 : &filetype )
endfunction
" NOTE: uses ft and bufnr for config only.
function ! neomake #create_maker_object ( maker , ft ) abort
let [maker , ft , bufnr ] = [a :maker , a :ft , bufnr ( '%' ) ]
" Create the maker object.
let l :GetEntries = neomake #utils #GetSetting ( 'get_list_entries' , maker , -1 , ft , bufnr )
if GetEntries isnot # -1
let maker = copy ( maker )
let maker .get_list_entries = GetEntries
else
let maker = extend ( copy ( s :command_maker_base ) , copy ( maker ) )
endif
if ! has_key ( maker , 'get_list_entries' )
" Set defaults for command/job based makers.
let defaults = extend (
\ copy ( g :neomake #config #_defaults ['maker_defaults' ]) ,
\ neomake #config #get ( 'maker_defaults' ) )
call extend ( defaults , {
\ 'exe' : maker .name ,
\ 'args' : [],
\ })
if ! has_key ( maker , 'process_output' ) && ! has_key ( maker , 'process_json' )
call extend ( defaults , {
\ 'errorformat' : &errorformat ,
\ })
endif
for [key , default ] in items ( defaults )
let maker [key ] = neomake #utils #GetSetting ( key , {'name' : maker .name }, get ( maker , key , default ) , ft , bufnr , 1 )
unlet default " for Vim without patch-7.4.1546
endfor
" Check settings, without setting a default.
for key in ['cwd' ]
let setting = neomake #utils #GetSetting ( key , {'name' : maker .name }, get ( maker , key , s :unset ) , ft , bufnr , 1 )
if setting isnot s :unset
let maker [key ] = setting
endif
endfor
endif
2025-02-03 16:36:38 +08:00
if exists ( 's:hack_keep_refs_for_profiling' ) " might not exist when starting profiling later..!
2020-06-13 14:06:35 +08:00
call add ( s :hack_keep_refs_for_profiling , maker )
endif
return maker
endfunction
if exists ( '*getcompletion' )
function ! s :get_makers_for_pattern ( pattern ) abort
" Get function prefix based on pattern, until the first backslash.
let prefix = substitute ( a :pattern , '\v\\.*' , '' , '' )
" NOTE: the pattern uses &ignorecase.
let funcs = getcompletion ( prefix .'[a-z]' , 'function' )
call filter ( funcs , 'v:val =~# a:pattern' )
" Remove prefix.
call map ( funcs , 'v:val[' .len ( prefix ) .':]' )
" Only keep lowercase function names.
call filter ( funcs , "v:val =~# '\\m^[a-z].*('" )
" Remove parenthesis and #.* (for project makers).
return sort ( map ( funcs , "substitute(v:val, '\\v[(#].*', '', '')" ) )
endfunction
else
function ! s :get_makers_for_pattern ( pattern ) abort
let funcs_output = neomake #utils #redir ( 'fun /' .a :pattern )
return sort ( map ( split ( funcs_output , '\n' ) ,
\ "substitute(v:val, '\\v^.*#(.*)\\(.*$', '\\1', '')" ) )
endfunction
endif
function ! neomake #GetMakers ( ft ) abort
" Get all makers for a given filetype. This is used from completion.
" XXX: this should probably use a callback or some other more stable
" approach to get the list of makers (than looking at the lowercase
" functions)?!
let makers = []
" Do not use 'b:neomake_jsx_javascript_foo_maker' twice for
" ft=jsx.javascript.
let used_vars = []
for ft in neomake #utils #get_config_fts ( a :ft )
call neomake #utils #load_ft_makers ( ft )
" Add sorted list per filetype.
let add = []
let maker_names = s :get_makers_for_pattern ( 'neomake#makers#ft#' .ft .'#\l' )
for maker_name in maker_names
if index ( makers , maker_name ) = = -1 && index ( add , maker_name ) = = -1
let add + = [maker_name ]
endif
endfor
" Get makers from g:/b: variables.
for v in sort ( extend ( keys ( g :) , keys ( b :) ) )
if index ( used_vars , v ) ! = -1
continue
endif
let maker_name = matchstr ( v , '\v^neomake_' .ft .'_\zs[0-9a-z_]+\ze_maker$' )
if ! empty ( maker_name )
\ && index ( makers , maker_name ) = = -1
\ && index ( add , maker_name ) = = -1
let used_vars + = [v ]
let add + = [maker_name ]
endif
endfor
" Get makers from new-style config.
for [maker_name , val ] in items ( neomake #config #get ( 'ft.' .ft ) )
if has_key ( val , 'maker' )
\ && index ( makers , maker_name ) = = -1
\ && index ( add , maker_name ) = = -1
let add + = [maker_name ]
endif
endfor
call sort ( add )
call extend ( makers , add )
endfor
return makers
endfunction
function ! neomake #GetProjectMakers ( ) abort
call neomake #utils #load_global_makers ( )
return s :get_makers_for_pattern ( 'neomake#makers#\(ft#\)\@!\l' )
endfunction
function ! neomake #GetEnabledMakers ( ...) abort
let file_mode = a :0
if ! file_mode
" If we have no filetype, use the global default makers.
" This variable is also used for project jobs, so it has no
" buffer local ('b:') counterpart for now.
let enabled_makers = copy ( get ( g :, 'neomake_enabled_makers' , []) )
if empty ( enabled_makers )
let makeprg_maker = s :get_makeprg_maker ( )
if ! empty ( makeprg_maker )
let makeprg_maker = neomake #GetMaker ( makeprg_maker )
let makeprg_maker .auto_enabled = 1
let enabled_makers = [makeprg_maker ]
endif
else
call map ( enabled_makers , "extend(neomake#GetMaker(v:val),
\ {'auto_enabled' : 0 }, 'error' ) ")
endif
else
let enabled_makers = []
let bufnr = bufnr ( '%' )
let makers = neomake #utils #GetSetting ( 'enabled_makers' , {}, s :unset_list , a :1 , bufnr )
if makers is # s :unset_list
let auto_enabled = 1
for config_ft in neomake #utils #get_config_fts ( a :1 )
call neomake #utils #load_ft_makers ( config_ft )
let fnname = 'neomake#makers#ft#' .config_ft .'#EnabledMakers'
if exists ( '*' .fnname )
try
let makers = call ( fnname , [])
catch /^Vim(let):E119:/ " Not enough arguments for function
let makers = call ( fnname , [{'file_mode' : file_mode , 'bufnr' : bufnr }])
endtry
break
endif
endfor
else
let auto_enabled = 0
endif
let enabled_makers = neomake #map_makers ( makers , a :1 , auto_enabled )
endif
return enabled_makers
endfunction
" a:1: override "open_list" setting.
function ! s :HandleLoclistQflistDisplay ( jobinfo , loc_or_qflist , ...) abort
let open_list_default = a :0 ? a :1 : 0
let open_val = neomake #utils #GetSetting ( 'open_list' , a :jobinfo .maker , open_list_default , a :jobinfo .ft , a :jobinfo .bufnr )
if ! open_val
return
endif
let height = neomake #utils #GetSetting ( 'list_height' , a :jobinfo .maker , 10 , a :jobinfo .ft , a :jobinfo .bufnr )
if ! height
return
endif
let height = min ( [len ( a :loc_or_qflist ) , height ])
if a :jobinfo .file_mode
call neomake #log #debug ( 'Handling location list: executing lwindow.' , a :jobinfo )
let cmd = 'lwindow'
else
call neomake #log #debug ( 'Handling quickfix list: executing cwindow.' , a :jobinfo )
let cmd = 'botright cwindow'
endif
if open_val = = 2
let make_id = a :jobinfo .make_id
let make_info = s :make_info [make_id ]
let g :neomake #core #_ignore_autocommands + = 1
try
call neomake #compat #save_prev_windows ( )
let win_count = winnr ( '$' )
exe cmd height
let new_win_count = winnr ( '$' )
if win_count = = new_win_count
" No new window, adjust height eventually.
let found = 0
if get ( make_info , '_did_lwindow' , 0 )
for w in range ( 1 , winnr ( '$' ) )
if getwinvar ( w , 'neomake_window_for_make_id' ) = = make_id
let found = w
break
endif
endfor
if found
let cmd = printf ( '%dresize %d' , found , height )
if winheight ( found ) ! = height
call neomake #log #debug ( printf (
\ 'Resizing existing quickfix window: %s.' ,
\ cmd ) , a :jobinfo )
exe cmd
endif
else
call neomake #log #debug (
\ 'Could not find corresponding quickfix window.' ,
\ a :jobinfo )
endif
endif
elseif new_win_count > win_count
if &filetype ! = # 'qf'
call neomake #log #debug ( printf (
\ 'WARN: unexpected filetype for new window: %s' ,
\ &filetype ) , a :jobinfo )
else
call neomake #log #debug ( printf (
\ 'list window has been opened (old count: %d, new count: %d, height: %d).' ,
\ win_count , new_win_count , winheight ( 0 ) ) , a :jobinfo )
let w :neomake_window_for_make_id = a :jobinfo .make_id
endif
else
call neomake #log #debug ( printf (
\ 'list window has been closed (old count: %d, new count: %d).' ,
\ win_count , new_win_count ) , a :jobinfo )
endif
call neomake #compat #restore_prev_windows ( )
let make_info ._did_lwindow = 1
finally
let g :neomake #core #_ignore_autocommands - = 1
endtry
else
exe cmd height
endif
endfunction
" Experimental/private wrapper.
function ! neomake #_handle_list_display ( jobinfo , ...) abort
if a :0
let list = a :1
else
let list = a :jobinfo .file_mode ? getloclist ( 0 ) : getqflist ( )
endif
call s :HandleLoclistQflistDisplay ( a :jobinfo , list , 2 )
endfunction
" Get a maker for &makeprg.
" This could be cached, but needs to take into account / set &errorformat,
" and other settings that are handled by neomake#GetMaker.
function ! s :get_makeprg_maker ( ) abort
if empty ( &makeprg )
return {}
elseif &makeprg = ~ # '\s'
let maker = neomake #utils #MakerFromCommand ( &makeprg )
else
let maker = neomake #utils #MakerFromCommand ( [&makeprg ])
endif
let maker .name = 'makeprg'
" Do not append file. &makeprg should contain %/# for this instead.
let maker .append_file = 0
return neomake #GetMaker ( maker )
endfunction
function ! s :Make ( options ) abort
let is_automake = get ( a :options , 'automake' , ! empty ( expand ( '<abuf>' ) ) )
if is_automake
if g :neomake #core #_ignore_autocommands
call neomake #log #debug ( printf (
\ 'Ignoring Make through autocommand due to ignore_autocommands=%d.' , g :neomake #core #_ignore_autocommands ) , {'winnr' : winnr ( ) })
return []
endif
let disabled = neomake #config #get_with_source ( 'disabled' , 0 )
if disabled [0 ]
call neomake #log #debug ( printf (
\ 'Make through autocommand disabled via %s.' , disabled [1 ]) )
return []
endif
endif
let s :make_id + = 1
let make_id = s :make_id
let options = extend ( copy ( a :options ) , {
\ 'file_mode' : 1 ,
\ 'ft' : &filetype ,
\ }, 'keep' )
let options .make_id = make_id " Deprecated.
let file_mode = options .file_mode
" Require winid/winnr with non-current buffer in file_mode.
if has_key ( options , 'bufnr' )
if options .bufnr ! = bufnr ( '%' )
if ! has_key ( options , 'winid' ) && ! has_key ( options , 'winnr' )
throw 'Neomake: winid or winnr are required for non-current buffer.'
endif
endif
if ! bufexists ( options .bufnr )
throw printf ( 'Neomake: buffer %d does not exist.' , options .bufnr )
endif
else
let options .bufnr = bufnr ( '%' )
endif
" Validate winid/winnr (required for location list windows).
let file_mode_win = 0
if file_mode
if has_key ( options , 'winid' )
if win_id2tabwin ( options .winid ) = = [0 , 0 ]
throw printf ( 'Neomake: window id %d does not exist.' , options .winid )
endif
let file_mode_win = options .winid
elseif has_key ( options , 'winnr' )
if winbufnr ( options .winnr ) = = -1
throw printf ( 'Neomake: window %d does not exist.' , options .winnr )
endif
let file_mode_win = options .winnr
elseif exists ( '*win_getid' )
let options .winid = win_getid ( )
endif
elseif has_key ( options , 'winid' )
throw 'Neomake: do not use winid with file_mode=0.'
elseif has_key ( options , 'winnr' )
throw 'Neomake: do not use winnr with file_mode=0.'
endif
lockvar 1 options
let s :make_info [make_id ] = {
\ 'make_id' : make_id ,
\ 'cwd' : getcwd ( ) ,
\ 'verbosity' : get ( g :, 'neomake_verbose' , 1 ) ,
\ 'active_jobs' : [],
\ 'finished_jobs' : [],
\ 'options' : options ,
\ }
let make_info = s :make_info [make_id ]
let bufnr = options .bufnr
if &verbose
let make_info .verbosity + = &verbose
call neomake #log #debug ( printf (
\ 'Adding &verbose (%d) to verbosity level: %d.' ,
\ &verbose , make_info .verbosity ) , make_info )
endif
if make_info .verbosity > = 3
call neomake #log #debug ( printf (
\ 'Calling Make with options %s.' ,
\ string ( filter ( copy ( options ) , "index(['bufnr', 'make_id'], v:key) == -1" ) ) ) , {'make_id' : make_id , 'bufnr' : bufnr })
endif
" Use pre-compiled jobs (used with automake).
if has_key ( options , 'jobs' )
let jobs = map ( copy ( options .jobs ) , "extend(v:val, {'make_id': make_id})" )
else
if has_key ( options , 'enabled_makers' )
if file_mode
let makers = neomake #map_makers ( options .enabled_makers , options .ft , 0 )
else
let makers = neomake #map_makers ( options .enabled_makers , -1 , 0 )
endif
else
let makers = call ( 'neomake#GetEnabledMakers' , file_mode ? [options .ft ] : [])
if empty ( makers )
if file_mode
let msg = printf ( 'Nothing to make: no enabled file mode makers (filetype=%s).' , options .ft )
if is_automake
call neomake #log #debug ( msg , make_info )
else
call neomake #log #warning ( msg , make_info )
endif
unlet s :make_info [make_id ]
return []
endif
endif
endif
let job_options = copy ( options )
let job_options .make_id = make_id " Used for logging.
let jobs = neomake #core #create_jobs ( job_options , makers )
endif
if empty ( jobs )
call neomake #log #debug ( 'Nothing to make: no valid makers.' , make_info )
call s :clean_make_info ( make_info )
return []
endif
let make_info .jobs = copy ( jobs )
let maker_info = join ( map ( copy ( jobs ) ,
\ "v:val.maker.name . (get(v:val.maker, 'auto_enabled', 0) ? ' (auto)' : '')" ) , ', ' )
call neomake #log #debug ( printf ( 'Running makers: %s.' , maker_info ) , make_info )
let make_info .jobs_queue = jobs
if file_mode
" XXX: this clears counts for job's buffer only, but we add counts for
" the entry's buffers, which might be different!
call neomake #statusline #ResetCountsForBuf ( bufnr )
if g :neomake_place_signs
call neomake #signs #Reset ( bufnr , 'file' )
endif
else
call neomake #statusline #ResetCountsForProject ( )
if g :neomake_place_signs
call neomake #signs #ResetProject ( )
endif
endif
" Store make_id on window (used to find window for location lists (without
" winid, but also used to check the current window via w: directly)).
if file_mode
call setwinvar ( file_mode_win , 'neomake_make_ids' ,
\ neomake #compat #getwinvar ( file_mode_win , 'neomake_make_ids' , []) + [make_id ])
endif
let use_list = get ( options , 'use_list' , 1 )
if use_list
let any_job_uses_list = 0
for job in jobs
if get ( job .maker , 'use_list' , 1 )
let any_job_uses_list = 1
break
endif
endfor
if ! any_job_uses_list
let use_list = 0
endif
endif
if use_list
let make_info .entries_list = neomake #list #ListForMake ( make_info )
" Reuse existing location list window with automake.
if is_automake && has ( 'patch-7.4.2200' )
if file_mode
let title = get ( getloclist ( 0 , {'title' : 1 }) , 'title' )
else
let title = get ( getqflist ( {'title' : 1 }) , 'title' )
endif
if title = ~ # '\V\^Neomake[auto]'
let make_info .entries_list .reset_existing_qflist = 1
endif
endif
endif
" Cancel any already running jobs for the makers from these jobs.
if ! empty ( s :jobs )
" @vimlint(EVL102, 1, l:job)
for job in jobs
let running_already = values ( filter ( copy ( s :jobs ) ,
\ '(v:val.maker.name == job.maker.name'
\ .' || (!is_automake && get(v:val, "automake", 0)))'
\ .' && v:val.bufnr == job.bufnr'
\ .' && v:val.file_mode == job.file_mode'
\ ." && !get(v:val, 'canceled')" ) )
if ! empty ( running_already )
for running_job in running_already
call neomake #log #info ( printf (
\ 'Canceling already running job (%d.%d) for the same maker.' ,
\ running_job .make_id , running_job .id ) , {'make_id' : make_id })
call neomake #CancelJob ( running_job .id , 1 )
endfor
endif
endfor
endif
" Update automake tick (used to skip unchanged buffers).
if is_automake
call neomake #configure #_update_automake_tick ( bufnr , options .ft )
endif
" Start all jobs in the queue (until serialized).
let jobinfos = []
while 1
if empty ( make_info .jobs_queue )
break
endif
let jobinfo = s :handle_next_job ( {})
if empty ( jobinfo )
break
endif
call add ( jobinfos , jobinfo )
if jobinfo .serialize
let make_info .serializing_for_job = jobinfo .id
" Break and continue through exit handler.
break
endif
endwhile
return jobinfos
endfunction
function ! s :AddExprCallback ( jobinfo , lines ) abort
if s :need_to_postpone_loclist ( a :jobinfo )
return neomake #action_queue #add ( ['BufEnter' , 'WinEnter' ], [s :function ( 's:AddExprCallback' ) ,
\ [a :jobinfo , a :lines ] + a :000 ])
endif
" Create location/quickfix list and add lines to it.
let cd_error = a :jobinfo .cd ( )
if ! empty ( cd_error )
call neomake #log #debug ( printf (
\ "Could not change to job's cwd (%s): %s." ,
\ a :jobinfo .cd_from_setting , cd_error ) , a :jobinfo )
endif
let make_list = s :make_info [a :jobinfo .make_id ].entries_list
let prev_list = copy ( make_list .entries )
let added_entries = make_list .add_lines_with_efm ( a :lines , a :jobinfo )
return s :ProcessEntries ( a :jobinfo , added_entries , prev_list )
endfunction
function ! s :CleanJobinfo ( jobinfo , ...) abort
if get ( a :jobinfo , '_in_exit_handler' , 0 )
" Do not clean job yet.
return
endif
if ! empty ( a :jobinfo .pending_output ) && ! get ( a :jobinfo , 'canceled' , 0 )
call neomake #log #debug (
\ 'Output left to be processed, not cleaning job yet.' , a :jobinfo )
return g :neomake #action_queue #not_processed
endif
let queued_actions = neomake #action_queue #get_queued_actions ( a :jobinfo )
if ! empty ( queued_actions )
call neomake #log #debug ( printf (
\ 'Skipping cleaning of job info because of queued actions: %s.' ,
\ join ( queued_actions , ', ' ) ) , a :jobinfo )
return neomake #action_queue #add ( ['WinEnter' ], [s :function ( 's:CleanJobinfo' ) , [a :jobinfo ]])
endif
call neomake #log #debug ( 'Cleaning jobinfo.' , a :jobinfo )
let a :jobinfo .finished = 1
if ! has_key ( s :make_info , a :jobinfo .make_id )
return g :neomake #action_queue #processed
endif
let make_info = s :make_info [a :jobinfo .make_id ]
if has_key ( s :jobs , get ( a :jobinfo , 'id' , -1 ) )
call remove ( s :jobs , a :jobinfo .id )
call filter ( s :map_job_ids , 'v:val != a:jobinfo.id' )
endif
if exists ( 's:kill_vim_timers' )
for [timer , job ] in items ( s :kill_vim_timers )
if job = = a :jobinfo
call timer_stop ( + timer )
unlet s :kill_vim_timers [timer ]
break
endif
endfor
endif
if ! get ( a :jobinfo , 'canceled' , 0 )
\ && ! get ( a :jobinfo , 'failed_to_start' , 0 )
let make_info .finished_jobs + = [a :jobinfo ]
call neomake #utils #hook ( 'NeomakeJobFinished' , {'jobinfo' : a :jobinfo })
endif
call filter ( make_info .active_jobs , 'v:val != a:jobinfo' )
" Trigger cleanup (and autocommands) if all jobs have finished.
if empty ( make_info .active_jobs ) && empty ( make_info .jobs_queue )
call s :clean_make_info ( make_info )
endif
return g :neomake #action_queue #processed
endfunction
function ! s :clean_make_info ( make_info , ...) abort
let make_id = a :make_info .make_id
let bang = a :0 ? a :1 : 0
if ! bang && ! empty ( a :make_info .active_jobs )
call neomake #log #debug ( printf (
\ 'Skipping cleaning of make info: %d active jobs: %s.' ,
\ len ( a :make_info .active_jobs ) ,
\ string ( map ( copy ( a :make_info .active_jobs ) , 'v:val.as_string()' ) ) ) ,
\ a :make_info )
return
endif
" Queue cleanup in case of queued actions, e.g. NeomakeJobFinished hook.
let queued = []
for [_ , v ] in g :neomake #action_queue #_s .action_queue
if has_key ( v [1 ][0 ], 'id' )
let jobinfo = v [1 ][0 ]
if jobinfo .make_id = = make_id
let queued + = ['job ' .jobinfo .id ]
endif
else
if v [1 ][0 ] = = a :make_info
let queued + = ['make ' .make_id ]
endif
endif
endfor
if ! empty ( queued )
call neomake #log #debug ( printf ( 'Queuing clean_make_info for already queued actions: %s' , string ( queued ) ) )
return neomake #action_queue #add (
\ g :neomake #action_queue #any_event ,
\ [s :function ( 's:clean_make_info' ) , [a :make_info ]])
endif
if exists ( '*neomake#statusline#make_finished' )
call neomake #statusline #make_finished ( a :make_info )
endif
if ! empty ( a :make_info .finished_jobs )
" Clean old signs after all jobs have finished, so that they can be
" reused, avoiding flicker and keeping them for longer in general.
if g :neomake_place_signs
if a :make_info .options .file_mode
call neomake #signs #CleanOldSigns ( a :make_info .options .bufnr , 'file' )
else
call neomake #signs #CleanAllOldSigns ( 'project' )
endif
endif
call s :clean_for_new_make ( a :make_info )
if exists ( '#neomake' )
call neomake #EchoCurrentError ( 1 )
call neomake #virtualtext #handle_current_error ( )
endif
if get ( a :make_info , 'canceled' , 0 )
call neomake #log #debug ( 'Skipping final processing for canceled make.' , a :make_info )
call s :do_clean_make_info ( a :make_info )
elseif has_key ( a :make_info , 'entries_list' ) " use_list option
return s :handle_locqf_list_for_finished_jobs ( a :make_info )
else
call s :handle_finished_make ( a :make_info )
endif
else
call s :do_clean_make_info ( a :make_info )
endif
return g :neomake #action_queue #processed
endfunction
function ! s :do_clean_make_info ( make_info ) abort
call neomake #log #debug ( 'Cleaning make info.' , a :make_info )
let make_id = a :make_info .make_id
" Remove make_id from its window.
let [t , w ] = neomake #core #get_tabwin_for_makeid ( make_id )
if [t , w ] ! = [-1 , -1 ]
let make_ids = neomake #compat #gettabwinvar ( t , w , 'neomake_make_ids' , [])
let idx = index ( make_ids , make_id )
if idx ! = -1
call remove ( make_ids , idx )
call settabwinvar ( t , w , 'neomake_make_ids' , make_ids )
endif
endif
" Clean up temporary files and buffers.
let wipe_unlisted_buffers = get ( a :make_info , '_wipe_unlisted_buffers' , [])
let tempfiles = get ( a :make_info , 'tempfiles' )
if ! empty ( tempfiles )
for tempfile in tempfiles
let delete_ret = delete ( tempfile )
if delete_ret = = 0
call neomake #log #debug ( printf ( 'Removing temporary file: "%s".' ,
\ tempfile ) )
else
call neomake #log #warning ( printf ( 'Failed to remove temporary file: "%s" (%d).' ,
\ tempfile , delete_ret ) )
endif
let bufnr_tempfile = bufnr ( tempfile )
if bufnr_tempfile ! = -1 && ! buflisted ( bufnr_tempfile )
let wipe_unlisted_buffers + = [bufnr_tempfile ]
endif
endfor
" Only delete the dir, if Vim supports it.
if v :version > = 705 | | ( v :version = = 704 && has ( 'patch1107' ) )
for dir in reverse ( copy ( get ( a :make_info , 'created_dirs' ) ) )
call delete ( dir , 'd' )
endfor
endif
endif
if ! empty ( wipe_unlisted_buffers )
if ! empty ( wipe_unlisted_buffers )
call neomake #compat #uniq ( sort ( wipe_unlisted_buffers ) )
endif
call neomake #log #debug ( printf ( 'Wiping out %d unlisted/remapped buffers: %s.' ,
\ len ( wipe_unlisted_buffers ) ,
\ string ( wipe_unlisted_buffers ) ) )
" NOTE: needs to be silent with more than a single buffer.
exe 'silent bwipeout ' .join ( wipe_unlisted_buffers )
endif
let buf_prev_makes = getbufvar ( a :make_info .options .bufnr , '_neomake_automake_make_ids' )
if ! empty ( buf_prev_makes )
call filter ( buf_prev_makes , 'v:val != make_id' )
call setbufvar ( a :make_info .options .bufnr , '_neomake_automake_make_ids' , buf_prev_makes )
endif
unlet s :make_info [make_id ]
endfunction
function ! s :handle_locqf_list_for_finished_jobs ( make_info ) abort
let file_mode = a :make_info .options .file_mode
let create_list = a :make_info .entries_list .need_init
let open_val = get ( g :, 'neomake_open_list' , 0 )
let height = open_val ? get ( g :, 'neomake_list_height' , 10 ) : 0
if height
let close_list = create_list | | empty ( file_mode ? getloclist ( 0 ) : getqflist ( ) )
else
let close_list = 0
endif
if file_mode
if create_list && ! bufexists ( a :make_info .options .bufnr )
call neomake #log #info ( 'No buffer found for location list!' , a :make_info )
let create_list = 0
let close_list = 0
elseif ( create_list | | close_list )
if index ( get ( w :, 'neomake_make_ids' , []) , a :make_info .make_id ) = = -1
call neomake #log #debug (
\ 'Postponing final location list handling (in another window).' ,
\ a :make_info )
return neomake #action_queue #add ( ['WinEnter' ], [s :function ( 's:handle_locqf_list_for_finished_jobs' ) ,
\ [a :make_info ] + a :000 ])
endif
" TODO: merge with s:need_to_postpone_output_processing.
if neomake #compat #in_completion ( )
call neomake #log #debug (
\ 'Postponing final location list handling during completion.' ,
\ a :make_info )
return neomake #action_queue #add ( ['CompleteDone' ], [s :function ( 's:handle_locqf_list_for_finished_jobs' ) ,
\ [a :make_info ] + a :000 ])
endif
let mode = neomake #compat #get_mode ( )
if index ( ['n' , 'i' ], mode ) = = -1
call neomake #log #debug ( printf (
\ 'Postponing final location list handling for mode "%s".' , mode ) ,
\ a :make_info )
return neomake #action_queue #add ( ['CursorHold' , 'WinEnter' ], [s :function ( 's:handle_locqf_list_for_finished_jobs' ) ,
\ [a :make_info ] + a :000 ])
endif
endif
endif
" Update list title.
" This has to be done currently by itself to reflect running/finished
" state properly.
if create_list | | ! a :make_info .entries_list .need_init
if has_key ( a :make_info , 'entries_list' )
call a :make_info .entries_list .finish_for_make ( )
endif
endif
" Close empty list.
if close_list
if file_mode
call neomake #log #debug ( 'Handling location list: executing lclose.' , {'winnr' : winnr ( ) })
lclose
else
call neomake #log #debug ( 'Handling quickfix list: executing cclose.' )
cclose
endif
endif
call s :handle_finished_make ( a :make_info )
return g :neomake #action_queue #processed
endfunction
function ! s :handle_finished_make ( make_info ) abort
let hook_context = {
\ 'make_info' : a :make_info ,
\ 'make_id' : a :make_info .make_id ,
\ 'options' : a :make_info .options ,
\ 'finished_jobs' : a :make_info .finished_jobs ,
\ }
call neomake #utils #hook ( 'NeomakeFinished' , hook_context )
call neomake #configure #_reset_automake_cancelations ( a :make_info .options .bufnr )
call s :do_clean_make_info ( a :make_info )
endfunction
function ! neomake #VimLeave ( ) abort
call neomake #log #debug ( 'Calling VimLeave.' )
for make_id in keys ( s :make_info )
call neomake #CancelMake ( make_id )
endfor
endfunction
function ! s :clean_for_new_make ( make_info ) abort
if get ( a :make_info , 'cleaned_for_make' , 0 )
return
endif
" XXX: needs to handle buffers for list entries?!
" See "get_list_entries: minimal example (from doc)" in
" tests/makers.vader.
call neomake #_clean_errors ( extend ( copy ( a :make_info .options ) , {'make_id' : a :make_info .make_id }) )
let a :make_info .cleaned_for_make = 1
endfunction
" a:context: dictionary with keys:
" - file_mode
" - bufnr (required for file_mode)
" - make_id (used for logging)
function ! neomake #_clean_errors ( context ) abort
if a :context .file_mode
let bufnr = a :context .bufnr
if has_key ( s :current_errors ['file' ], bufnr )
unlet s :current_errors ['file' ][bufnr ]
endif
call neomake #highlights #ResetFile ( bufnr )
call neomake #log #debug ( 'File-level errors cleaned.' , a :context )
else
let s :current_errors ['project' ] = {}
call neomake #highlights #ResetProject ( )
call neomake #log #debug ( 'Project-level errors cleaned.' , a :context )
endif
endfunction
" Change to a job's cwd, if any.
" Returns: a list:
" - error (empty for success)
" - directory changed into (empty if skipped)
" - command to change back to the current workding dir (might be empty)
" Call a:fn with a:args and queue it, in case if fails with E48/E523.
function ! s :pcall ( fn , args ) abort
let jobinfo = a :args [0 ]
try
return call ( a :fn , a :args + [1 ])
catch /^\%(Vim\%((\a\+)\)\=:\%(E48\|E523\)\)/ " only E48/E523 (sandbox / not allowed here)
call neomake #log #debug ( 'Error during pcall: ' .v :exception .'.' , jobinfo )
call neomake #log #debug ( printf ( '(in %s)' , v :throwpoint ) , jobinfo )
" Might throw in case of X failed attempts.
call neomake #action_queue #add ( ['Timer' , 'WinEnter' ], [s :function ( a :fn ) , a :args ])
endtry
return g :neomake #action_queue #not_processed
endfunction
function ! s :ProcessEntries ( jobinfo , entries , ...) abort
if empty ( a :entries )
return
endif
if get ( a :jobinfo , 'canceled' )
return
endif
if s :need_to_postpone_loclist ( a :jobinfo )
return neomake #action_queue #add ( ['BufEnter' , 'WinEnter' ], [s :function ( 's:ProcessEntries' ) ,
\ [a :jobinfo , a :entries ] + a :000 ])
endif
if ! a :0 | | type ( a :[len ( a :000 ) ]) ! = 0
return s :pcall ( 's:ProcessEntries' , [a :jobinfo , a :entries ] + a :000 )
endif
let file_mode = a :jobinfo .file_mode
call neomake #log #debug ( printf (
\ 'Processing %d entries.' , len ( a :entries ) ) , a :jobinfo )
let make_info = s :make_info [a :jobinfo .make_id ]
let make_list = make_info .entries_list
let maker_name = a :jobinfo .maker .name
if a :0 > 1
" Via errorformat processing, where the list has been set already.
let prev_list = a :1
let parsed_entries = a :entries
else
" Fix entries with get_list_entries/process_output/process_json.
" @vimlint(EVL102, 1, l:default_type)
let default_type = neomake #utils #GetSetting ( 'default_entry_type' , a :jobinfo .maker , 'W' , a :jobinfo .ft , a :jobinfo .bufnr )
call map ( a :entries , 'extend(v:val, {'
\ . "'bufnr': str2nr(get(v:val, 'bufnr', 0)),"
\ . "'lnum': str2nr(get(v:val, 'lnum', 0)),"
\ . "'col': str2nr(get(v:val, 'col', 0)),"
\ . "'vcol': str2nr(get(v:val, 'vcol', 0)),"
\ . "'type': get(v:val, 'type', default_type),"
\ . "'nr': get(v:val, 'nr', has_key(v:val, 'text') ? -1 : 0),"
\ . "'text': get(v:val, 'text', ''),"
\ . '})' )
let cd_error = a :jobinfo .cd ( )
if ! empty ( cd_error )
call neomake #log #debug ( printf (
\ "Could not change to job's cwd (%s): %s." ,
\ a :jobinfo .cd_from_setting , cd_error ) , a :jobinfo )
endif
let prev_list = file_mode ? getloclist ( 0 ) : getqflist ( )
try
let parsed_entries = make_list .add_entries_for_job ( a :entries , a :jobinfo )
if exists ( ':Assert' ) && ! empty ( a :entries )
Assert get ( a :entries [0 ], 'text' , '' ) ! ~ # 'nmcfg:'
endif
finally
call a :jobinfo .cd_back ( )
endtry
endif
call s :clean_for_new_make ( make_info )
let counts_changed = 0
let maker_type = file_mode ? 'file' : 'project'
let do_highlight = get ( g :, 'neomake_highlight_columns' , 1 )
\ | | get ( g :, 'neomake_highlight_lines' , 0 )
let signs_by_bufnr = {}
let debug = neomake #utils #get_verbosity ( a :jobinfo ) > = 3 | | ! empty ( get ( g :, 'neomake_logfile' ) ) | | s :is_testing
let entries_with_lnum_by_bufnr = {}
let skipped_without_bufnr = []
let skipped_without_lnum = []
let idx = -1
for entry in parsed_entries
let idx + = 1
if ! file_mode
if neomake #statusline #AddQflistCount ( entry )
let counts_changed = 1
endif
endif
if ! entry .bufnr
if debug
let skipped_without_bufnr + = [idx ]
endif
continue
endif
if file_mode
if neomake #statusline #AddLoclistCount ( entry .bufnr , entry )
let counts_changed = 1
endif
endif
if ! entry .lnum
if debug
let skipped_without_lnum + = [idx ]
endif
continue
endif
if ! has_key ( entries_with_lnum_by_bufnr , entry .bufnr )
let entries_with_lnum_by_bufnr [entry .bufnr ] = []
let signs_by_bufnr [entry .bufnr ] = []
endif
if do_highlight | | g :neomake_place_signs
" NOTE: only lnum/type required for signs. Similar for do_highlight?!
call add ( entries_with_lnum_by_bufnr [entry .bufnr ], entry )
endif
" Track all errors by buffer and line
let entry .maker_name = maker_name
call neomake #_add_error ( maker_type , entry )
endfor
" Handle placing signs and highlights.
for [b , entries ] in items ( entries_with_lnum_by_bufnr )
if g :neomake_place_signs
call neomake #signs #PlaceSigns ( b , entries , maker_type )
endif
if do_highlight
for entry in entries
call neomake #highlights #AddHighlight ( entry , maker_type )
endfor
endif
endfor
if ! empty ( skipped_without_bufnr )
call neomake #log #debug ( printf ( 'Skipped %d entries without bufnr: %s.' ,
\ len ( skipped_without_bufnr ) ,
\ string ( map ( skipped_without_bufnr , 'a:entries[v:val]' ) ) ) , a :jobinfo )
endif
if ! empty ( skipped_without_lnum )
call neomake #log #debug ( printf (
\ 'Could not place signs for %d entries without line number: %s.' ,
\ len ( skipped_without_lnum ) ,
\ string ( map ( skipped_without_lnum , 'a:entries[v:val]' ) ) ) , a :jobinfo )
endif
let new_list = make_list .entries
if ! counts_changed
let counts_changed = new_list ! = prev_list
endif
if counts_changed
call neomake #utils #hook ( 'NeomakeCountsChanged' , {'reset' : 0 , 'jobinfo' : a :jobinfo })
endif
if has_key ( a :jobinfo , '_delayed_qf_autocmd' ) && exists ( '#QuickfixCmdPost' )
" NOTE: need to use :silent, since we can only check the event, but
" not the pattern - `exists()` for 'laddexpr' will not match '*'.
silent call neomake #compat #doautocmd ( a :jobinfo ._delayed_qf_autocmd )
unlet a :jobinfo ._delayed_qf_autocmd
endif
if ! empty ( new_list )
call s :HandleLoclistQflistDisplay ( a :jobinfo , new_list )
endif
call neomake #highlights #ShowHighlights ( )
return g :neomake #action_queue #processed
endfunction
function ! s :ProcessJobOutput ( jobinfo , lines , source , ...) abort
if s :need_to_postpone_loclist ( a :jobinfo )
return neomake #action_queue #add ( ['BufEnter' , 'WinEnter' ], [s :function ( 's:ProcessJobOutput' ) ,
\ [a :jobinfo , a :lines , a :source ]])
endif
if ! a :0
return s :pcall ( 's:ProcessJobOutput' , [a :jobinfo , a :lines , a :source ])
endif
let maker = a :jobinfo .maker
call neomake #log #debug ( printf ( 'Processing %d lines of output.' ,
\ len ( a :lines ) ) , a :jobinfo )
let cd_error = a :jobinfo .cd ( )
if ! empty ( cd_error )
call neomake #log #debug ( printf (
\ "Could not change to job's cwd (%s): %s." ,
\ a :jobinfo .cd_from_setting , cd_error ) , a :jobinfo )
endif
try
if has_key ( maker , 'process_json' ) | | has_key ( maker , 'process_output' )
if has_key ( maker , 'process_json' )
let method = 'process_json'
let output = join ( a :lines , "\n" )
try
let json = neomake #compat #json_decode ( output )
catch
let error = printf (
\ 'Failed to decode JSON: %s (output: %s).' ,
\ substitute ( v :exception , '^Neomake: ' , '' , '' ) , string ( output ) )
call neomake #log #exception ( error , a :jobinfo )
return g :neomake #action_queue #not_processed
endtry
call neomake #log #debug ( printf (
\ "Calling maker's process_json method with %d JSON entries." ,
\ len ( json ) ) , a :jobinfo )
let entries = call ( maker .process_json , [{
\ 'json' : json ,
\ 'source' : a :source ,
\ 'jobinfo' : a :jobinfo }], maker )
else
call neomake #log #debug ( printf (
\ "Calling maker's process_output method with %d lines of output on %s." ,
\ len ( a :lines ) , a :source ) , a :jobinfo )
let method = 'process_output'
let entries = call ( maker .process_output , [{
\ 'output' : a :lines ,
\ 'source' : a :source ,
\ 'jobinfo' : a :jobinfo }], maker )
endif
if type ( entries ) ! = type ( [])
call neomake #log #error ( printf ( 'The %s method for maker %s did not return a list, but: %s.' ,
\ method , maker .name , string ( entries ) [:100 ]) , a :jobinfo )
return g :neomake #action_queue #not_processed
elseif ! empty ( entries ) && type ( entries [0 ]) ! = type ( {})
call neomake #log #error ( printf ( 'The %s method for maker %s did not return a list of dicts, but: %s.' ,
\ method , maker .name , string ( entries ) [:100 ]) , a :jobinfo )
return g :neomake #action_queue #not_processed
endif
return s :ProcessEntries ( a :jobinfo , entries )
endif
" Old-school handling through errorformat.
if has_key ( maker , 'mapexpr' )
let neomake_bufname = fnamemodify ( bufname ( a :jobinfo .bufnr ) , ':p' )
" @vimlint(EVL102, 1, l:neomake_bufdir)
let neomake_bufdir = fnamemodify ( neomake_bufname , ':h' )
" @vimlint(EVL102, 1, l:neomake_output_source)
let neomake_output_source = a :source
call map ( a :lines , maker .mapexpr )
endif
if ! empty ( a :lines )
call s :AddExprCallback ( a :jobinfo , a :lines )
endif
catch /^\%(Vim\%((\a\+)\)\=:\%(E48\|E523\)\)\@!/ " everything, but E48/E523 (sandbox / not allowed here)
if v :exception = = # 'NeomakeTestsException'
throw v :exception
endif
call neomake #log #exception ( printf (
\ 'Error during output processing for %s: %s.' ,
\ a :jobinfo .maker .name , v :exception ) , a :jobinfo )
return
finally
call a :jobinfo .cd_back ( )
endtry
return g :neomake #action_queue #processed
endfunction
function ! s :process_pending_output ( jobinfo , lines , source , ...) abort
let retry_events = s :need_to_postpone_output_processing ( a :jobinfo )
if empty ( retry_events )
let retry_events = s :ProcessPendingOutput ( a :jobinfo , a :lines , a :source )
if empty ( retry_events )
return g :neomake #action_queue #processed
endif
endif
if ! a :0
" Remember pending output, but only when not called via action queue.
call add ( a :jobinfo .pending_output , [a :lines , a :source ])
endif
if index ( neomake #action_queue #get_queued_actions ( a :jobinfo ) ,
\ ['process_pending_output' , retry_events ]) = = -1
return neomake #action_queue #add ( retry_events , [s :function ( 's:process_pending_output' ) , [a :jobinfo , [], a :source , retry_events ]])
endif
return g :neomake #action_queue #not_processed
endfunction
function ! s :ProcessPendingOutput ( jobinfo , lines , source ) abort
if a :jobinfo .file_mode
let window_make_ids = get ( w :, 'neomake_make_ids' , [])
if index ( window_make_ids , a :jobinfo .make_id ) = = -1
if ! bufexists ( a :jobinfo .bufnr )
call neomake #log #info ( 'No buffer found for output!' , a :jobinfo )
return []
endif
if a :jobinfo .bufnr ! = bufnr ( '%' )
call neomake #log #debug ( printf ( 'Skipped pending job output for another buffer (current=%d).' , bufnr ( '%' ) ) , a :jobinfo )
return ['BufEnter' , 'WinEnter' ]
elseif neomake #core #get_tabwin_for_makeid ( a :jobinfo .make_id ) ! = [-1 , -1 ]
call neomake #log #debug ( 'Skipped pending job output (not in origin window).' , a :jobinfo )
return ['WinEnter' ]
else
call neomake #log #debug ( "Processing pending output for job's buffer in new window." , a :jobinfo )
let w :neomake_make_ids = add ( get ( w :, 'neomake_make_ids' , []) , a :jobinfo .make_id )
endif
endif
endif
" Process any pending output first.
if ! empty ( a :jobinfo .pending_output )
let outputs = {'stdout' : [], 'stderr' : []}
for [lines , source ] in a :jobinfo .pending_output
call extend ( outputs [source ], lines )
endfor
for [source , lines ] in items ( outputs )
if ! empty ( lines )
call s :ProcessJobOutput ( a :jobinfo , lines , source )
endif
endfor
call neomake #log #debug ( printf (
\ 'Processed %d pending outputs.' , len ( a :jobinfo .pending_output ) ) ,
\ a :jobinfo )
call neomake #action_queue #remove ( a :jobinfo , s :function ( 's:process_pending_output' ) )
endif
if ! empty ( a :lines )
call s :ProcessJobOutput ( a :jobinfo , a :lines , a :source )
endif
" Clean job if it had exited already.
if ! empty ( a :jobinfo .pending_output )
let a :jobinfo .pending_output = []
if has_key ( a :jobinfo , 'exit_code' )
" XXX: add test (tested manually)
call s :CleanJobinfo ( a :jobinfo )
endif
endif
return []
endfunction
" Do we need to postpone location list processing (creation and :laddexpr)?
function ! s :need_to_postpone_loclist ( jobinfo ) abort
if ! a :jobinfo .file_mode
return 0
endif
if index ( get ( w :, 'neomake_make_ids' , []) , a :jobinfo .make_id ) ! = -1
return 0
endif
call neomake #log #debug ( 'Postponing location list processing.' , a :jobinfo )
return 1
endfunction
" TODO: merge with s:handle_locqf_list_for_finished_jobs.
let s :has_getcmdwintype = exists ( '*getcmdwintype' )
function ! s :need_to_postpone_output_processing ( jobinfo ) abort
" We can only process output (change the location/quickfix list) in
" certain modes, otherwise e.g. the visual selection gets lost.
if neomake #compat #in_completion ( )
call neomake #log #debug ( 'Not processing output during completion.' , a :jobinfo )
return ['CompleteDone' ]
endif
let mode = neomake #compat #get_mode ( )
if index ( ['n' , 'i' ], mode ) = = -1
call neomake #log #debug ( 'Not processing output for mode "' .mode .'".' , a :jobinfo )
return ['BufEnter' , 'WinEnter' , 'InsertLeave' , 'CursorHold' , 'CursorHoldI' ]
endif
if s :has_getcmdwintype && ! empty ( getcmdwintype ( ) )
call neomake #log #debug ( 'Not processing output from command-line window "' .getcmdwintype ( ) .'".' , a :jobinfo )
return ['InsertLeave' , 'CursorHold' , 'CursorHoldI' ]
endif
return []
endfunction
function ! s :RegisterJobOutput ( jobinfo , lines , source ) abort
" Allow to filter output (storing the setting on the jobinfo lazily).
if ! has_key ( a :jobinfo , 'filter_output' )
let a :jobinfo .filter_output = neomake #utils #GetSetting ( 'filter_output' , a :jobinfo .maker , '' , a :jobinfo .ft , a :jobinfo .bufnr )
endif
if ! empty ( a :jobinfo .filter_output )
call call ( a :jobinfo .filter_output , [
\ a :lines , {'source' : a :source , 'jobinfo' : a :jobinfo }],
\ a :jobinfo .maker )
endif
if empty ( a :lines )
return
endif
" Register unexpected output.
if a :jobinfo .output_stream ! = # 'both' && a :jobinfo .output_stream ! = # a :source
if ! has_key ( a :jobinfo , 'unexpected_output' )
let a :jobinfo .unexpected_output = {}
endif
if ! has_key ( a :jobinfo .unexpected_output , a :source )
let a :jobinfo .unexpected_output [a :source ] = []
endif
let a :jobinfo .unexpected_output [a :source ] + = a :lines
return
endif
let make_info = s :make_info [a :jobinfo .make_id ]
if has_key ( make_info , 'entries_list' ) " use_list option
" Process output for list processing.
call s :process_pending_output ( a :jobinfo , a :lines , a :source )
endif
endfunction
function ! s :vim_output_handler ( channel , output , event_type ) abort
let channel_id = ch_info ( a :channel ) ['id' ]
let jobinfo = get ( s :jobs , get ( s :map_job_ids , channel_id , -1 ) , {})
if empty ( jobinfo )
call neomake #log #debug ( printf ( "warn: job '%s' not found for output on %s." ,
\ a :channel , a :event_type ) )
return
endif
let data = split ( a :output , '\r\?\n' , 1 )
call s :output_handler_queued ( jobinfo , data , a :event_type , 0 )
endfunction
function ! s :vim_output_handler_stdout ( channel , output ) abort
call s :vim_output_handler ( a :channel , a :output , 'stdout' )
endfunction
function ! s :vim_output_handler_stderr ( channel , output ) abort
call s :vim_output_handler ( a :channel , a :output , 'stderr' )
endfunction
function ! s :vim_exit_handler ( channel ) abort
let channel_id = ch_info ( a :channel ) ['id' ]
let jobinfo = get ( s :jobs , get ( s :map_job_ids , channel_id , -1 ) , {})
if empty ( jobinfo )
try
let job_info = job_info ( ch_getjob ( a :channel ) )
catch /^Vim(let):E916:/
" Might happen with older Vim (8.0.69, but not 8.0.586).
call neomake #log #debug ( printf ( 'exit: job not found: %s.' , a :channel ) )
return
endtry
call neomake #log #debug ( printf ( 'exit: job not found: %s (%s).' , a :channel , job_info ) )
return
endif
let job_info = job_info ( ch_getjob ( a :channel ) )
" Handle failing starts from Vim here.
let status = job_info ['exitval' ]
if status = = 122 " Vim uses EXEC_FAILED, but only on Unix?!
let jobinfo .failed_to_start = 1
" The error is on stderr.
let error = 'Vim job failed to run: ' .substitute ( join ( jobinfo .stderr ) , '\v\s+$' , '' , '' ) .'.'
let jobinfo .stderr = []
call neomake #log #error ( error )
call s :CleanJobinfo ( jobinfo )
else
call s :exit_handler ( jobinfo , status )
endif
endfunction
" @vimlint(EVL108, 1)
if has ( 'nvim-0.2.0' )
" @vimlint(EVL108, 0)
function ! s :nvim_output_handler ( job_id , data , event_type ) abort
let jobinfo = get ( s :jobs , get ( s :map_job_ids , a :job_id , -1 ) , {})
if empty ( jobinfo )
call neomake #log #debug ( printf ( 'output [%s]: job %d not found.' , a :event_type , a :job_id ) )
return
endif
if a :data = = ['' ] && ! exists ( 'jobinfo[a:event_type]' )
" EOF in Neovim (see :h on_data).
return
endif
call s :output_handler_queued ( jobinfo , copy ( a :data ) , a :event_type , 1 )
endfunction
else
" Neovim: register output from jobs as quick as possible, and trigger
" processing through a timer.
" This works around https://github.com/neovim/neovim/issues/5889).
" NOTE: a:data is never [''] here (like with other/newer Neovim
" handlers)
let s :nvim_output_handler_queue = []
function ! s :nvim_output_handler ( job_id , data , event_type ) abort
let jobinfo = get ( s :jobs , get ( s :map_job_ids , a :job_id , -1 ) , {})
if empty ( jobinfo )
call neomake #log #debug ( printf ( 'output [%s]: job %d not found.' , a :event_type , a :job_id ) )
return
endif
let args = [jobinfo , copy ( a :data ) , a :event_type , 1 ]
call add ( s :nvim_output_handler_queue , args )
if ! exists ( 'jobinfo._nvim_in_handler' )
let jobinfo ._nvim_in_handler = 1
else
let jobinfo ._nvim_in_handler + = 1
endif
if ! exists ( 's:nvim_output_handler_timer' )
let s :nvim_output_handler_timer = timer_start ( 0 , function ( 's:nvim_output_handler_cb' ) )
endif
endfunction
function ! s :nvim_output_handler_cb ( _timer ) abort
while ! empty ( s :nvim_output_handler_queue )
let args = remove ( s :nvim_output_handler_queue , 0 )
let jobinfo = args [0 ]
call call ( 's:output_handler' , args )
let jobinfo ._nvim_in_handler - = 1
if ! jobinfo ._nvim_in_handler
" Trigger previously delayed exit handler.
unlet jobinfo ._nvim_in_handler
2025-02-03 16:36:38 +08:00
if has_key ( jobinfo , '_exited_while_in_handler' )
2020-06-13 14:06:35 +08:00
call neomake #log #debug ( 'Trigger delayed exit.' , jobinfo )
call s :exit_handler ( jobinfo , jobinfo ._exited_while_in_handler )
endif
endif
endwhile
unlet ! s :nvim_output_handler_timer
endfunction
endif
" Exit handler for buffered output with Neovim.
" In this case the output gets stored on the jobstart-options dict.
function ! s :nvim_exit_handler_buffered ( job_id , data , _event_type ) abort
let jobinfo = get ( s :jobs , get ( s :map_job_ids , a :job_id , -1 ) , {})
if empty ( jobinfo )
call neomake #log #debug ( printf ( 'exit: job not found: %d.' , a :job_id ) )
return
endif
for stream in ['stdout' , 'stderr' ]
if has_key ( jobinfo .jobstart_opts , stream )
let data = copy ( jobinfo .jobstart_opts [stream ])
if data = = ['' ]
" EOF in Neovim (see :h on_data).
continue
endif
call s :output_handler ( jobinfo , data , stream , 1 )
endif
endfor
call s :exit_handler ( jobinfo , a :data )
endfunction
function ! s :nvim_exit_handler ( job_id , data , _event_type ) abort
let jobinfo = get ( s :jobs , get ( s :map_job_ids , a :job_id , -1 ) , {})
if empty ( jobinfo )
call neomake #log #debug ( printf ( 'exit: job not found: %d.' , a :job_id ) )
return
endif
call s :exit_handler ( jobinfo , a :data )
endfunction
function ! s :exit_handler ( jobinfo , data ) abort
let jobinfo = a :jobinfo
let jobinfo .exit_code = a :data
let maker = jobinfo .maker
if get ( jobinfo , 'canceled' )
call neomake #log #debug ( printf ( 'exit: %s: %s (job was canceled).' ,
\ maker .name , string ( a :data ) ) , jobinfo )
call s :CleanJobinfo ( jobinfo )
return
endif
2025-02-03 16:36:38 +08:00
if has_key ( jobinfo , '_output_while_in_handler' ) | | has_key ( jobinfo , '_nvim_in_handler' )
2020-06-13 14:06:35 +08:00
let jobinfo ._exited_while_in_handler = a :data
call neomake #log #debug ( printf ( 'exit (delayed): %s: %s.' ,
\ maker .name , string ( a :data ) ) , jobinfo )
return
endif
call neomake #log #debug ( printf ( 'exit: %s: %s.' ,
\ maker .name , string ( a :data ) ) , jobinfo )
let jobinfo ._in_exit_handler = 1
try
" Handle any unfinished lines from stdout/stderr callbacks.
for event_type in ['stdout' , 'stderr' ]
if has_key ( jobinfo , event_type )
let lines = jobinfo [event_type ]
if ! empty ( lines )
if lines [-1 ] = = # ''
call remove ( lines , -1 )
endif
if ! empty ( lines )
call s :RegisterJobOutput ( jobinfo , lines , event_type )
endif
unlet jobinfo [event_type ]
endif
endif
endfor
if ! get ( jobinfo , 'failed_to_start' )
let l :ExitCallback = neomake #utils #GetSetting ( 'exit_callback' ,
\ extend ( copy ( jobinfo ) , maker ) , 0 , jobinfo .ft , jobinfo .bufnr )
if ExitCallback isnot # 0
let callback_dict = { 'status' : jobinfo .exit_code ,
\ 'name' : maker .name ,
\ 'has_next' : ! empty ( s :make_info [jobinfo .make_id ].jobs_queue ) }
try
if type ( ExitCallback ) = = type ( '' )
let l :ExitCallback = function ( ExitCallback )
endif
call call ( ExitCallback , [callback_dict ], jobinfo )
catch
call neomake #log #error ( printf (
\ 'Error during exit_callback: %s.' , v :exception ) ,
\ jobinfo )
endtry
endif
endif
if s :async
if has ( 'nvim' ) | | jobinfo .exit_code ! = 122
call neomake #log #debug ( printf (
\ '%s: completed with exit code %d.' ,
\ maker .name , jobinfo .exit_code ) , jobinfo )
endif
let jobinfo .finished = 1
endif
if has_key ( jobinfo , 'unexpected_output' )
for [source , output ] in items ( jobinfo .unexpected_output )
let msg = printf ( '%s: unexpected output on %s: ' , maker .name , source )
call neomake #log #debug ( msg . join ( output , '\n' ) . '.' , jobinfo )
echohl WarningMsg
echom printf ( 'Neomake: %s%s' , msg , output [0 ])
for line in output [1 :-1 ]
echom line
endfor
echohl None
endfor
" NOTE: messages do not cause a wait-enter prompt during job
" callback processing. Therefore we're giving a final
" message referring to ":messages".
" (related: https://github.com/vim/vim/issues/836)
if s :async
call neomake #log #error ( printf (
\ '%s: unexpected output. See :messages for more information.' , maker .name ) , jobinfo )
else
" For non-async the above messages are visible, but we want an
" error for the log also.
call neomake #log #error ( printf ( '%s: unexpected output.' , maker .name ) , jobinfo )
endif
endif
finally
unlet jobinfo ._in_exit_handler
endtry
call s :handle_next_job ( jobinfo )
endfunction
function ! s :output_handler_queued ( jobinfo , data , event_type , trim_CR ) abort
let jobinfo = a :jobinfo
2025-02-03 16:36:38 +08:00
if has_key ( jobinfo , '_output_while_in_handler' )
2020-06-13 14:06:35 +08:00
call neomake #log #debug ( printf ( 'Queuing: %s: %s: %s.' ,
\ a :event_type , jobinfo .maker .name , string ( a :data ) ) , jobinfo )
let jobinfo ._output_while_in_handler + = [[jobinfo , a :data , a :event_type , a :trim_CR ]]
return
else
let jobinfo ._output_while_in_handler = []
endif
call s :output_handler ( jobinfo , a :data , a :event_type , a :trim_CR )
" Process queued events that might have arrived by now.
" The attribute might be unset here, since output_handler might have
" been interrupted.
2025-02-03 16:36:38 +08:00
if has_key ( jobinfo , '_output_while_in_handler' )
2020-06-13 14:06:35 +08:00
while has_key ( jobinfo , '_output_while_in_handler' ) && ! empty ( jobinfo ._output_while_in_handler )
let args = remove ( jobinfo ._output_while_in_handler , 0 )
call call ( 's:output_handler' , args )
endwhile
unlet ! jobinfo ._output_while_in_handler
endif
" Trigger previously delayed exit handler.
2025-02-03 16:36:38 +08:00
if has_key ( jobinfo , '_exited_while_in_handler' )
2020-06-13 14:06:35 +08:00
call neomake #log #debug ( 'Trigger delayed exit.' , jobinfo )
call s :exit_handler ( jobinfo , jobinfo ._exited_while_in_handler )
endif
endfunction
function ! s :output_handler ( jobinfo , data , event_type , trim_CR ) abort
let jobinfo = a :jobinfo
call neomake #log #debug ( printf ( 'output on %s: %s.' ,
\ a :event_type , string ( a :data ) ) , jobinfo )
if get ( jobinfo , 'canceled' )
call neomake #log #debug ( 'Ignoring output (job was canceled).' , jobinfo )
return
endif
let data = copy ( a :data )
if a :trim_CR && ! empty ( a :data )
call map ( data , "substitute(v:val, '\\r$', '', '')" )
endif
let last_event_type = get ( jobinfo , 'event_type' , a :event_type )
" data is a list of 'lines' read. Each element *after* the first
" element represents a newline.
if has_key ( jobinfo , a :event_type )
let jobinfo [a :event_type ][-1 ] .= data [0 ]
call extend ( jobinfo [a :event_type ], data [1 :])
else
let jobinfo [a :event_type ] = data
endif
if ! jobinfo .buffer_output | | last_event_type ! = # a :event_type
let lines = jobinfo [a :event_type ][:-2 ]
let jobinfo [a :event_type ] = jobinfo [a :event_type ][-1 :]
if ! empty ( lines )
call s :RegisterJobOutput ( jobinfo , lines , a :event_type )
endif
endif
endfunction
function ! s :abort_next_makers ( make_id ) abort
let jobs_queue = s :make_info [a :make_id ].jobs_queue
if ! empty ( jobs_queue )
let next_makers = join ( map ( copy ( jobs_queue ) , 'v:val.maker.name' ) , ', ' )
call neomake #log #info ( 'Aborting next makers: ' .next_makers .'.' , {'make_id' : a :make_id })
let s :make_info [a :make_id ].aborted_jobs = copy ( s :make_info [a :make_id ].jobs_queue )
let s :make_info [a :make_id ].jobs_queue = []
endif
endfunction
function ! s :handle_next_job ( prev_jobinfo ) abort
let make_id = get ( a :prev_jobinfo , 'make_id' , s :make_id )
if ! has_key ( s :make_info , make_id )
return {}
endif
let make_info = s :make_info [make_id ]
if ! empty ( a :prev_jobinfo )
let status = get ( a :prev_jobinfo , 'exit_code' , 0 )
if status ! = 0 && index ( [122 , 127 ], status ) = = -1
" TODO: mark maker.exe as non-executable with status 127, and
" maybe re-introduce a wrapper for `executable()` to handle it.
" Ref: https://github.com/neomake/neomake/issues/1699
if neomake #utils #GetSetting ( 'serialize_abort_on_error' , a :prev_jobinfo .maker , 0 , a :prev_jobinfo .ft , a :prev_jobinfo .bufnr )
let a :prev_jobinfo .aborted = 1
call s :abort_next_makers ( make_id )
call s :CleanJobinfo ( a :prev_jobinfo )
return {}
endif
endif
call s :CleanJobinfo ( a :prev_jobinfo )
if ! has_key ( s :make_info , make_id )
" Last job was cleaned.
return {}
endif
let serializing_for_job = get ( make_info , 'serializing_for_job' )
if serializing_for_job
if serializing_for_job ! = a :prev_jobinfo .id
call neomake #log #debug ( printf ( 'waiting for job %d to finish.' , serializing_for_job ) )
return {}
endif
unlet make_info .serializing_for_job
endif
endif
" Create job from the start of the queue, returning it.
while ! empty ( make_info .jobs_queue )
let options = remove ( make_info .jobs_queue , 0 )
let maker = options .maker
if empty ( maker )
continue
endif
" Serialization of jobs, always for non-async Vim.
if ! has_key ( options , 'serialize' )
if ! s :async | | neomake #utils #GetSetting ( 'serialize' , maker , 0 , options .ft , options .bufnr )
let options .serialize = 1
else
let options .serialize = 0
endif
endif
try
let jobinfo = s :MakeJob ( make_id , options )
catch /^Neomake: /
let log_context = extend ( options , {'make_id' : make_id })
if v :exception = ~ # '\v^Neomake: skip_job: '
let msg = substitute ( v :exception , '^Neomake: skip_job: ' , '' , '' )
call neomake #log #debug ( printf ( 'Skipping job: %s' , msg ) , log_context )
else
let error = substitute ( v :exception , '^Neomake: ' , '' , '' )
call neomake #log #exception ( error , log_context )
if options .serialize
if neomake #utils #GetSetting ( 'serialize_abort_on_error' , maker , 0 , options .ft , options .bufnr )
call s :abort_next_makers ( make_id )
break
endif
endif
endif
continue
endtry
if ! empty ( jobinfo )
return jobinfo
endif
endwhile
" Cleanup make info, but only if there are no queued actions.
for [_ , v ] in g :neomake #action_queue #_s .action_queue
if v [1 ][0 ] = = make_info
call neomake #log #debug ( 'Skipping cleaning of make info for queued actions.' , make_info )
return {}
endif
endfor
call s :clean_make_info ( make_info )
return {}
endfunction
function ! neomake #_add_error ( maker_type , entry ) abort
if ! has_key ( s :current_errors [a :maker_type ], a :entry .bufnr )
let s :current_errors [a :maker_type ][a :entry .bufnr ] = {}
endif
if ! has_key ( s :current_errors [a :maker_type ][a :entry .bufnr ], a :entry .lnum )
let s :current_errors [a :maker_type ][a :entry .bufnr ][a :entry .lnum ] = [a :entry ]
else
call add ( s :current_errors [a :maker_type ][a :entry .bufnr ][a :entry .lnum ], a :entry )
endif
endfunction
function ! neomake #get_nearest_error ( ) abort
let buf = bufnr ( '%' )
let ln = line ( '.' )
let ln_errors = []
for maker_type in ['file' , 'project' ]
let buf_errors = get ( s :current_errors [maker_type ], buf , {})
let ln_errors + = get ( buf_errors , ln , [])
endfor
if empty ( ln_errors )
return {}
endif
if len ( ln_errors ) > 1
call sort ( ln_errors , function ( 'neomake#utils#sort_by_col' ) )
endif
return ln_errors [0 ]
endfunction
function ! neomake #GetCurrentErrorMsg ( ) abort
let entry = neomake #get_nearest_error ( )
if empty ( entry )
return ''
endif
let r = entry .maker_name . ': ' . entry .text
let suffix = entry .type . ( entry .nr ! = -1 ? entry .nr : '' )
if ! empty ( suffix )
let r .= ' (' .suffix .')'
endif
return r
endfunction
function ! neomake #EchoCurrentError ( ...) abort
if ! get ( g :, 'neomake_echo_current_error' , 1 )
return
endif
" a:1 might be a timer from the VimResized event.
let force = a :0 ? a :1 : 0
let message = neomake #GetCurrentErrorMsg ( )
if empty ( message )
if exists ( 's:neomake_last_echoed_error' )
echon ''
unlet s :neomake_last_echoed_error
endif
return
endif
if ! force && exists ( 's:neomake_last_echoed_error' )
\ && s :neomake_last_echoed_error = = message
return
endif
let s :neomake_last_echoed_error = message
call neomake #utils #WideMessage ( message )
endfunction
function ! neomake #CursorMoved ( ) abort
call neomake #EchoCurrentError ( )
call neomake #virtualtext #handle_current_error ( )
endfunction
function ! s :cursormoved_delayed_cb ( ...) abort
if getpos ( '.' ) = = s :cursormoved_last_pos
call neomake #CursorMoved ( )
endif
endfunction
function ! neomake #CursorMovedDelayed ( ) abort
if exists ( 's:cursormoved_timer' )
call timer_stop ( s :cursormoved_timer )
endif
let delay = get ( g :, 'neomake_cursormoved_delay' , 100 )
let s :cursormoved_timer = timer_start ( delay , function ( 's:cursormoved_delayed_cb' ) )
let s :cursormoved_last_pos = getpos ( '.' )
endfunction
function ! neomake #Make ( file_mode_or_options , ...) abort
if type ( a :file_mode_or_options ) = = type ( {})
return s :Make ( a :file_mode_or_options )
endif
let file_mode = a :file_mode_or_options
let options = {'file_mode' : file_mode }
if file_mode
let options .ft = &filetype
endif
if a :0
if ! empty ( a :1 )
let maker_names = a :1
" Split names on non-breaking space (annotation from completion).
call map ( maker_names , "type(v:val) == 1 ? split(v:val, ' (')[0] : v:val" )
let options .enabled_makers = a :1
endif
if a :0 > 1
let options .exit_callback = a :2
endif
endif
return map ( copy ( s :Make ( options ) ) , 'v:val.id' )
endfunction
function ! neomake #ShCommand ( bang , sh_command , ...) abort
let maker = neomake #utils #MakerFromCommand ( a :sh_command )
let maker .name = 'sh: ' .a :sh_command
let maker .errorformat = '%m'
let maker .default_entry_type = ''
let options = {
\ 'enabled_makers' : [maker ],
\ 'file_mode' : 0 ,
\ 'output_stream' : 'both' ,
\ 'buffer_output' : ! a :bang ,
\ }
if a :0
call extend ( options , a :1 )
endif
let jobinfos = s :Make ( options )
return empty ( jobinfos ) ? -1 : jobinfos [0 ].id
endfunction
function ! neomake #Sh ( sh_command , ...) abort
" Deprecated, but documented.
let options = a :0 ? { 'exit_callback' : a :1 } : {}
return neomake #ShCommand ( 0 , a :sh_command , options )
endfunction
function ! neomake #map_makers ( makers , ft , auto_enabled ) abort
let makers = []
let errors = []
let get_args = a :ft is # -1 ? [] : [a :ft ]
for maker in a :makers
try
let m = call ( 'neomake#GetMaker' , [maker ] + get_args )
catch /^Neomake: /
call add ( errors , substitute ( v :exception , '^Neomake: ' , '' , '' ) .'.' )
unlet maker " vim73/vim-trusty
continue
endtry
call add ( makers , m )
unlet maker " vim73/vim-trusty
endfor
if ! empty ( errors )
let log_context = get ( s :make_info , s :make_id , {})
for error in errors
if a :auto_enabled
call neomake #log #debug ( error , log_context )
else
call neomake #log #error ( error , log_context )
endif
endfor
endif
" Set auto_enabled, but keep explicitly set value.
call map ( makers , 'extend(v:val, {''auto_enabled'': a:auto_enabled}, ''keep'')' )
return makers
endfunction