mirror of
https://github.com/dense-analysis/ale.git
synced 2025-12-16 01:07:06 +08:00
#2132 - lint and fix with ale#command#Run
A new function is added here which will later be modified for public use in linter and fixer callbacks. All linting and fixing now goes through this new function, to prove that it works in all cases.
This commit is contained in:
@@ -3,33 +3,37 @@
|
||||
" managing files during linting and fixing cycles.
|
||||
|
||||
" This dictionary holds lists of files and directories to remove later.
|
||||
if !exists('s:managed_data')
|
||||
let s:managed_data = {}
|
||||
if !exists('s:buffer_data')
|
||||
let s:buffer_data = {}
|
||||
endif
|
||||
|
||||
" Used to get the data in tests.
|
||||
function! ale#command#GetData() abort
|
||||
return deepcopy(s:managed_data)
|
||||
return deepcopy(s:buffer_data)
|
||||
endfunction
|
||||
|
||||
function! ale#command#ClearData() abort
|
||||
let s:managed_data = {}
|
||||
let s:buffer_data = {}
|
||||
endfunction
|
||||
|
||||
function! ale#command#InitData(buffer) abort
|
||||
if !has_key(s:buffer_data, a:buffer)
|
||||
let s:buffer_data[a:buffer] = {
|
||||
\ 'jobs': {},
|
||||
\ 'file_list': [],
|
||||
\ 'directory_list': [],
|
||||
\}
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#command#ManageFile(buffer, file) abort
|
||||
if !has_key(s:managed_data, a:buffer)
|
||||
let s:managed_data[a:buffer] = {'file_list': [], 'directory_list': []}
|
||||
endif
|
||||
|
||||
call add(s:managed_data[a:buffer].file_list, a:file)
|
||||
call ale#command#InitData(a:buffer)
|
||||
call add(s:buffer_data[a:buffer].file_list, a:file)
|
||||
endfunction
|
||||
|
||||
function! ale#command#ManageDirectory(buffer, directory) abort
|
||||
if !has_key(s:managed_data, a:buffer)
|
||||
let s:managed_data[a:buffer] = {'file_list': [], 'directory_list': []}
|
||||
endif
|
||||
|
||||
call add(s:managed_data[a:buffer].directory_list, a:directory)
|
||||
call ale#command#InitData(a:buffer)
|
||||
call add(s:buffer_data[a:buffer].directory_list, a:directory)
|
||||
endfunction
|
||||
|
||||
function! ale#command#CreateFile(buffer) abort
|
||||
@@ -61,17 +65,9 @@ function! ale#command#CreateDirectory(buffer) abort
|
||||
endfunction
|
||||
|
||||
function! ale#command#RemoveManagedFiles(buffer) abort
|
||||
let l:info = get(s:managed_data, a:buffer, {})
|
||||
let l:info = get(s:buffer_data, a:buffer, {})
|
||||
|
||||
if !empty(l:info)
|
||||
\&& (
|
||||
\ !exists('*ale#engine#IsCheckingBuffer')
|
||||
\ || !ale#engine#IsCheckingBuffer(a:buffer)
|
||||
\)
|
||||
\&& (
|
||||
\ !has_key(g:ale_fix_buffer_data, a:buffer)
|
||||
\ || g:ale_fix_buffer_data[a:buffer].done
|
||||
\)
|
||||
if !empty(l:info) && empty(l:info.jobs)
|
||||
" We can't delete anything in a sandbox, so wait until we escape from
|
||||
" it to delete temporary files and directories.
|
||||
if ale#util#InSandbox()
|
||||
@@ -91,7 +87,7 @@ function! ale#command#RemoveManagedFiles(buffer) abort
|
||||
call delete(l:directory, 'rf')
|
||||
endfor
|
||||
|
||||
call remove(s:managed_data, a:buffer)
|
||||
call remove(s:buffer_data, a:buffer)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
@@ -187,3 +183,148 @@ function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_ne
|
||||
|
||||
return [l:temporary_file, l:command, l:file_created]
|
||||
endfunction
|
||||
|
||||
function! ale#command#StopJobs(buffer, job_type) abort
|
||||
let l:info = get(s:buffer_data, a:buffer, {})
|
||||
|
||||
if !empty(l:info)
|
||||
let l:new_map = {}
|
||||
|
||||
for [l:job_id, l:job_type] in items(l:info.jobs)
|
||||
let l:job_id = str2nr(l:job_id)
|
||||
|
||||
if a:job_type is# 'all' || a:job_type is# l:job_type
|
||||
call ale#job#Stop(l:job_id)
|
||||
else
|
||||
let l:new_map[l:job_id] = l:job_type
|
||||
endif
|
||||
endfor
|
||||
|
||||
let l:info.jobs = l:new_map
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:GatherOutput(line_list, job_id, line) abort
|
||||
call add(a:line_list, a:line)
|
||||
endfunction
|
||||
|
||||
function! s:ExitCallback(buffer, line_list, Callback, data) abort
|
||||
if !has_key(s:buffer_data, a:buffer)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:jobs = s:buffer_data[a:buffer].jobs
|
||||
|
||||
if !has_key(l:jobs, a:data.job_id)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:job_type = remove(l:jobs, a:data.job_id)
|
||||
|
||||
if g:ale_history_enabled
|
||||
call ale#history#SetExitCode(a:buffer, a:data.job_id, a:data.exit_code)
|
||||
|
||||
" Log the output of the command for ALEInfo if we should.
|
||||
if g:ale_history_log_output && a:data.log_output is 1
|
||||
call ale#history#RememberOutput(
|
||||
\ a:buffer,
|
||||
\ a:data.job_id,
|
||||
\ a:line_list[:]
|
||||
\)
|
||||
endif
|
||||
endif
|
||||
|
||||
" If the callback starts any new jobs, use the same job type for them.
|
||||
call setbufvar(a:buffer, 'ale_job_type', l:job_type)
|
||||
call a:Callback(a:buffer, a:line_list, a:data)
|
||||
endfunction
|
||||
|
||||
function! ale#command#Run(buffer, command, options) abort
|
||||
let l:Callback = a:options.callback
|
||||
let l:output_stream = get(a:options, 'output_stream', 'stdout')
|
||||
let l:line_list = []
|
||||
|
||||
let [l:temporary_file, l:command, l:file_created] = ale#command#FormatCommand(
|
||||
\ a:buffer,
|
||||
\ get(a:options, 'executable', ''),
|
||||
\ a:command,
|
||||
\ get(a:options, 'read_buffer', 0),
|
||||
\ get(a:options, 'input', v:null),
|
||||
\)
|
||||
let l:command = ale#job#PrepareCommand(a:buffer, l:command)
|
||||
let l:job_options = {
|
||||
\ 'exit_cb': {job_id, exit_code -> s:ExitCallback(
|
||||
\ a:buffer,
|
||||
\ l:line_list,
|
||||
\ l:Callback,
|
||||
\ {
|
||||
\ 'job_id': job_id,
|
||||
\ 'exit_code': exit_code,
|
||||
\ 'temporary_file': l:temporary_file,
|
||||
\ 'log_output': get(a:options, 'log_output', 1),
|
||||
\ }
|
||||
\ )},
|
||||
\ 'mode': 'nl',
|
||||
\}
|
||||
|
||||
if l:output_stream is# 'stdout'
|
||||
let l:job_options.out_cb = function('s:GatherOutput', [l:line_list])
|
||||
elseif l:output_stream is# 'stderr'
|
||||
let l:job_options.err_cb = function('s:GatherOutput', [l:line_list])
|
||||
elseif l:output_stream is# 'both'
|
||||
let l:job_options.out_cb = function('s:GatherOutput', [l:line_list])
|
||||
let l:job_options.err_cb = function('s:GatherOutput', [l:line_list])
|
||||
endif
|
||||
|
||||
let l:status = 'failed'
|
||||
|
||||
if get(g:, 'ale_run_synchronously') == 1
|
||||
if get(g:, 'ale_emulate_job_failure') == 1
|
||||
let l:job_id = 0
|
||||
else
|
||||
" Generate a fake job ID for tests.
|
||||
let s:fake_job_id = get(s:, 'fake_job_id', 0) + 1
|
||||
let l:job_id = s:fake_job_id
|
||||
endif
|
||||
else
|
||||
let l:job_id = ale#job#Start(l:command, l:job_options)
|
||||
endif
|
||||
|
||||
if l:job_id
|
||||
let l:status = 'started'
|
||||
let l:job_type = getbufvar(a:buffer, 'ale_job_type', 'all')
|
||||
|
||||
call ale#command#InitData(a:buffer)
|
||||
let s:buffer_data[a:buffer].jobs[l:job_id] = l:job_type
|
||||
endif
|
||||
|
||||
if g:ale_history_enabled
|
||||
call ale#history#Add(a:buffer, l:status, l:job_id, l:command)
|
||||
endif
|
||||
|
||||
if get(g:, 'ale_run_synchronously') == 1 && l:job_id
|
||||
" Run a command synchronously if this test option is set.
|
||||
call extend(l:line_list, systemlist(
|
||||
\ type(l:command) is v:t_list
|
||||
\ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2])
|
||||
\ : l:command
|
||||
\))
|
||||
|
||||
" Don't capture output when the callbacks aren't set.
|
||||
if !has_key(l:job_options, 'out_cb')
|
||||
\&& !has_key(l:job_options, 'err_cb')
|
||||
let l:line_list = []
|
||||
endif
|
||||
|
||||
if !exists('g:ale_run_synchronously_callbacks')
|
||||
let g:ale_run_synchronously_callbacks = []
|
||||
endif
|
||||
|
||||
call add(
|
||||
\ g:ale_run_synchronously_callbacks,
|
||||
\ {-> l:job_options.exit_cb(l:job_id, v:shell_error)}
|
||||
\)
|
||||
endif
|
||||
|
||||
return l:job_id ? v:true : v:false
|
||||
endfunction
|
||||
|
||||
@@ -5,20 +5,10 @@
|
||||
" Remapping of linter problems.
|
||||
let g:ale_type_map = get(g:, 'ale_type_map', {})
|
||||
|
||||
" Stores information for each job including:
|
||||
"
|
||||
" linter: The linter dictionary for the job.
|
||||
" buffer: The buffer number for the job.
|
||||
" output: The array of lines for the output of the job.
|
||||
if !has_key(s:, 'job_info_map')
|
||||
let s:job_info_map = {}
|
||||
endif
|
||||
|
||||
if !has_key(s:, 'executable_cache_map')
|
||||
let s:executable_cache_map = {}
|
||||
endif
|
||||
|
||||
|
||||
function! ale#engine#CleanupEveryBuffer() abort
|
||||
for l:key in keys(g:ale_buffer_info)
|
||||
" The key could be a filename or a buffer number, so try and
|
||||
@@ -71,11 +61,9 @@ endfunction
|
||||
|
||||
function! ale#engine#InitBufferInfo(buffer) abort
|
||||
if !has_key(g:ale_buffer_info, a:buffer)
|
||||
" job_list will hold the list of job IDs
|
||||
" active_linter_list will hold the list of active linter names
|
||||
" loclist holds the loclist items after all jobs have completed.
|
||||
let g:ale_buffer_info[a:buffer] = {
|
||||
\ 'job_list': [],
|
||||
\ 'active_linter_list': [],
|
||||
\ 'active_other_sources_list': [],
|
||||
\ 'loclist': [],
|
||||
@@ -121,12 +109,6 @@ function! ale#engine#CreateDirectory(buffer) abort
|
||||
return ale#command#CreateDirectory(a:buffer)
|
||||
endfunction
|
||||
|
||||
function! s:GatherOutput(job_id, line) abort
|
||||
if has_key(s:job_info_map, a:job_id)
|
||||
call add(s:job_info_map[a:job_id].output, a:line)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_source) abort
|
||||
let l:info = get(g:ale_buffer_info, a:buffer, {})
|
||||
|
||||
@@ -137,7 +119,7 @@ function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_sour
|
||||
if !a:from_other_source
|
||||
" Remove this linter from the list of active linters.
|
||||
" This may have already been done when the job exits.
|
||||
call filter(l:info.active_linter_list, 'v:val isnot# a:linter_name')
|
||||
call filter(l:info.active_linter_list, 'v:val.name isnot# a:linter_name')
|
||||
endif
|
||||
|
||||
" Make some adjustments to the loclists to fix common problems, and also
|
||||
@@ -170,27 +152,19 @@ function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_sour
|
||||
call ale#engine#SetResults(a:buffer, l:info.loclist)
|
||||
endfunction
|
||||
|
||||
function! s:HandleExit(job_id, exit_code) abort
|
||||
if !has_key(s:job_info_map, a:job_id)
|
||||
function! s:HandleExit(job_info, buffer, output, data) abort
|
||||
let l:buffer_info = get(g:ale_buffer_info, a:buffer)
|
||||
|
||||
if empty(l:buffer_info)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:job_info = s:job_info_map[a:job_id]
|
||||
let l:linter = l:job_info.linter
|
||||
let l:output = l:job_info.output
|
||||
let l:buffer = l:job_info.buffer
|
||||
let l:executable = l:job_info.executable
|
||||
let l:next_chain_index = l:job_info.next_chain_index
|
||||
|
||||
if g:ale_history_enabled
|
||||
call ale#history#SetExitCode(l:buffer, a:job_id, a:exit_code)
|
||||
endif
|
||||
let l:linter = a:job_info.linter
|
||||
let l:executable = a:job_info.executable
|
||||
let l:next_chain_index = a:job_info.next_chain_index
|
||||
|
||||
" Remove this job from the list.
|
||||
call ale#job#Stop(a:job_id)
|
||||
call remove(s:job_info_map, a:job_id)
|
||||
call filter(g:ale_buffer_info[l:buffer].job_list, 'v:val isnot# a:job_id')
|
||||
call filter(g:ale_buffer_info[l:buffer].active_linter_list, 'v:val isnot# l:linter.name')
|
||||
call filter(l:buffer_info.active_linter_list, 'v:val.name isnot# l:linter.name')
|
||||
|
||||
" Stop here if we land in the handle for a job completing if we're in
|
||||
" a sandbox.
|
||||
@@ -198,29 +172,24 @@ function! s:HandleExit(job_id, exit_code) abort
|
||||
return
|
||||
endif
|
||||
|
||||
if has('nvim') && !empty(l:output) && empty(l:output[-1])
|
||||
call remove(l:output, -1)
|
||||
if has('nvim') && !empty(a:output) && empty(a:output[-1])
|
||||
call remove(a:output, -1)
|
||||
endif
|
||||
|
||||
if l:next_chain_index < len(get(l:linter, 'command_chain', []))
|
||||
call s:InvokeChain(l:buffer, l:executable, l:linter, l:next_chain_index, l:output)
|
||||
call s:InvokeChain(a:buffer, l:executable, l:linter, l:next_chain_index, a:output)
|
||||
|
||||
return
|
||||
endif
|
||||
|
||||
" Log the output of the command for ALEInfo if we should.
|
||||
if g:ale_history_enabled && g:ale_history_log_output
|
||||
call ale#history#RememberOutput(l:buffer, a:job_id, l:output[:])
|
||||
endif
|
||||
|
||||
try
|
||||
let l:loclist = ale#util#GetFunction(l:linter.callback)(l:buffer, l:output)
|
||||
let l:loclist = ale#util#GetFunction(l:linter.callback)(a:buffer, a:output)
|
||||
" Handle the function being unknown, or being deleted.
|
||||
catch /E700/
|
||||
let l:loclist = []
|
||||
endtry
|
||||
|
||||
call ale#engine#HandleLoclist(l:linter.name, l:buffer, l:loclist, 0)
|
||||
call ale#engine#HandleLoclist(l:linter.name, a:buffer, l:loclist, 0)
|
||||
endfunction
|
||||
|
||||
function! ale#engine#SetResults(buffer, loclist) abort
|
||||
@@ -431,7 +400,7 @@ function! s:RunJob(options) abort
|
||||
let l:command = a:options.command
|
||||
|
||||
if empty(l:command)
|
||||
return 0
|
||||
return v:false
|
||||
endif
|
||||
|
||||
let l:executable = a:options.executable
|
||||
@@ -442,87 +411,37 @@ function! s:RunJob(options) abort
|
||||
let l:read_buffer = a:options.read_buffer
|
||||
let l:info = g:ale_buffer_info[l:buffer]
|
||||
|
||||
let [l:temporary_file, l:command, l:file_created] = ale#command#FormatCommand(
|
||||
\ l:buffer,
|
||||
\ l:executable,
|
||||
\ l:command,
|
||||
\ l:read_buffer,
|
||||
\ v:null,
|
||||
\)
|
||||
|
||||
if l:file_created
|
||||
" If a temporary filename has been formatted in to the command, then
|
||||
" we do not need to send the Vim buffer to the command.
|
||||
let l:read_buffer = 0
|
||||
endif
|
||||
|
||||
let l:command = ale#job#PrepareCommand(l:buffer, l:command)
|
||||
let l:job_options = {
|
||||
\ 'mode': 'nl',
|
||||
\ 'exit_cb': function('s:HandleExit'),
|
||||
\}
|
||||
|
||||
if l:output_stream is# 'stderr'
|
||||
let l:job_options.err_cb = function('s:GatherOutput')
|
||||
elseif l:output_stream is# 'both'
|
||||
let l:job_options.out_cb = function('s:GatherOutput')
|
||||
let l:job_options.err_cb = function('s:GatherOutput')
|
||||
else
|
||||
let l:job_options.out_cb = function('s:GatherOutput')
|
||||
endif
|
||||
|
||||
if get(g:, 'ale_run_synchronously') == 1
|
||||
" Find a unique Job value to use, which will be the same as the ID for
|
||||
" running commands synchronously. This is only for test code.
|
||||
let l:job_id = len(s:job_info_map) + 1
|
||||
|
||||
while has_key(s:job_info_map, l:job_id)
|
||||
let l:job_id += 1
|
||||
endwhile
|
||||
else
|
||||
let l:job_id = ale#job#Start(l:command, l:job_options)
|
||||
endif
|
||||
|
||||
let l:status = 'failed'
|
||||
let l:run = ale#command#Run(l:buffer, l:command, {
|
||||
\ 'output_stream': l:output_stream,
|
||||
\ 'executable': l:executable,
|
||||
\ 'read_buffer': l:read_buffer,
|
||||
\ 'log_output': l:next_chain_index >= len(get(l:linter, 'command_chain', [])),
|
||||
\ 'callback': function('s:HandleExit', [{
|
||||
\ 'linter': l:linter,
|
||||
\ 'executable': l:executable,
|
||||
\ 'next_chain_index': l:next_chain_index,
|
||||
\ }]),
|
||||
\})
|
||||
|
||||
" Only proceed if the job is being run.
|
||||
if l:job_id
|
||||
" Add the job to the list of jobs, so we can track them.
|
||||
call add(l:info.job_list, l:job_id)
|
||||
if l:run
|
||||
let l:found = 0
|
||||
|
||||
if index(l:info.active_linter_list, l:linter.name) < 0
|
||||
call add(l:info.active_linter_list, l:linter.name)
|
||||
for l:other_linter in l:info.active_linter_list
|
||||
if l:other_linter.name is# l:linter.name
|
||||
let l:found = 1
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
|
||||
if !l:found
|
||||
call add(l:info.active_linter_list, l:linter)
|
||||
endif
|
||||
|
||||
let l:status = 'started'
|
||||
" Store the ID for the job in the map to read back again.
|
||||
let s:job_info_map[l:job_id] = {
|
||||
\ 'linter': l:linter,
|
||||
\ 'buffer': l:buffer,
|
||||
\ 'executable': l:executable,
|
||||
\ 'output': [],
|
||||
\ 'next_chain_index': l:next_chain_index,
|
||||
\}
|
||||
|
||||
silent doautocmd <nomodeline> User ALEJobStarted
|
||||
endif
|
||||
|
||||
if g:ale_history_enabled
|
||||
call ale#history#Add(l:buffer, l:status, l:job_id, l:command)
|
||||
endif
|
||||
|
||||
if get(g:, 'ale_run_synchronously') == 1
|
||||
" Run a command synchronously if this test option is set.
|
||||
let s:job_info_map[l:job_id].output = systemlist(
|
||||
\ type(l:command) is v:t_list
|
||||
\ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2])
|
||||
\ : l:command
|
||||
\)
|
||||
|
||||
call l:job_options.exit_cb(l:job_id, v:shell_error)
|
||||
endif
|
||||
|
||||
return l:job_id != 0
|
||||
return l:run
|
||||
endfunction
|
||||
|
||||
" Determine which commands to run for a link in a command chain, or
|
||||
@@ -599,35 +518,26 @@ function! s:InvokeChain(buffer, executable, linter, chain_index, input) abort
|
||||
endfunction
|
||||
|
||||
function! s:StopCurrentJobs(buffer, include_lint_file_jobs) abort
|
||||
call ale#command#StopJobs(a:buffer, 'linter')
|
||||
|
||||
if a:include_lint_file_jobs
|
||||
call ale#command#StopJobs(a:buffer, 'file_linter')
|
||||
endif
|
||||
|
||||
let l:info = get(g:ale_buffer_info, a:buffer, {})
|
||||
let l:new_job_list = []
|
||||
let l:new_active_linter_list = []
|
||||
|
||||
for l:job_id in get(l:info, 'job_list', [])
|
||||
let l:job_info = get(s:job_info_map, l:job_id, {})
|
||||
|
||||
if !empty(l:job_info)
|
||||
if a:include_lint_file_jobs || !l:job_info.linter.lint_file
|
||||
call ale#job#Stop(l:job_id)
|
||||
call remove(s:job_info_map, l:job_id)
|
||||
else
|
||||
call add(l:new_job_list, l:job_id)
|
||||
" Linters with jobs still running are still active.
|
||||
call add(l:new_active_linter_list, l:job_info.linter.name)
|
||||
endif
|
||||
for l:linter in get(l:info, 'active_linter_list', [])
|
||||
" Keep jobs for linting files when we're only linting buffers.
|
||||
if !a:include_lint_file_jobs && get(l:linter, 'lint_file')
|
||||
call add(l:new_active_linter_list, l:linter)
|
||||
endif
|
||||
endfor
|
||||
|
||||
" Remove duplicates from the active linter list.
|
||||
call uniq(sort(l:new_active_linter_list))
|
||||
|
||||
" Update the List, so it includes only the jobs we still need.
|
||||
let l:info.job_list = l:new_job_list
|
||||
" Update the active linter list, clearing out anything not running.
|
||||
let l:info.active_linter_list = l:new_active_linter_list
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort
|
||||
" Figure out which linters are still enabled, and remove
|
||||
" problems for linters which are no longer enabled.
|
||||
@@ -687,6 +597,10 @@ function! s:RunLinter(buffer, linter) abort
|
||||
else
|
||||
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
|
||||
|
||||
" Use different job types for file or linter jobs.
|
||||
let l:job_type = a:linter.lint_file ? 'file_linter' : 'linter'
|
||||
call setbufvar(a:buffer, 'ale_job_type', l:job_type)
|
||||
|
||||
if ale#engine#IsExecutable(a:buffer, l:executable)
|
||||
return s:InvokeChain(a:buffer, l:executable, a:linter, 0, [])
|
||||
endif
|
||||
@@ -757,90 +671,3 @@ function! ale#engine#GetLoclist(buffer) abort
|
||||
|
||||
return g:ale_buffer_info[a:buffer].loclist
|
||||
endfunction
|
||||
|
||||
" This function can be called with a timeout to wait for all jobs to finish.
|
||||
" If the jobs to not finish in the given number of milliseconds,
|
||||
" an exception will be thrown.
|
||||
"
|
||||
" The time taken will be a very rough approximation, and more time may be
|
||||
" permitted than is specified.
|
||||
function! ale#engine#WaitForJobs(deadline) abort
|
||||
let l:start_time = ale#events#ClockMilliseconds()
|
||||
|
||||
if l:start_time == 0
|
||||
throw 'Failed to read milliseconds from the clock!'
|
||||
endif
|
||||
|
||||
let l:job_list = []
|
||||
|
||||
" Gather all of the jobs from every buffer.
|
||||
for l:info in values(g:ale_buffer_info)
|
||||
call extend(l:job_list, get(l:info, 'job_list', []))
|
||||
endfor
|
||||
|
||||
" NeoVim has a built-in API for this, so use that.
|
||||
if has('nvim')
|
||||
let l:nvim_code_list = jobwait(l:job_list, a:deadline)
|
||||
|
||||
if index(l:nvim_code_list, -1) >= 0
|
||||
throw 'Jobs did not complete on time!'
|
||||
endif
|
||||
|
||||
return
|
||||
endif
|
||||
|
||||
let l:should_wait_more = 1
|
||||
|
||||
while l:should_wait_more
|
||||
let l:should_wait_more = 0
|
||||
|
||||
for l:job_id in l:job_list
|
||||
if ale#job#IsRunning(l:job_id)
|
||||
let l:now = ale#events#ClockMilliseconds()
|
||||
|
||||
if l:now - l:start_time > a:deadline
|
||||
" Stop waiting after a timeout, so we don't wait forever.
|
||||
throw 'Jobs did not complete on time!'
|
||||
endif
|
||||
|
||||
" Wait another 10 milliseconds
|
||||
let l:should_wait_more = 1
|
||||
sleep 10ms
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
endwhile
|
||||
|
||||
" Sleep for a small amount of time after all jobs finish.
|
||||
" This seems to be enough to let handlers after jobs end run, and
|
||||
" prevents the occasional failure where this function exits after jobs
|
||||
" end, but before handlers are run.
|
||||
sleep 10ms
|
||||
|
||||
" We must check the buffer data again to see if new jobs started
|
||||
" for command_chain linters.
|
||||
let l:has_new_jobs = 0
|
||||
|
||||
" Check again to see if any jobs are running.
|
||||
for l:info in values(g:ale_buffer_info)
|
||||
for l:job_id in get(l:info, 'job_list', [])
|
||||
if ale#job#IsRunning(l:job_id)
|
||||
let l:has_new_jobs = 1
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
endfor
|
||||
|
||||
if l:has_new_jobs
|
||||
" We have to wait more. Offset the timeout by the time taken so far.
|
||||
let l:now = ale#events#ClockMilliseconds()
|
||||
let l:new_deadline = a:deadline - (l:now - l:start_time)
|
||||
|
||||
if l:new_deadline <= 0
|
||||
" Enough time passed already, so stop immediately.
|
||||
throw 'Jobs did not complete on time!'
|
||||
endif
|
||||
|
||||
call ale#engine#WaitForJobs(l:new_deadline)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
if !has_key(s:, 'job_info_map')
|
||||
let s:job_info_map = {}
|
||||
endif
|
||||
|
||||
function! s:GatherOutput(job_id, line) abort
|
||||
if has_key(s:job_info_map, a:job_id)
|
||||
call add(s:job_info_map[a:job_id].output, a:line)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" Apply fixes queued up for buffers which may be hidden.
|
||||
" Vim doesn't let you modify hidden buffers.
|
||||
function! ale#fix#ApplyQueuedFixes() abort
|
||||
@@ -94,52 +84,48 @@ function! ale#fix#ApplyFixes(buffer, output) abort
|
||||
call ale#fix#ApplyQueuedFixes()
|
||||
endfunction
|
||||
|
||||
function! s:HandleExit(job_id, exit_code) abort
|
||||
if !has_key(s:job_info_map, a:job_id)
|
||||
function! s:HandleExit(job_info, buffer, job_output, data) abort
|
||||
let l:buffer_info = get(g:ale_fix_buffer_data, a:buffer, {})
|
||||
|
||||
if empty(l:buffer_info)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:job_info = remove(s:job_info_map, a:job_id)
|
||||
let l:buffer = l:job_info.buffer
|
||||
|
||||
if g:ale_history_enabled
|
||||
call ale#history#SetExitCode(l:buffer, a:job_id, a:exit_code)
|
||||
if a:job_info.read_temporary_file
|
||||
let l:output = !empty(a:data.temporary_file)
|
||||
\ ? readfile(a:data.temporary_file)
|
||||
\ : []
|
||||
else
|
||||
let l:output = a:job_output
|
||||
endif
|
||||
|
||||
if has_key(l:job_info, 'file_to_read')
|
||||
let l:job_info.output = readfile(l:job_info.file_to_read)
|
||||
endif
|
||||
|
||||
let l:ChainCallback = get(l:job_info, 'chain_with', v:null)
|
||||
let l:ProcessWith = get(l:job_info, 'process_with', v:null)
|
||||
let l:ChainCallback = get(a:job_info, 'chain_with', v:null)
|
||||
let l:ProcessWith = get(a:job_info, 'process_with', v:null)
|
||||
|
||||
" Post-process the output with a function if we have one.
|
||||
if l:ProcessWith isnot v:null
|
||||
let l:job_info.output = call(
|
||||
\ ale#util#GetFunction(l:ProcessWith),
|
||||
\ [l:buffer, l:job_info.output]
|
||||
\)
|
||||
let l:output = call(l:ProcessWith, [a:buffer, l:output])
|
||||
endif
|
||||
|
||||
" Use the output of the job for changing the file if it isn't empty,
|
||||
" otherwise skip this job and use the input from before.
|
||||
"
|
||||
" We'll use the input from before for chained commands.
|
||||
if l:ChainCallback is v:null && !empty(split(join(l:job_info.output)))
|
||||
let l:input = l:job_info.output
|
||||
if l:ChainCallback is v:null && !empty(split(join(l:output)))
|
||||
let l:input = l:output
|
||||
else
|
||||
let l:input = l:job_info.input
|
||||
let l:input = a:job_info.input
|
||||
endif
|
||||
|
||||
let l:next_index = l:ChainCallback is v:null
|
||||
\ ? l:job_info.callback_index + 1
|
||||
\ : l:job_info.callback_index
|
||||
\ ? a:job_info.callback_index + 1
|
||||
\ : a:job_info.callback_index
|
||||
|
||||
call s:RunFixer({
|
||||
\ 'buffer': l:buffer,
|
||||
\ 'buffer': a:buffer,
|
||||
\ 'input': l:input,
|
||||
\ 'output': l:job_info.output,
|
||||
\ 'callback_list': l:job_info.callback_list,
|
||||
\ 'output': l:output,
|
||||
\ 'callback_list': a:job_info.callback_list,
|
||||
\ 'callback_index': l:next_index,
|
||||
\ 'chain_callback': l:ChainCallback,
|
||||
\})
|
||||
@@ -149,15 +135,13 @@ function! s:RunJob(options) abort
|
||||
let l:buffer = a:options.buffer
|
||||
let l:command = a:options.command
|
||||
let l:input = a:options.input
|
||||
let l:output_stream = a:options.output_stream
|
||||
let l:read_temporary_file = a:options.read_temporary_file
|
||||
let l:ChainWith = a:options.chain_with
|
||||
let l:read_buffer = a:options.read_buffer
|
||||
|
||||
if empty(l:command)
|
||||
" If there's nothing further to chain the command with, stop here.
|
||||
if l:ChainWith is v:null
|
||||
return 0
|
||||
return v:false
|
||||
endif
|
||||
|
||||
" If there's another chained callback to run, then run that.
|
||||
@@ -170,87 +154,30 @@ function! s:RunJob(options) abort
|
||||
\ 'output': [],
|
||||
\})
|
||||
|
||||
return 1
|
||||
return v:true
|
||||
endif
|
||||
|
||||
let [l:temporary_file, l:command, l:file_created] = ale#command#FormatCommand(
|
||||
\ l:buffer,
|
||||
\ '',
|
||||
\ l:command,
|
||||
\ l:read_buffer,
|
||||
\ l:input,
|
||||
\)
|
||||
let l:output_stream = a:options.output_stream
|
||||
|
||||
let l:command = ale#job#PrepareCommand(l:buffer, l:command)
|
||||
let l:job_options = {
|
||||
\ 'mode': 'nl',
|
||||
\ 'exit_cb': function('s:HandleExit'),
|
||||
\}
|
||||
if a:options.read_temporary_file
|
||||
let l:output_stream = 'none'
|
||||
endif
|
||||
|
||||
let l:job_info = {
|
||||
\ 'buffer': l:buffer,
|
||||
return ale#command#Run(l:buffer, l:command, {
|
||||
\ 'output_stream': l:output_stream,
|
||||
\ 'executable': '',
|
||||
\ 'read_buffer': l:read_buffer,
|
||||
\ 'input': l:input,
|
||||
\ 'output': [],
|
||||
\ 'chain_with': l:ChainWith,
|
||||
\ 'callback_index': a:options.callback_index,
|
||||
\ 'callback_list': a:options.callback_list,
|
||||
\ 'process_with': a:options.process_with,
|
||||
\}
|
||||
|
||||
if l:read_temporary_file
|
||||
" TODO: Check that a temporary file is set here.
|
||||
let l:job_info.file_to_read = l:temporary_file
|
||||
elseif l:output_stream is# 'stderr'
|
||||
let l:job_options.err_cb = function('s:GatherOutput')
|
||||
elseif l:output_stream is# 'both'
|
||||
let l:job_options.out_cb = function('s:GatherOutput')
|
||||
let l:job_options.err_cb = function('s:GatherOutput')
|
||||
else
|
||||
let l:job_options.out_cb = function('s:GatherOutput')
|
||||
endif
|
||||
|
||||
if get(g:, 'ale_emulate_job_failure') == 1
|
||||
let l:job_id = 0
|
||||
elseif get(g:, 'ale_run_synchronously') == 1
|
||||
" Find a unique Job value to use, which will be the same as the ID for
|
||||
" running commands synchronously. This is only for test code.
|
||||
let l:job_id = len(s:job_info_map) + 1
|
||||
|
||||
while has_key(s:job_info_map, l:job_id)
|
||||
let l:job_id += 1
|
||||
endwhile
|
||||
else
|
||||
let l:job_id = ale#job#Start(l:command, l:job_options)
|
||||
endif
|
||||
|
||||
let l:status = l:job_id ? 'started' : 'failed'
|
||||
|
||||
if g:ale_history_enabled
|
||||
call ale#history#Add(l:buffer, l:status, l:job_id, l:command)
|
||||
endif
|
||||
|
||||
if l:job_id == 0
|
||||
return 0
|
||||
endif
|
||||
|
||||
let s:job_info_map[l:job_id] = l:job_info
|
||||
|
||||
if get(g:, 'ale_run_synchronously') == 1
|
||||
" Run a command synchronously if this test option is set.
|
||||
let l:output = systemlist(
|
||||
\ type(l:command) is v:t_list
|
||||
\ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2])
|
||||
\ : l:command
|
||||
\)
|
||||
|
||||
if !l:read_temporary_file
|
||||
let s:job_info_map[l:job_id].output = l:output
|
||||
endif
|
||||
|
||||
call l:job_options.exit_cb(l:job_id, v:shell_error)
|
||||
endif
|
||||
|
||||
return 1
|
||||
\ 'log_output': 0,
|
||||
\ 'callback': function('s:HandleExit', [{
|
||||
\ 'input': l:input,
|
||||
\ 'chain_with': l:ChainWith,
|
||||
\ 'callback_index': a:options.callback_index,
|
||||
\ 'callback_list': a:options.callback_list,
|
||||
\ 'process_with': a:options.process_with,
|
||||
\ 'read_temporary_file': a:options.read_temporary_file,
|
||||
\ }]),
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! s:RunFixer(options) abort
|
||||
@@ -259,6 +186,9 @@ function! s:RunFixer(options) abort
|
||||
let l:index = a:options.callback_index
|
||||
let l:ChainCallback = get(a:options, 'chain_callback', v:null)
|
||||
|
||||
" Record new jobs started as fixer jobs.
|
||||
call setbufvar(l:buffer, 'ale_job_type', 'fixer')
|
||||
|
||||
while len(a:options.callback_list) > l:index
|
||||
let l:Function = l:ChainCallback isnot v:null
|
||||
\ ? ale#util#GetFunction(l:ChainCallback)
|
||||
@@ -419,16 +349,7 @@ function! ale#fix#Fix(buffer, fixing_flag, ...) abort
|
||||
return 0
|
||||
endif
|
||||
|
||||
for l:job_id in keys(s:job_info_map)
|
||||
call remove(s:job_info_map, l:job_id)
|
||||
call ale#job#Stop(l:job_id)
|
||||
endfor
|
||||
|
||||
" Mark the buffer as `done` so files can be removed.
|
||||
if has_key(g:ale_fix_buffer_data, a:buffer)
|
||||
let g:ale_fix_buffer_data[a:buffer].done = 1
|
||||
endif
|
||||
|
||||
call ale#command#StopJobs(a:buffer, 'fixer')
|
||||
" Clean up any files we might have left behind from a previous run.
|
||||
call ale#command#RemoveManagedFiles(a:buffer)
|
||||
call ale#fix#InitBufferData(a:buffer, a:fixing_flag)
|
||||
|
||||
@@ -99,7 +99,8 @@ function! s:VimCloseCallback(channel) abort
|
||||
if job_status(l:job) is# 'dead'
|
||||
try
|
||||
if !empty(l:info) && has_key(l:info, 'exit_cb')
|
||||
call ale#util#GetFunction(l:info.exit_cb)(l:job_id, get(l:info, 'exit_code', 1))
|
||||
" We have to remove the callback, so we don't call it twice.
|
||||
call ale#util#GetFunction(remove(l:info, 'exit_cb'))(l:job_id, get(l:info, 'exit_code', 1))
|
||||
endif
|
||||
finally
|
||||
" Automatically forget about the job after it's done.
|
||||
@@ -124,7 +125,8 @@ function! s:VimExitCallback(job, exit_code) abort
|
||||
if ch_status(job_getchannel(a:job)) is# 'closed'
|
||||
try
|
||||
if !empty(l:info) && has_key(l:info, 'exit_cb')
|
||||
call ale#util#GetFunction(l:info.exit_cb)(l:job_id, a:exit_code)
|
||||
" We have to remove the callback, so we don't call it twice.
|
||||
call ale#util#GetFunction(remove(l:info, 'exit_cb'))(l:job_id, a:exit_code)
|
||||
endif
|
||||
finally
|
||||
" Automatically forget about the job after it's done.
|
||||
|
||||
@@ -292,8 +292,17 @@ function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort
|
||||
endif
|
||||
|
||||
if l:notified
|
||||
if index(l:info.active_linter_list, a:linter.name) < 0
|
||||
call add(l:info.active_linter_list, a:linter.name)
|
||||
let l:found = 0
|
||||
|
||||
for l:other_linter in l:info.active_linter_list
|
||||
if l:other_linter.name is# a:linter.name
|
||||
let l:found = 1
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
|
||||
if !l:found
|
||||
call add(l:info.active_linter_list, a:linter)
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
@@ -85,3 +85,103 @@ function! ale#test#GetPreviewWindowText() abort
|
||||
endif
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
" This function can be called with a timeout to wait for all jobs to finish.
|
||||
" If the jobs to not finish in the given number of milliseconds,
|
||||
" an exception will be thrown.
|
||||
"
|
||||
" The time taken will be a very rough approximation, and more time may be
|
||||
" permitted than is specified.
|
||||
function! ale#test#WaitForJobs(deadline) abort
|
||||
let l:start_time = ale#events#ClockMilliseconds()
|
||||
|
||||
if l:start_time == 0
|
||||
throw 'Failed to read milliseconds from the clock!'
|
||||
endif
|
||||
|
||||
let l:job_list = []
|
||||
|
||||
" Gather all of the jobs from every buffer.
|
||||
for [l:buffer, l:data] in items(ale#command#GetData())
|
||||
call extend(l:job_list, map(keys(l:data.jobs), 'str2nr(v:val)'))
|
||||
endfor
|
||||
|
||||
" NeoVim has a built-in API for this, so use that.
|
||||
if has('nvim')
|
||||
let l:nvim_code_list = jobwait(l:job_list, a:deadline)
|
||||
|
||||
if index(l:nvim_code_list, -1) >= 0
|
||||
throw 'Jobs did not complete on time!'
|
||||
endif
|
||||
|
||||
return
|
||||
endif
|
||||
|
||||
let l:should_wait_more = 1
|
||||
|
||||
while l:should_wait_more
|
||||
let l:should_wait_more = 0
|
||||
|
||||
for l:job_id in l:job_list
|
||||
if ale#job#IsRunning(l:job_id)
|
||||
let l:now = ale#events#ClockMilliseconds()
|
||||
|
||||
if l:now - l:start_time > a:deadline
|
||||
" Stop waiting after a timeout, so we don't wait forever.
|
||||
throw 'Jobs did not complete on time!'
|
||||
endif
|
||||
|
||||
" Wait another 10 milliseconds
|
||||
let l:should_wait_more = 1
|
||||
sleep 10ms
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
endwhile
|
||||
|
||||
" Sleep for a small amount of time after all jobs finish.
|
||||
" This seems to be enough to let handlers after jobs end run, and
|
||||
" prevents the occasional failure where this function exits after jobs
|
||||
" end, but before handlers are run.
|
||||
sleep 10ms
|
||||
|
||||
" We must check the buffer data again to see if new jobs started
|
||||
" for command_chain linters.
|
||||
let l:has_new_jobs = 0
|
||||
|
||||
" Check again to see if any jobs are running.
|
||||
for l:info in values(g:ale_buffer_info)
|
||||
for [l:job_id, l:linter] in get(l:info, 'job_list', [])
|
||||
if ale#job#IsRunning(l:job_id)
|
||||
let l:has_new_jobs = 1
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
endfor
|
||||
|
||||
if l:has_new_jobs
|
||||
" We have to wait more. Offset the timeout by the time taken so far.
|
||||
let l:now = ale#events#ClockMilliseconds()
|
||||
let l:new_deadline = a:deadline - (l:now - l:start_time)
|
||||
|
||||
if l:new_deadline <= 0
|
||||
" Enough time passed already, so stop immediately.
|
||||
throw 'Jobs did not complete on time!'
|
||||
endif
|
||||
|
||||
call ale#test#WaitForJobs(l:new_deadline)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#test#FlushJobs() abort
|
||||
" The variable is checked for in a loop, as calling one series of
|
||||
" callbacks can trigger a further series of callbacks.
|
||||
while exists('g:ale_run_synchronously_callbacks')
|
||||
let l:callbacks = g:ale_run_synchronously_callbacks
|
||||
unlet g:ale_run_synchronously_callbacks
|
||||
|
||||
for l:Callback in l:callbacks
|
||||
call l:Callback()
|
||||
endfor
|
||||
endwhile
|
||||
endfunction
|
||||
|
||||
Reference in New Issue
Block a user