diff options
author | Bjorn Neergaard <bjorn@neersighted.com> | 2016-10-10 13:51:29 -0500 |
---|---|---|
committer | w0rp <w0rp@users.noreply.github.com> | 2016-10-10 19:51:29 +0100 |
commit | 7f0ce89d2b574fd5bdd0c050eaad92deeb63086d (patch) | |
tree | c508480ff3ccbf36ef7283610fdcaefe476ae6e1 /plugin | |
parent | 0680f875fe9ef07daceb3e9a90224bee613730df (diff) | |
download | ale-7f0ce89d2b574fd5bdd0c050eaad92deeb63086d.zip |
First pass at optimizing ale to autoload (#80)
* First pass at optimizing ale to autoload
First off, the structure/function names should be revised a bit,
but I will wait for @w0rp's input before unifying the naming style.
Second off, the docs probably need some more work, I just did some
simple find-and-replace work.
With that said, this pull brings major performance gains for ale. On my
slowest system, fully loading ale and all its code takes around 150ms.
I have moved all of ale's autoload-able code to autoload/, and in
addition, implemented lazy-loading of linters. This brings load time on
that same system down to 5ms.
The only downside of lazy loading is that `g:ale_linters` cannot be
changed at runtime; however, it also speeds up performance at runtime by
simplfying the logic greatly.
Please let me know what you think!
Closes #59
* Address Travis/Vint errors
For some reason, ale isn't running vint for me...
* Incorporate feedback, make fixes
Lazy-loading logic is much improved.
* Add header comments; remove incorrect workaround
* Remove unneeded plugin guards
* Fix lazy-loading linter logic
Set the wrong variable....
* Fix capitialization
Diffstat (limited to 'plugin')
-rw-r--r-- | plugin/ale.vim (renamed from plugin/ale/aaflags.vim) | 58 | ||||
-rw-r--r-- | plugin/ale/cursor.vim | 125 | ||||
-rw-r--r-- | plugin/ale/handlers.vim | 82 | ||||
-rw-r--r-- | plugin/ale/sign.vim | 143 | ||||
-rw-r--r-- | plugin/ale/statusline.vim | 39 | ||||
-rw-r--r-- | plugin/ale/util.vim | 43 | ||||
-rw-r--r-- | plugin/ale/zmain.vim | 437 |
7 files changed, 53 insertions, 874 deletions
diff --git a/plugin/ale/aaflags.vim b/plugin/ale.vim index 56ed4175..3982ee69 100644 --- a/plugin/ale/aaflags.vim +++ b/plugin/ale.vim @@ -1,12 +1,11 @@ " Author: w0rp <devw0rp@gmail.com> -" Description: This file sets up configuration settings for the ALE plugin. -" Flags can be set in vimrc files and so on to disable particular features +" Description: Main entry point for the plugin: sets up prefs and autocommands +" Preferences can be set in vimrc files and so on to configure ale -if exists('g:loaded_ale_flags') +if exists('g:loaded_ale') finish endif - -let g:loaded_ale_flags = 1 +let g:loaded_ale = 1 " A flag for detecting if the required features are set. if has('nvim') @@ -15,6 +14,15 @@ else let g:ale_has_required_features = has('timers') && has('job') && has('channel') endif +if !g:ale_has_required_features + echoerr 'ALE requires NeoVim >= 0.1.5 or Vim 8 with +timers +job +channel' + echoerr 'Please update your editor appropriately.' + finish +endif + +" This list configures which linters are enabled for which languages. +let g:ale_linters = get(g:, 'ale_linters', {}) + " This flag can be set to 0 to disable linting when text is changed. let g:ale_lint_on_text_changed = get(g:, 'ale_lint_on_text_changed', 1) @@ -64,3 +72,43 @@ let g:ale_echo_msg_format = get(g:, 'ale_echo_msg_format', '%s') " Strings used for severity in the echoed message let g:ale_echo_msg_error_str = get(g:, 'ale_echo_msg_error_str', 'Error') let g:ale_echo_msg_warning_str = get(g:, 'ale_echo_msg_warning_str', 'Warning') + +if g:ale_lint_on_text_changed + augroup ALERunOnTextChangedGroup + autocmd! + autocmd TextChanged,TextChangedI * call ale#Queue(g:ale_lint_delay) + augroup END +endif + +if g:ale_lint_on_enter + augroup ALERunOnEnterGroup + autocmd! + autocmd BufEnter,BufRead * call ale#Queue(100) + augroup END +endif + +if g:ale_lint_on_save + augroup ALERunOnSaveGroup + autocmd! + autocmd BufWrite * call ale#Queue(0) + augroup END +endif + +" Clean up buffers automatically when they are unloaded. +augroup ALEBufferCleanup + autocmd! + autocmd BufUnload * call ale#cleanup#Buffer('<abuf>') +augroup END + +" Globals which each part of the plugin should use. +let g:ale_buffer_loclist_map = {} +let g:ale_buffer_should_reset_map = {} +let g:ale_buffer_sign_dummy_map = {} + +" Backwards compatibility +function! ALELint(delay) + call ale#Queue(a:delay) +endfunction +function! ALEGetStatusLine() + call ale#statusline#Status() +endfunction diff --git a/plugin/ale/cursor.vim b/plugin/ale/cursor.vim deleted file mode 100644 index a6d23979..00000000 --- a/plugin/ale/cursor.vim +++ /dev/null @@ -1,125 +0,0 @@ -" Author: w0rp <devw0rp@gmail.com> -" Description: Echoes lint message for the current line, if any - -if exists('g:loaded_ale_cursor') - finish -endif - -let g:loaded_ale_cursor = 1 - -" Return a formatted message according to g:ale_echo_msg_format variable -function! s:GetMessage(linter, type, text) abort - let msg = g:ale_echo_msg_format - let type = a:type ==# 'E' - \ ? g:ale_echo_msg_error_str - \ : g:ale_echo_msg_warning_str - " Capitalize the 1st character - let text = toupper(a:text[0]) . a:text[1:-1] - - " Replace handlers if they exist - for [k, v] in items({'linter': a:linter, 'severity': type}) - let msg = substitute(msg, '\V%' . k . '%', v, '') - endfor - - return printf(msg, text) -endfunction - -" This function will perform a binary search to find a message from the -" loclist to echo when the cursor moves. -function! s:BinarySearch(loclist, line, column) - let min = 0 - let max = len(a:loclist) - 1 - let last_column_match = -1 - - while 1 - if max < min - return last_column_match - endif - - let mid = (min + max) / 2 - let obj = a:loclist[mid] - - " Binary search to get on the same line - if a:loclist[mid]['lnum'] < a:line - let min = mid + 1 - elseif a:loclist[mid]['lnum'] > a:line - let max = mid - 1 - else - let last_column_match = mid - - " Binary search to get the same column, or near it - if a:loclist[mid]['col'] < a:column - let min = mid + 1 - elseif a:loclist[mid]['col'] > a:column - let max = mid - 1 - else - return mid - endif - endif - endwhile -endfunction - -function! ale#cursor#TruncatedEcho(message) - let message = a:message - " Change tabs to spaces. - let message = substitute(message, "\t", ' ', 'g') - " Remove any newlines in the message. - let message = substitute(message, "\n", '', 'g') - - " We need to turn T for truncated messages on for shortmess, - " and then then we need to reset the option back to what it was. - let shortmess_options = getbufvar('%', '&shortmess') - - try - " Echo the message truncated to fit without creating a prompt. - setlocal shortmess+=T - exec "norm :echomsg message\n" - finally - call setbufvar('%', '&shortmess', shortmess_options) - endtry -endfunction - -function! ale#cursor#EchoCursorWarning(...) - " Only echo the warnings in normal mode, otherwise we will get problems. - if mode() !=# 'n' - return - endif - - let buffer = bufnr('%') - - if !has_key(g:ale_buffer_loclist_map, buffer) - return - endif - - let loclist = g:ale_buffer_loclist_map[buffer] - - let pos = getcurpos() - - let index = s:BinarySearch(loclist, pos[1], pos[2]) - - if index >= 0 - let l = loclist[index] - let msg = s:GetMessage(l.linter_name, l.type, l.text) - call ale#cursor#TruncatedEcho(msg) - else - echo - endif -endfunction - -let s:cursor_timer = -1 - -function! ale#cursor#EchoCursorWarningWithDelay() - if s:cursor_timer != -1 - call timer_stop(s:cursor_timer) - let s:cursor_timer = -1 - endif - - let s:cursor_timer = timer_start(10, function('ale#cursor#EchoCursorWarning')) -endfunction - -if g:ale_has_required_features && g:ale_echo_cursor - augroup ALECursorGroup - autocmd! - autocmd CursorMoved,CursorHold * call ale#cursor#EchoCursorWarningWithDelay() - augroup END -endif diff --git a/plugin/ale/handlers.vim b/plugin/ale/handlers.vim deleted file mode 100644 index a348ad30..00000000 --- a/plugin/ale/handlers.vim +++ /dev/null @@ -1,82 +0,0 @@ -scriptencoding utf-8 -" Author: w0rp <devw0rp@gmail.com> -" Description: This file defines some standard error format handlers. Any -" linter which outputs warnings and errors in a format accepted by one of -" these functions can simply use one of these pre-defined error handlers. - -if exists('g:loaded_ale_handlers') - finish -endif - -let g:loaded_ale_handlers = 1 - -function! ale#handlers#HandleGCCFormat(buffer, lines) - " Look for lines like the following. - " - " <stdin>:8:5: warning: conversion lacks type at end of format [-Wformat=] - " <stdin>:10:27: error: invalid operands to binary - (have ‘int’ and ‘char *’) - " -:189:7: note: $/${} is unnecessary on arithmetic variables. [SC2004] - let pattern = '^.\+:\(\d\+\):\(\d\+\): \([^:]\+\): \(.\+\)$' - let output = [] - - for line in a:lines - let l:match = matchlist(line, pattern) - - if len(l:match) == 0 - continue - endif - - call add(output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:match[1] + 0, - \ 'vcol': 0, - \ 'col': l:match[2] + 0, - \ 'text': l:match[4], - \ 'type': l:match[3] ==# 'error' ? 'E' : 'W', - \ 'nr': -1, - \}) - endfor - - return output -endfunction - -function! ale#handlers#HandleCSSLintFormat(buffer, lines) - " Matches patterns line the following: - " - " something.css: line 2, col 1, Error - Expected RBRACE at line 2, col 1. (errors) - " something.css: line 2, col 5, Warning - Expected (inline | block | list-item | inline-block | table | inline-table | table-row-group | table-header-group | table-footer-group | table-row | table-column-group | table-column | table-cell | table-caption | grid | inline-grid | run-in | ruby | ruby-base | ruby-text | ruby-base-container | ruby-text-container | contents | none | -moz-box | -moz-inline-block | -moz-inline-box | -moz-inline-grid | -moz-inline-stack | -moz-inline-table | -moz-grid | -moz-grid-group | -moz-grid-line | -moz-groupbox | -moz-deck | -moz-popup | -moz-stack | -moz-marker | -webkit-box | -webkit-inline-box | -ms-flexbox | -ms-inline-flexbox | flex | -webkit-flex | inline-flex | -webkit-inline-flex) but found 'wat'. (known-properties) - " - " These errors can be very massive, so the type will be moved to the front - " so you can actually read the error type. - let pattern = '^.*: line \(\d\+\), col \(\d\+\), \(Error\|Warning\) - \(.\+\) (\([^)]\+\))$' - let output = [] - - for line in a:lines - let l:match = matchlist(line, pattern) - - if len(l:match) == 0 - continue - endif - - let text = l:match[4] - let type = l:match[3] - let errorGroup = l:match[5] - - " Put the error group at the front, so we can see what kind of error - " it is on small echo lines. - let text = '(' . errorGroup . ') ' . text - - " vcol is Needed to indicate that the column is a character. - call add(output, { - \ 'bufnr': a:buffer, - \ 'lnum': l:match[1] + 0, - \ 'vcol': 0, - \ 'col': l:match[2] + 0, - \ 'text': text, - \ 'type': type ==# 'Warning' ? 'W' : 'E', - \ 'nr': -1, - \}) - endfor - - return output -endfunction diff --git a/plugin/ale/sign.vim b/plugin/ale/sign.vim deleted file mode 100644 index 5950ae99..00000000 --- a/plugin/ale/sign.vim +++ /dev/null @@ -1,143 +0,0 @@ -scriptencoding utf-8 -" Author: w0rp <devw0rp@gmail.com> -" Description: Draws error and warning signs into signcolumn - -if exists('g:loaded_ale_sign') - finish -endif - -let g:loaded_ale_sign = 1 -let b:dummy_sign_set_map = {} - -if !hlexists('ALEErrorSign') - highlight link ALEErrorSign error -endif - -if !hlexists('ALEWarningSign') - highlight link ALEWarningSign todo -endif - -if !hlexists('ALEError') - highlight link ALEError SpellBad -endif - -if !hlexists('ALEWarning') - highlight link ALEWarning SpellCap -endif - -" Global variables for signs -let g:ale_sign_error = get(g:, 'ale_sign_error', '>>') -let g:ale_sign_warning = get(g:, 'ale_sign_warning', '--') -" An offset which can be set for sign IDs. -" This ID can be changed depending on what IDs are set for other plugins. -" The dummy sign will use the ID exactly equal to the offset. -let g:ale_sign_offset = get(g:, 'ale_sign_offset', 1000000) - -" Signs show up on the left for error markers. -execute 'sign define ALEErrorSign text=' . g:ale_sign_error -\ . ' texthl=ALEErrorSign' -execute 'sign define ALEWarningSign text=' . g:ale_sign_warning -\ . ' texthl=ALEWarningSign' -sign define ALEDummySign - -function! ale#sign#FindCurrentSigns(buffer) - " Matches output like : - " line=4 id=1 name=ALEErrorSign - " строка=1 id=1000001 имя=ALEErrorSign - let pattern = 'id=\(\d\+\).*=ALE\(Warning\|Error\)Sign' - - redir => output - silent exec 'sign place buffer=' . a:buffer - redir END - - let id_list = [] - - for line in split(output, "\n") - let match = matchlist(line, pattern) - - if len(match) > 0 - call add(id_list, match[1] + 0) - endif - endfor - - return id_list -endfunction - -" Given a loclist, combine the loclist into a list of signs such that only -" one sign appears per line. Error lines will take precedence. -" The loclist will have been previously sorted. -function! ale#sign#CombineSigns(loclist) - let signlist = [] - - for obj in a:loclist - let should_append = 1 - - if obj.lnum < 1 - " Skip warnings and errors at line 0, etc. - continue - endif - - if len(signlist) > 0 && signlist[-1].lnum == obj.lnum - " We can't add the same line twice, because signs must be - " unique per line. - let should_append = 0 - - if signlist[-1].type ==# 'W' && obj.type ==# 'E' - " If we had a warning previously, but now have an error, - " we replace the object to set an error instead. - let signlist[-1] = obj - endif - endif - - if should_append - call add(signlist, obj) - endif - endfor - - return signlist -endfunction - -" This function will set the signs which show up on the left. -function! ale#sign#SetSigns(buffer, loclist) - let signlist = ale#sign#CombineSigns(a:loclist) - - if len(signlist) > 0 || g:ale_sign_column_always - if !get(g:ale_buffer_sign_dummy_map, a:buffer, 0) - " Insert a dummy sign if one is missing. - execute 'sign place ' . g:ale_sign_offset - \ . ' line=1 name=ALEDummySign buffer=' - \ . a:buffer - - let g:ale_buffer_sign_dummy_map[a:buffer] = 1 - endif - endif - - " Find the current signs with the markers we use. - let current_id_list = ale#sign#FindCurrentSigns(a:buffer) - - " Remove those markers. - for current_id in current_id_list - exec 'sign unplace ' . current_id . ' buffer=' . a:buffer - endfor - - " Now set all of the signs. - for i in range(0, len(signlist) - 1) - let obj = signlist[i] - let name = obj['type'] ==# 'W' ? 'ALEWarningSign' : 'ALEErrorSign' - - let sign_line = 'sign place ' . (i + g:ale_sign_offset + 1) - \. ' line=' . obj['lnum'] - \. ' name=' . name - \. ' buffer=' . a:buffer - - exec sign_line - endfor - - if !g:ale_sign_column_always && len(signlist) > 0 - if get(g:ale_buffer_sign_dummy_map, a:buffer, 0) - execute 'sign unplace ' . g:ale_sign_offset . ' buffer=' . a:buffer - - let g:ale_buffer_sign_dummy_map[a:buffer] = 0 - endif - endif -endfunction diff --git a/plugin/ale/statusline.vim b/plugin/ale/statusline.vim deleted file mode 100644 index baef418a..00000000 --- a/plugin/ale/statusline.vim +++ /dev/null @@ -1,39 +0,0 @@ -" Author: KabbAmine <amine.kabb@gmail.com> -" Description: Statusline related function(s) - -function! ALEGetStatusLine() abort - " Returns a formatted string that can be integrated in the - " statusline - - let buf = bufnr('%') - let bufLoclist = g:ale_buffer_loclist_map - - if !has_key(bufLoclist, buf) - return '' - endif - - let errors = 0 - let warnings = 0 - for e in bufLoclist[buf] - if e.type ==# 'E' - let errors += 1 - else - let warnings += 1 - endif - endfor - - let errors = errors ? printf(g:ale_statusline_format[0], errors) : '' - let warnings = warnings ? printf(g:ale_statusline_format[1], warnings) : '' - let no_errors = g:ale_statusline_format[2] - - " Different formats if no errors or no warnings - if empty(errors) && empty(warnings) - let res = no_errors - elseif !empty(errors) && !empty(warnings) - let res = printf('%s %s', errors, warnings) - else - let res = empty(errors) ? warnings : errors - endif - - return res -endfunction diff --git a/plugin/ale/util.vim b/plugin/ale/util.vim deleted file mode 100644 index fd9bb356..00000000 --- a/plugin/ale/util.vim +++ /dev/null @@ -1,43 +0,0 @@ -" Author: w0rp <devw0rp@gmail.com> -" Description: Contains miscellaneous functions - -if exists('g:loaded_ale_util') - finish -endif - -let g:loaded_ale_util = 1 - -function! s:FindWrapperScript() - for parent in split(&runtimepath, ',') - " Expand the path to deal with ~ issues. - let path = expand(parent . '/' . 'stdin-wrapper') - - if filereadable(path) - if has('win32') - return path . '.exe' - endif - - return path - endif - endfor -endfunction - -let g:ale#util#stdin_wrapper = s:FindWrapperScript() - -" Return the number of lines for a given buffer. -function! ale#util#GetLineCount(buffer) - return len(getbufline(a:buffer, 1, '$')) -endfunction - -" Given a buffer and a filename, find the nearest file by searching upwards -" through the paths relative to the given buffer. -function! ale#util#FindNearestFile(buffer, filename) - return findfile(a:filename, fnamemodify(bufname(a:buffer), ':p') . ';') -endfunction - -" A null file for sending output to nothing. -let g:ale#util#nul_file = '/dev/null' - -if has('win32') - let g:ale#util#nul_file = 'nul' -endif diff --git a/plugin/ale/zmain.vim b/plugin/ale/zmain.vim deleted file mode 100644 index 16b0a476..00000000 --- a/plugin/ale/zmain.vim +++ /dev/null @@ -1,437 +0,0 @@ -" Author: w0rp <devw0rp@gmail.com> -" Description: Main entry point for this plugin -" Loads linters and manages lint jobs - -if exists('g:loaded_ale_zmain') - finish -endif - -let g:loaded_ale_zmain = 1 - -let s:lint_timer = -1 -let s:linters = {} - -if !exists('g:ale_linters') - let g:ale_linters = {} -endif - -" 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. -let s:job_info_map = {} - -" Globals which each part of the plugin should use. -let g:ale_buffer_loclist_map = {} -let g:ale_buffer_should_reset_map = {} -let g:ale_buffer_sign_dummy_map = {} - -function! s:GetFunction(string_or_ref) - if type(a:string_or_ref) == type('') - return function(a:string_or_ref) - endif - - return a:string_or_ref -endfunction - -function! s:ClearJob(job) - let job_id = s:GetJobID(a:job) - let linter = s:job_info_map[job_id].linter - - if has('nvim') - call jobstop(a:job) - else - " We must close the channel for reading the buffer if it is open - " when stopping a job. Otherwise, we will get errors in the status line. - if ch_status(job_getchannel(a:job)) ==# 'open' - call ch_close_in(job_getchannel(a:job)) - endif - - call job_stop(a:job) - endif - - call remove(s:job_info_map, job_id) - call remove(linter, 'job') -endfunction - -function! s:GatherOutput(job, data) - let job_id = s:GetJobID(a:job) - - if !has_key(s:job_info_map, job_id) - return - endif - - call extend(s:job_info_map[job_id].output, a:data) -endfunction - -function! s:GatherOutputNeoVim(job, data, event) - call s:GatherOutput(a:job, a:data) -endfunction - -function! s:GatherOutputVim(channel, data) - call s:GatherOutput(ch_getjob(a:channel), [a:data]) -endfunction - -function! s:LocItemCompare(left, right) - if a:left['lnum'] < a:right['lnum'] - return -1 - endif - - if a:left['lnum'] > a:right['lnum'] - return 1 - endif - - if a:left['col'] < a:right['col'] - return -1 - endif - - if a:left['col'] > a:right['col'] - return 1 - endif - - return 0 -endfunction - -function! s:FixLoclist(buffer, loclist) - " Some errors have line numbers beyond the end of the file, - " so we need to adjust them so they set the error at the last line - " of the file instead. - let last_line_number = ale#util#GetLineCount(a:buffer) - - for item in a:loclist - if item.lnum == 0 - " When errors appear at line 0, put them at line 1 instead. - let item.lnum = 1 - elseif item.lnum > last_line_number - let item.lnum = last_line_number - endif - endfor -endfunction - -function! s:HandleExit(job) - if a:job ==# 'no process' - " Stop right away when the job is not valid in Vim 8. - return - endif - - let job_id = s:GetJobID(a:job) - - if !has_key(s:job_info_map, job_id) - return - endif - - let job_info = s:job_info_map[job_id] - - call s:ClearJob(a:job) - - let linter = job_info.linter - let output = job_info.output - let buffer = job_info.buffer - - let linter_loclist = s:GetFunction(linter.callback)(buffer, output) - - " Make some adjustments to the loclists to fix common problems. - call s:FixLoclist(buffer, linter_loclist) - - " Remember which linter returned these items for later use. - for obj in linter_loclist - let obj.linter_name = linter.name - endfor - - if g:ale_buffer_should_reset_map[buffer] - let g:ale_buffer_should_reset_map[buffer] = 0 - let g:ale_buffer_loclist_map[buffer] = [] - endif - - " Add the loclist items from the linter. - call extend(g:ale_buffer_loclist_map[buffer], linter_loclist) - - " Sort the loclist again. - " We need a sorted list so we can run a binary search against it - " for efficient lookup of the messages in the cursor handler. - call sort(g:ale_buffer_loclist_map[buffer], 's:LocItemCompare') - - if g:ale_set_loclist - call setloclist(0, g:ale_buffer_loclist_map[buffer]) - endif - - if g:ale_set_signs - call ale#sign#SetSigns(buffer, g:ale_buffer_loclist_map[buffer]) - endif - - " Mark line 200, column 17 with a squiggly line or something - " matchadd('ALEError', '\%200l\%17v') -endfunction - -function! s:GetJobID(job) - if has('nvim') - "In NeoVim, job values are just IDs. - return a:job - endif - - " In Vim 8, the job is a special variable, and we open a channel for each - " job. We'll use the ID of the channel instead as the job ID. - return ch_info(job_getchannel(a:job)).id -endfunction - -function! s:HandleExitNeoVim(job, data, event) - call s:HandleExit(a:job) -endfunction - -function! s:HandleExitVim(channel) - call s:HandleExit(ch_getjob(a:channel)) -endfunction - -function! s:ApplyLinter(buffer, linter) - if has_key(a:linter, 'job') - " Stop previous jobs for the same linter. - call s:ClearJob(a:linter.job) - endif - - if has_key(a:linter, 'command_callback') - " If there is a callback for generating a command, call that instead. - let command = s:GetFunction(a:linter.command_callback)(a:buffer) - else - let command = a:linter.command - endif - - if command =~# '%s' - " If there is a '%s' in the command string, replace it with the name - " of the file. - let command = printf(command, shellescape(fnamemodify(bufname(a:buffer), ':p'))) - endif - - if has('nvim') - if a:linter.output_stream ==# 'stderr' - " Read from stderr instead of stdout. - let job = jobstart(command, { - \ 'on_stderr': 's:GatherOutputNeoVim', - \ 'on_exit': 's:HandleExitNeoVim', - \}) - elseif a:linter.output_stream ==# 'both' - let job = jobstart(command, { - \ 'on_stdout': 's:GatherOutputNeoVim', - \ 'on_stderr': 's:GatherOutputNeoVim', - \ 'on_exit': 's:HandleExitNeoVim', - \}) - else - let job = jobstart(command, { - \ 'on_stdout': 's:GatherOutputNeoVim', - \ 'on_exit': 's:HandleExitNeoVim', - \}) - endif - else - let job_options = { - \ 'in_mode': 'nl', - \ 'out_mode': 'nl', - \ 'err_mode': 'nl', - \ 'close_cb': function('s:HandleExitVim'), - \} - - if a:linter.output_stream ==# 'stderr' - " Read from stderr instead of stdout. - let job_options.err_cb = function('s:GatherOutputVim') - elseif a:linter.output_stream ==# 'both' - " Read from both streams. - let job_options.out_cb = function('s:GatherOutputVim') - let job_options.err_cb = function('s:GatherOutputVim') - else - let job_options.out_cb = function('s:GatherOutputVim') - endif - - if has('win32') - " job_start commands on Windows have to be run with cmd /c, - " othwerwise %PATHTEXT% will not be used to programs ending int - " .cmd, .bat, .exe, etc. - let l:command = 'cmd /c ' . l:command - else - " On Unix machines, we can send the Vim buffer directly. - " This is faster than reading the lines ourselves. - let job_options.in_io = 'buffer' - let job_options.in_buf = a:buffer - endif - - " Vim 8 will read the stdin from the file's buffer. - let job = job_start(l:command, l:job_options) - endif - - " Only proceed if the job is being run. - if has('nvim') || (job !=# 'no process' && job_status(job) ==# 'run') - let a:linter.job = job - - " Store the ID for the job in the map to read back again. - let s:job_info_map[s:GetJobID(job)] = { - \ 'linter': a:linter, - \ 'buffer': a:buffer, - \ 'output': [], - \} - - if has('nvim') - " In NeoVim, we have to send the buffer lines ourselves. - let input = join(getbufline(a:buffer, 1, '$'), "\n") . "\n" - - call jobsend(job, input) - call jobclose(job, 'stdin') - elseif has('win32') - " On Windows, we have to send the buffer lines ourselves, - " as there are issues with Windows and 'in_buf' - let input = join(getbufline(a:buffer, 1, '$'), "\n") . "\n" - let channel = job_getchannel(job) - - if ch_status(channel) ==# 'open' - call ch_sendraw(channel, input) - call ch_close_in(channel) - endif - endif - endif -endfunction - -function! s:TimerHandler(...) - let filetype = &filetype - let linters = ALEGetLinters(filetype) - - let buffer = bufnr('%') - - " Set a variable telling us to clear the loclist later. - let g:ale_buffer_should_reset_map[buffer] = 1 - - for linter in linters - " Check if a given linter has a program which can be executed. - if has_key(linter, 'executable_callback') - let l:executable = s:GetFunction(linter.executable_callback)(buffer) - else - let l:executable = linter.executable - endif - - if !executable(l:executable) - " The linter's program cannot be executed, so skip it. - continue - endif - - call s:ApplyLinter(buffer, linter) - endfor -endfunction - -function s:BufferCleanup(buffer) - if has_key(g:ale_buffer_should_reset_map, a:buffer) - call remove(g:ale_buffer_should_reset_map, a:buffer) - endif - - if has_key(g:ale_buffer_loclist_map, a:buffer) - call remove(g:ale_buffer_loclist_map, a:buffer) - endif - - if has_key(g:ale_buffer_sign_dummy_map, a:buffer) - call remove(g:ale_buffer_sign_dummy_map, a:buffer) - endif -endfunction - -function! ALEAddLinter(filetype, linter) - if !has_key(s:linters, a:filetype) - let s:linters[a:filetype] = [] - endif - - let new_linter = { - \ 'name': a:linter.name, - \ 'callback': a:linter.callback, - \} - - if has_key(a:linter, 'executable_callback') - let new_linter.executable_callback = a:linter.executable_callback - else - let new_linter.executable = a:linter.executable - endif - - if has_key(a:linter, 'command_callback') - let new_linter.command_callback = a:linter.command_callback - else - let new_linter.command = a:linter.command - endif - - if has_key(a:linter, 'output_stream') - let new_linter.output_stream = a:linter.output_stream - else - let new_linter.output_stream = 'stdout' - endif - - " TODO: Assert the value of the output_stream to be something sensible. - - call add(s:linters[a:filetype], new_linter) -endfunction - -function! ALEGetLinters(filetype) - if !has_key(s:linters, a:filetype) - return [] - endif - - if has_key(g:ale_linters, a:filetype) - let linters = [] - " Filter loaded linters according to list of linters specified in option - for linter in s:linters[a:filetype] - if index(g:ale_linters[a:filetype], linter.name) != -1 - call add(linters, linter) - endif - endfor - return linters - endif - - return s:linters[a:filetype] -endfunction - -function! ALELint(delay) - let filetype = &filetype - let linters = ALEGetLinters(filetype) - - if s:lint_timer != -1 - call timer_stop(s:lint_timer) - let s:lint_timer = -1 - endif - - if len(linters) == 0 - " There are no linters to lint with, so stop here. - return - endif - - if a:delay > 0 - let s:lint_timer = timer_start(a:delay, function('s:TimerHandler')) - else - call s:TimerHandler() - endif -endfunction - -" Load all of the linters for each filetype. -runtime! ale_linters/*/*.vim - -if !g:ale_has_required_features - echoerr 'ALE requires NeoVim >= 0.1.5 or Vim 8 with +timers +job +channel' - echoerr 'Please update your editor appropriately.' - finish -endif - -if g:ale_lint_on_text_changed - augroup ALERunOnTextChangedGroup - autocmd! - autocmd TextChanged,TextChangedI * call ALELint(g:ale_lint_delay) - augroup END -endif - -if g:ale_lint_on_enter - augroup ALERunOnEnterGroup - autocmd! - autocmd BufEnter,BufRead * call ALELint(100) - augroup END -endif - -if g:ale_lint_on_save - augroup ALERunOnSaveGroup - autocmd! - autocmd BufWrite * call ALELint(0) - augroup END -endif - -" Clean up buffers automatically when they are unloaded. -augroup ALEBuffferCleanup - autocmd! - autocmd BufUnload * call s:BufferCleanup('<abuf>') -augroup END |