From d16e82d468eb0d5bb1e662ac4812c0ca6fc0fc64 Mon Sep 17 00:00:00 2001 From: Yaroslav Date: Tue, 25 Feb 2020 14:47:03 +0300 Subject: reorganized repo to be easier to use with GNU stow; added script to stow --- dotfiles/.vim/autoload/neomake.vim | 2582 ++++++++++++++++++++++++++++++++++++ 1 file changed, 2582 insertions(+) create mode 100644 dotfiles/.vim/autoload/neomake.vim (limited to 'dotfiles/.vim/autoload/neomake.vim') diff --git a/dotfiles/.vim/autoload/neomake.vim b/dotfiles/.vim/autoload/neomake.vim new file mode 100644 index 0000000..6322a05 --- /dev/null +++ b/dotfiles/.vim/autoload/neomake.vim @@ -0,0 +1,2582 @@ +" 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 -- cgit v1.2.3