" 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. let s:hack_keep_refs_for_profiling = [] " 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(''), '.*\zs\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 call jobstop(job) let ret = 1 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(deepcopy(g:neomake#jobinfo#base), 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 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 = '' let _uses_stdin = neomake#utils#GetSetting('uses_stdin', a:jobinfo.maker, s:unset_dict, a:jobinfo.ft, bufnr) if _uses_stdin isnot s:unset_dict let a:jobinfo.uses_stdin = _uses_stdin let uses_stdin = _uses_stdin call neomake#log#debug(printf('Using uses_stdin (%s) from setting.', \ a:jobinfo.uses_stdin), a:jobinfo) if a:jobinfo.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 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 makers = neomake#map_makers(makers, a:1, auto_enabled) for maker in makers let maker.auto_enabled = auto_enabled let enabled_makers += [maker] endfor 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(''))) 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 == job.maker' \ .' && v:val.bufnr == job.bufnr' \ .' && v:val.file_mode == job.file_mode' \ ." && !get(v:val, 'canceled')")) if !empty(running_already) let jobinfo = running_already[0] call neomake#log#info(printf( \ 'Canceling already running job (%d.%d) for the same maker.', \ jobinfo.make_id, jobinfo.id), {'make_id': make_id}) call neomake#CancelJob(jobinfo.id, 1) endif endfor endif " Update automake tick (used to skip unchanged buffers). call neomake#configure#_update_automake_tick(bufnr, options.ft) " 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) call neomake#EchoCurrentError(1) call neomake#virtualtext#handle_current_error() 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 call add(a:jobinfo.pending_output, [a:lines, a:source]) 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 if get(jobinfo, 'canceled') call neomake#log#debug('exit: job was canceled.', jobinfo) call s:CleanJobinfo(jobinfo) return endif let maker = jobinfo.maker 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') redraw 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 call neomake#log#error(printf( \ '%s: unexpected output. See :messages for more information.', maker.name), jobinfo) 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 return makers endfunction