]> git.madduck.net Git - etc/vim.git/blob - .vim/bundle/ale/autoload/ale/engine.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Merge commit '294584081929424aec883f90c7d6515b3743358d' as '.vim/bundle/vim-lsp-ale'
[etc/vim.git] / .vim / bundle / ale / autoload / ale / engine.vim
1 " Author: w0rp <devw0rp@gmail.com>
2 " Description: Backend execution and job management
3 "   Executes linters in the background, using NeoVim or Vim 8 jobs
4
5 " Remapping of linter problems.
6 let g:ale_type_map = get(g:, 'ale_type_map', {})
7 let g:ale_filename_mappings = get(g:, 'ale_filename_mappings', {})
8
9 if !has_key(s:, 'executable_cache_map')
10     let s:executable_cache_map = {}
11 endif
12
13 function! ale#engine#CleanupEveryBuffer() abort
14     for l:key in keys(g:ale_buffer_info)
15         " The key could be a filename or a buffer number, so try and
16         " convert it to a number. We need a number for the other
17         " functions.
18         let l:buffer = str2nr(l:key)
19
20         if l:buffer > 0
21             " Stop all jobs and clear the results for everything, and delete
22             " all of the data we stored for the buffer.
23             call ale#engine#Cleanup(l:buffer)
24         endif
25     endfor
26 endfunction
27
28 function! ale#engine#MarkLinterActive(info, linter) abort
29     let l:found = 0
30
31     for l:other_linter in a:info.active_linter_list
32         if l:other_linter.name is# a:linter.name
33             let l:found = 1
34             break
35         endif
36     endfor
37
38     if !l:found
39         call add(a:info.active_linter_list, a:linter)
40     endif
41 endfunction
42
43 function! ale#engine#MarkLinterInactive(info, linter_name) abort
44     call filter(a:info.active_linter_list, 'v:val.name isnot# a:linter_name')
45 endfunction
46
47 function! ale#engine#ResetExecutableCache() abort
48     let s:executable_cache_map = {}
49 endfunction
50
51 " Check if files are executable, and if they are, remember that they are
52 " for subsequent calls. We'll keep checking until programs can be executed.
53 function! ale#engine#IsExecutable(buffer, executable) abort
54     if empty(a:executable)
55         " Don't log the executable check if the executable string is empty.
56         return 0
57     endif
58
59     " Check for a cached executable() check.
60     let l:result = get(s:executable_cache_map, a:executable, v:null)
61
62     if l:result isnot v:null
63         return l:result
64     endif
65
66     " Check if the file is executable, and convert -1 to 1.
67     let l:result = executable(a:executable) isnot 0
68
69     " Cache the executable check if we found it, or if the option to cache
70     " failing checks is on.
71     if l:result || get(g:, 'ale_cache_executable_check_failures')
72         let s:executable_cache_map[a:executable] = l:result
73     endif
74
75     if g:ale_history_enabled
76         call ale#history#Add(a:buffer, l:result, 'executable', a:executable)
77     endif
78
79     return l:result
80 endfunction
81
82 function! ale#engine#InitBufferInfo(buffer) abort
83     if !has_key(g:ale_buffer_info, a:buffer)
84         " active_linter_list will hold the list of active linter names
85         " loclist holds the loclist items after all jobs have completed.
86         let g:ale_buffer_info[a:buffer] = {
87         \   'active_linter_list': [],
88         \   'active_other_sources_list': [],
89         \   'loclist': [],
90         \}
91
92         return 1
93     endif
94
95     return 0
96 endfunction
97
98 " This function is documented and part of the public API.
99 "
100 " Return 1 if ALE is busy checking a given buffer
101 function! ale#engine#IsCheckingBuffer(buffer) abort
102     let l:info = get(g:ale_buffer_info, a:buffer, {})
103
104     return !empty(get(l:info, 'active_linter_list', []))
105     \   || !empty(get(l:info, 'active_other_sources_list', []))
106 endfunction
107
108 function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_source) abort
109     let l:info = get(g:ale_buffer_info, a:buffer, {})
110
111     if empty(l:info)
112         return
113     endif
114
115     if !a:from_other_source
116         " Remove this linter from the list of active linters.
117         " This may have already been done when the job exits.
118         call filter(l:info.active_linter_list, 'v:val.name isnot# a:linter_name')
119     endif
120
121     " Make some adjustments to the loclists to fix common problems, and also
122     " to set default values for loclist items.
123     let l:linter_loclist = ale#engine#FixLocList(
124     \   a:buffer,
125     \   a:linter_name,
126     \   a:from_other_source,
127     \   a:loclist,
128     \)
129
130     " Remove previous items for this linter.
131     call filter(l:info.loclist, 'v:val.linter_name isnot# a:linter_name')
132
133     " We don't need to add items or sort the list when this list is empty.
134     if !empty(l:linter_loclist)
135         " Add the new items.
136         call extend(l:info.loclist, l:linter_loclist)
137
138         " Sort the loclist again.
139         " We need a sorted list so we can run a binary search against it
140         " for efficient lookup of the messages in the cursor handler.
141         call sort(l:info.loclist, 'ale#util#LocItemCompare')
142     endif
143
144     if ale#ShouldDoNothing(a:buffer)
145         return
146     endif
147
148     call ale#engine#SetResults(a:buffer, l:info.loclist)
149 endfunction
150
151 function! s:HandleExit(job_info, buffer, output, data) abort
152     let l:buffer_info = get(g:ale_buffer_info, a:buffer)
153
154     if empty(l:buffer_info)
155         return
156     endif
157
158     let l:linter = a:job_info.linter
159     let l:executable = a:job_info.executable
160
161     " Remove this job from the list.
162     call ale#engine#MarkLinterInactive(l:buffer_info, l:linter.name)
163
164     " Stop here if we land in the handle for a job completing if we're in
165     " a sandbox.
166     if ale#util#InSandbox()
167         return
168     endif
169
170     if has('nvim') && !empty(a:output) && empty(a:output[-1])
171         call remove(a:output, -1)
172     endif
173
174     try
175         let l:loclist = ale#util#GetFunction(l:linter.callback)(a:buffer, a:output)
176     " Handle the function being unknown, or being deleted.
177     catch /E700/
178         let l:loclist = []
179     endtry
180
181     if type(l:loclist) isnot# v:t_list
182         " we only expect the list type; don't pass anything else down to
183         " `ale#engine#HandleLoclist` since it won't understand it
184         let l:loclist = []
185     endif
186
187     call ale#engine#HandleLoclist(l:linter.name, a:buffer, l:loclist, 0)
188 endfunction
189
190 function! ale#engine#SetResults(buffer, loclist) abort
191     let l:linting_is_done = !ale#engine#IsCheckingBuffer(a:buffer)
192
193     if g:ale_use_neovim_diagnostics_api
194         call ale#engine#SendResultsToNeovimDiagnostics(a:buffer, a:loclist)
195     endif
196
197     " Set signs first. This could potentially fix some line numbers.
198     " The List could be sorted again here by SetSigns.
199     if !g:ale_use_neovim_diagnostics_api && g:ale_set_signs
200         call ale#sign#SetSigns(a:buffer, a:loclist)
201     endif
202
203     if g:ale_set_quickfix || g:ale_set_loclist
204         call ale#list#SetLists(a:buffer, a:loclist)
205     endif
206
207     if exists('*ale#statusline#Update')
208         " Don't load/run if not already loaded.
209         call ale#statusline#Update(a:buffer, a:loclist)
210     endif
211
212     if !g:ale_use_neovim_diagnostics_api && g:ale_set_highlights
213         call ale#highlight#SetHighlights(a:buffer, a:loclist)
214     endif
215
216     if !g:ale_use_neovim_diagnostics_api
217     \&& (g:ale_virtualtext_cursor is# 'all' || g:ale_virtualtext_cursor == 2)
218         call ale#virtualtext#SetTexts(a:buffer, a:loclist)
219     endif
220
221     if l:linting_is_done
222         if g:ale_echo_cursor
223             " Try and echo the warning now.
224             " This will only do something meaningful if we're in normal mode.
225             call ale#cursor#EchoCursorWarning()
226         endif
227
228         if !g:ale_use_neovim_diagnostics_api
229         \&& (g:ale_virtualtext_cursor is# 'current' || g:ale_virtualtext_cursor == 1)
230             " Try and show the warning now.
231             " This will only do something meaningful if we're in normal mode.
232             call ale#virtualtext#ShowCursorWarning()
233         endif
234
235         " Reset the save event marker, used for opening windows, etc.
236         call setbufvar(a:buffer, 'ale_save_event_fired', 0)
237         " Set a marker showing how many times a buffer has been checked.
238         call setbufvar(
239         \   a:buffer,
240         \   'ale_linted',
241         \   getbufvar(a:buffer, 'ale_linted', 0) + 1
242         \)
243
244         " Automatically remove all managed temporary files and directories
245         " now that all jobs have completed.
246         call ale#command#RemoveManagedFiles(a:buffer)
247
248         " Call user autocommands. This allows users to hook into ALE's lint cycle.
249         silent doautocmd <nomodeline> User ALELintPost
250     endif
251 endfunction
252
253 function! ale#engine#SendResultsToNeovimDiagnostics(buffer, loclist) abort
254     if !has('nvim-0.6')
255         " We will warn the user on startup as well if they try to set
256         " g:ale_use_neovim_diagnostics_api outside of a Neovim context.
257         return
258     endif
259
260     " Keep the Lua surface area really small in the VimL part of ALE,
261     " and just require the diagnostics.lua module on demand.
262     let l:SendDiagnostics = luaeval('require("ale.diagnostics").send')
263     call l:SendDiagnostics(a:buffer, a:loclist)
264 endfunction
265
266 function! s:RemapItemTypes(type_map, loclist) abort
267     for l:item in a:loclist
268         let l:key = l:item.type
269         \   . (get(l:item, 'sub_type', '') is# 'style' ? 'S' : '')
270         let l:new_key = get(a:type_map, l:key, '')
271
272         if l:new_key is# 'E'
273         \|| l:new_key is# 'ES'
274         \|| l:new_key is# 'W'
275         \|| l:new_key is# 'WS'
276         \|| l:new_key is# 'I'
277             let l:item.type = l:new_key[0]
278
279             if l:new_key is# 'ES' || l:new_key is# 'WS'
280                 let l:item.sub_type = 'style'
281             elseif has_key(l:item, 'sub_type')
282                 call remove(l:item, 'sub_type')
283             endif
284         endif
285     endfor
286 endfunction
287
288 function! ale#engine#FixLocList(buffer, linter_name, from_other_source, loclist) abort
289     let l:mappings = ale#GetFilenameMappings(a:buffer, a:linter_name)
290
291     if !empty(l:mappings)
292         " We need to apply reverse filename mapping here.
293         let l:mappings = ale#filename_mapping#Invert(l:mappings)
294     endif
295
296     let l:bufnr_map = {}
297     let l:new_loclist = []
298
299     " Some errors have line numbers beyond the end of the file,
300     " so we need to adjust them so they set the error at the last line
301     " of the file instead.
302     let l:last_line_number = ale#util#GetLineCount(a:buffer)
303
304     for l:old_item in a:loclist
305         " Copy the loclist item with some default values and corrections.
306         "
307         " line and column numbers will be converted to numbers.
308         " The buffer will default to the buffer being checked.
309         " The vcol setting will default to 0, a byte index.
310         " The error type will default to 'E' for errors.
311         " The error number will default to -1.
312         "
313         " The line number and text are the only required keys.
314         "
315         " The linter_name will be set on the errors so it can be used in
316         " output, filtering, etc..
317         let l:item = {
318         \   'bufnr': a:buffer,
319         \   'text': l:old_item.text,
320         \   'lnum': str2nr(l:old_item.lnum),
321         \   'col': str2nr(get(l:old_item, 'col', 0)),
322         \   'vcol': 0,
323         \   'type': get(l:old_item, 'type', 'E'),
324         \   'nr': get(l:old_item, 'nr', -1),
325         \   'linter_name': a:linter_name,
326         \}
327
328         if a:from_other_source
329             let l:item.from_other_source = 1
330         endif
331
332         if has_key(l:old_item, 'code')
333             let l:item.code = l:old_item.code
334         endif
335
336         let l:old_name = get(l:old_item, 'filename', '')
337
338         " Map parsed from output to local filesystem files.
339         if !empty(l:old_name) && !empty(l:mappings)
340             let l:old_name = ale#filename_mapping#Map(l:old_name, l:mappings)
341         endif
342
343         if !empty(l:old_name) && !ale#path#IsTempName(l:old_name)
344             " Use the filename given.
345             " Temporary files are assumed to be for this buffer,
346             " and the filename is not included then, because it looks bad
347             " in the loclist window.
348             let l:filename = l:old_name
349             let l:item.filename = l:filename
350
351             if has_key(l:old_item, 'bufnr')
352                 " If a buffer number is also given, include that too.
353                 " If Vim detects that he buffer number is valid, it will
354                 " be used instead of the filename.
355                 let l:item.bufnr = l:old_item.bufnr
356             elseif has_key(l:bufnr_map, l:filename)
357                 " Get the buffer number from the map, which can be faster.
358                 let l:item.bufnr = l:bufnr_map[l:filename]
359             else
360                 " Look up the buffer number.
361                 let l:item.bufnr = bufnr(l:filename)
362                 let l:bufnr_map[l:filename] = l:item.bufnr
363             endif
364         elseif has_key(l:old_item, 'bufnr')
365             let l:item.bufnr = l:old_item.bufnr
366         endif
367
368         if has_key(l:old_item, 'detail')
369             let l:item.detail = l:old_item.detail
370         endif
371
372         " Pass on a end_col key if set, used for highlights.
373         if has_key(l:old_item, 'end_col')
374             let l:item.end_col = str2nr(l:old_item.end_col)
375         endif
376
377         if has_key(l:old_item, 'end_lnum')
378             let l:item.end_lnum = str2nr(l:old_item.end_lnum)
379
380             " When the error ends after the end of the file, put it at the
381             " end. This is only done for the current buffer.
382             if l:item.bufnr == a:buffer && l:item.end_lnum > l:last_line_number
383                 let l:item.end_lnum = l:last_line_number
384             endif
385         endif
386
387         if has_key(l:old_item, 'sub_type')
388             let l:item.sub_type = l:old_item.sub_type
389         endif
390
391         if l:item.lnum < 1
392             " When errors appear before line 1, put them at line 1.
393             let l:item.lnum = 1
394         elseif l:item.bufnr == a:buffer && l:item.lnum > l:last_line_number
395             " When errors go beyond the end of the file, put them at the end.
396             " This is only done for the current buffer.
397             let l:item.lnum = l:last_line_number
398         elseif get(l:old_item, 'vcol', 0)
399             " Convert virtual column positions to byte positions.
400             " The positions will be off if the buffer has changed recently.
401             let l:line = getbufline(a:buffer, l:item.lnum)[0]
402
403             let l:item.col = ale#util#Col(l:line, l:item.col)
404
405             if has_key(l:item, 'end_col')
406                 let l:end_line = get(l:item, 'end_lnum', l:line) != l:line
407                 \   ? getbufline(a:buffer, l:item.end_lnum)[0]
408                 \   : l:line
409
410                 let l:item.end_col = ale#util#Col(l:end_line, l:item.end_col)
411             endif
412         endif
413
414         call add(l:new_loclist, l:item)
415     endfor
416
417     let l:type_map = get(ale#Var(a:buffer, 'type_map'), a:linter_name, {})
418
419     if !empty(l:type_map)
420         call s:RemapItemTypes(l:type_map, l:new_loclist)
421     endif
422
423     return l:new_loclist
424 endfunction
425
426 " Given part of a command, replace any % with %%, so that no characters in
427 " the string will be replaced with filenames, etc.
428 function! ale#engine#EscapeCommandPart(command_part) abort
429     " TODO: Emit deprecation warning here later.
430     return ale#command#EscapeCommandPart(a:command_part)
431 endfunction
432
433 " Run a job.
434 "
435 " Returns 1 when a job was started successfully.
436 function! s:RunJob(command, options) abort
437     if ale#command#IsDeferred(a:command)
438         let a:command.result_callback = {
439         \   command -> s:RunJob(command, a:options)
440         \}
441
442         return 1
443     endif
444
445     let l:command = a:command
446
447     if empty(l:command)
448         return 0
449     endif
450
451     let l:cwd = a:options.cwd
452     let l:executable = a:options.executable
453     let l:buffer = a:options.buffer
454     let l:linter = a:options.linter
455     let l:output_stream = a:options.output_stream
456     let l:read_buffer = a:options.read_buffer && !a:options.lint_file
457     let l:info = g:ale_buffer_info[l:buffer]
458
459     let l:Callback = function('s:HandleExit', [{
460     \   'linter': l:linter,
461     \   'executable': l:executable,
462     \}])
463     let l:result = ale#command#Run(l:buffer, l:command, l:Callback, {
464     \   'cwd': l:cwd,
465     \   'output_stream': l:output_stream,
466     \   'executable': l:executable,
467     \   'read_buffer': l:read_buffer,
468     \   'log_output': 1,
469     \   'filename_mappings': ale#GetFilenameMappings(l:buffer, l:linter.name),
470     \})
471
472     " Only proceed if the job is being run.
473     if empty(l:result)
474         return 0
475     endif
476
477     call ale#engine#MarkLinterActive(l:info, l:linter)
478
479     silent doautocmd <nomodeline> User ALEJobStarted
480
481     return 1
482 endfunction
483
484 function! s:StopCurrentJobs(buffer, clear_lint_file_jobs, linter_slots) abort
485     let l:info = get(g:ale_buffer_info, a:buffer, {})
486     call ale#command#StopJobs(a:buffer, 'linter')
487
488     " Update the active linter list, clearing out anything not running.
489     if a:clear_lint_file_jobs
490         call ale#command#StopJobs(a:buffer, 'file_linter')
491         let l:info.active_linter_list = []
492     else
493         let l:lint_file_map = {}
494
495         " Use a previously computed map of `lint_file` values to find
496         " linters that are used for linting files.
497         for [l:lint_file, l:linter] in a:linter_slots
498             if l:lint_file is 1
499                 let l:lint_file_map[l:linter.name] = 1
500             endif
501         endfor
502
503         " Keep jobs for linting files when we're only linting buffers.
504         call filter(l:info.active_linter_list, 'get(l:lint_file_map, v:val.name)')
505     endif
506 endfunction
507
508 function! ale#engine#Stop(buffer) abort
509     call s:StopCurrentJobs(a:buffer, 1, [])
510 endfunction
511
512 function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort
513     " Figure out which linters are still enabled, and remove
514     " problems for linters which are no longer enabled.
515     " Problems from other sources will be kept.
516     let l:name_map = {}
517
518     for l:linter in a:linters
519         let l:name_map[l:linter.name] = 1
520     endfor
521
522     call filter(
523     \   get(g:ale_buffer_info[a:buffer], 'loclist', []),
524     \   'get(v:val, ''from_other_source'') || get(l:name_map, get(v:val, ''linter_name''))',
525     \)
526 endfunction
527
528 function! s:AddProblemsFromOtherBuffers(buffer, linters) abort
529     let l:filename = expand('#' . a:buffer . ':p')
530     let l:loclist = []
531     let l:name_map = {}
532
533     " Build a map of the active linters.
534     for l:linter in a:linters
535         let l:name_map[l:linter.name] = 1
536     endfor
537
538     " Find the items from other buffers, for the linters that are enabled.
539     for l:info in values(g:ale_buffer_info)
540         for l:item in l:info.loclist
541             if has_key(l:item, 'filename')
542             \&& l:item.filename is# l:filename
543             \&& has_key(l:name_map, l:item.linter_name)
544                 " Copy the items and set the buffer numbers to this one.
545                 let l:new_item = copy(l:item)
546                 let l:new_item.bufnr = a:buffer
547                 call add(l:loclist, l:new_item)
548             endif
549         endfor
550     endfor
551
552     if !empty(l:loclist)
553         call sort(l:loclist, function('ale#util#LocItemCompareWithText'))
554         call uniq(l:loclist, function('ale#util#LocItemCompareWithText'))
555
556         " Set the loclist variable, used by some parts of ALE.
557         let g:ale_buffer_info[a:buffer].loclist = l:loclist
558         call ale#engine#SetResults(a:buffer, l:loclist)
559     endif
560 endfunction
561
562 function! s:RunIfExecutable(buffer, linter, lint_file, executable) abort
563     if ale#command#IsDeferred(a:executable)
564         let a:executable.result_callback = {
565         \   executable -> s:RunIfExecutable(
566         \       a:buffer,
567         \       a:linter,
568         \       a:lint_file,
569         \       executable
570         \   )
571         \}
572
573         return 1
574     endif
575
576     if ale#engine#IsExecutable(a:buffer, a:executable)
577         " Use different job types for file or linter jobs.
578         let l:job_type = a:lint_file ? 'file_linter' : 'linter'
579         call setbufvar(a:buffer, 'ale_job_type', l:job_type)
580
581         " Get the cwd for the linter and set it before we call GetCommand.
582         " This will ensure that ale#command#Run uses it by default.
583         let l:cwd = ale#linter#GetCwd(a:buffer, a:linter)
584
585         if l:cwd isnot v:null
586             call ale#command#SetCwd(a:buffer, l:cwd)
587         endif
588
589         let l:command = ale#linter#GetCommand(a:buffer, a:linter)
590
591         if l:cwd isnot v:null
592             call ale#command#ResetCwd(a:buffer)
593         endif
594
595         let l:options = {
596         \   'cwd': l:cwd,
597         \   'executable': a:executable,
598         \   'buffer': a:buffer,
599         \   'linter': a:linter,
600         \   'output_stream': get(a:linter, 'output_stream', 'stdout'),
601         \   'read_buffer': a:linter.read_buffer,
602         \   'lint_file': a:lint_file,
603         \}
604
605         return s:RunJob(l:command, l:options)
606     endif
607
608     return 0
609 endfunction
610
611 " Run a linter for a buffer.
612 "
613 " Returns 1 if the linter was successfully run.
614 function! s:RunLinter(buffer, linter, lint_file) abort
615     if !empty(a:linter.lsp)
616         return ale#lsp_linter#CheckWithLSP(a:buffer, a:linter)
617     else
618         let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
619
620         return s:RunIfExecutable(a:buffer, a:linter, a:lint_file, l:executable)
621     endif
622
623     return 0
624 endfunction
625
626 function! s:GetLintFileSlots(buffer, linters) abort
627     let l:linter_slots = []
628
629     for l:linter in a:linters
630         let l:LintFile = l:linter.lint_file
631
632         if type(l:LintFile) is v:t_func
633             let l:LintFile = l:LintFile(a:buffer)
634         endif
635
636         call add(l:linter_slots, [l:LintFile, l:linter])
637     endfor
638
639     return l:linter_slots
640 endfunction
641
642 function! s:GetLintFileValues(slots, Callback) abort
643     let l:deferred_list = []
644     let l:new_slots = []
645
646     for [l:lint_file, l:linter] in a:slots
647         while ale#command#IsDeferred(l:lint_file) && has_key(l:lint_file, 'value')
648             " If we've already computed the return value, use it.
649             let l:lint_file = l:lint_file.value
650         endwhile
651
652         if ale#command#IsDeferred(l:lint_file)
653             " If we are going to return the result later, wait for it.
654             call add(l:deferred_list, l:lint_file)
655         else
656             " If we have the value now, coerce it to 0 or 1.
657             let l:lint_file = l:lint_file is 1
658         endif
659
660         call add(l:new_slots, [l:lint_file, l:linter])
661     endfor
662
663     if !empty(l:deferred_list)
664         for l:deferred in l:deferred_list
665             let l:deferred.result_callback =
666             \   {-> s:GetLintFileValues(l:new_slots, a:Callback)}
667         endfor
668     else
669         call a:Callback(l:new_slots)
670     endif
671 endfunction
672
673 function! s:RunLinters(
674 \   buffer,
675 \   linters,
676 \   slots,
677 \   should_lint_file,
678 \   new_buffer,
679 \) abort
680     call s:StopCurrentJobs(a:buffer, a:should_lint_file, a:slots)
681     call s:RemoveProblemsForDisabledLinters(a:buffer, a:linters)
682
683     " We can only clear the results if we aren't checking the buffer.
684     let l:can_clear_results = !ale#engine#IsCheckingBuffer(a:buffer)
685
686     silent doautocmd <nomodeline> User ALELintPre
687
688     for [l:lint_file, l:linter] in a:slots
689         " Only run lint_file linters if we should.
690         if !l:lint_file || a:should_lint_file
691             if s:RunLinter(a:buffer, l:linter, l:lint_file)
692                 " If a single linter ran, we shouldn't clear everything.
693                 let l:can_clear_results = 0
694             endif
695         else
696             " If we skipped running a lint_file linter still in the list,
697             " we shouldn't clear everything.
698             let l:can_clear_results = 0
699         endif
700     endfor
701
702     " Clear the results if we can. This needs to be done when linters are
703     " disabled, or ALE itself is disabled.
704     if l:can_clear_results
705         call ale#engine#SetResults(a:buffer, [])
706     elseif a:new_buffer
707         call s:AddProblemsFromOtherBuffers(
708         \   a:buffer,
709         \   map(copy(a:slots), 'v:val[1]')
710         \)
711     endif
712 endfunction
713
714 function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort
715     " Initialise the buffer information if needed.
716     let l:new_buffer = ale#engine#InitBufferInfo(a:buffer)
717
718     call s:GetLintFileValues(
719     \   s:GetLintFileSlots(a:buffer, a:linters),
720     \   {
721     \       slots -> s:RunLinters(
722     \           a:buffer,
723     \           a:linters,
724     \           slots,
725     \           a:should_lint_file,
726     \           l:new_buffer,
727     \       )
728     \   }
729     \)
730 endfunction
731
732 " Clean up a buffer.
733 "
734 " This function will stop all current jobs for the buffer,
735 " clear the state of everything, and remove the Dictionary for managing
736 " the buffer.
737 function! ale#engine#Cleanup(buffer) abort
738     " Don't bother with cleanup code when newer NeoVim versions are exiting.
739     if get(v:, 'exiting', v:null) isnot v:null
740         return
741     endif
742
743     if exists('*ale#lsp#CloseDocument')
744         call ale#lsp#CloseDocument(a:buffer)
745     endif
746
747     if !has_key(g:ale_buffer_info, a:buffer)
748         return
749     endif
750
751     call ale#engine#RunLinters(a:buffer, [], 1)
752
753     call remove(g:ale_buffer_info, a:buffer)
754 endfunction
755
756 " Given a buffer number, return the warnings and errors for a given buffer.
757 function! ale#engine#GetLoclist(buffer) abort
758     if !has_key(g:ale_buffer_info, a:buffer)
759         return []
760     endif
761
762     return g:ale_buffer_info[a:buffer].loclist
763 endfunction