1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-24 05:20:04 +08:00
SpaceVim/bundle/neomake/autoload/neomake.vim
2020-06-13 14:06:35 +08:00

2609 lines
97 KiB
VimL
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

" 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()
if exists('save_env_file')
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
if v:profiling
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
if exists('jobinfo._exited_while_in_handler')
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
if exists('jobinfo._output_while_in_handler') || exists('jobinfo._nvim_in_handler')
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
if exists('jobinfo._output_while_in_handler')
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.
if exists('jobinfo._output_while_in_handler')
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.
if exists('jobinfo._exited_while_in_handler')
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