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
This commit is contained in:
Bjorn Neergaard 2016-10-10 13:51:29 -05:00 committed by w0rp
parent 0680f875fe
commit 7f0ce89d2b
44 changed files with 337 additions and 336 deletions

View File

@ -12,7 +12,7 @@ if !exists('g:ale_c_gcc_options')
let g:ale_c_gcc_options = '-Wall'
endif
call ALEAddLinter('c', {
call ale#linter#Define('c', {
\ 'name': 'gcc',
\ 'output_stream': 'stderr',
\ 'executable': 'gcc',

View File

@ -7,7 +7,7 @@ endif
let g:loaded_ale_linters_coffee_coffee = 1
call ALEAddLinter('coffee', {
call ale#linter#Define('coffee', {
\ 'name': 'coffee',
\ 'executable': 'coffee',
\ 'command': 'coffee -cp -s',

View File

@ -44,7 +44,7 @@ function! ale_linters#coffee#coffeelint#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('coffee', {
call ale#linter#Define('coffee', {
\ 'name': 'coffeelint',
\ 'executable': 'coffeelint',
\ 'command': 'coffeelint --stdin --reporter csv',

View File

@ -12,7 +12,7 @@ if !exists('g:ale_cpp_gcc_options')
let g:ale_cpp_gcc_options = '-Wall'
endif
call ALEAddLinter('cpp', {
call ale#linter#Define('cpp', {
\ 'name': 'gcc',
\ 'output_stream': 'stderr',
\ 'executable': 'gcc',

View File

@ -7,7 +7,7 @@ endif
let g:loaded_ale_linters_css_csslint = 1
call ALEAddLinter('css', {
call ale#linter#Define('css', {
\ 'name': 'csslint',
\ 'executable': 'csslint',
\ 'command': g:ale#util#stdin_wrapper . ' .css csslint --format=compact',

View File

@ -61,7 +61,7 @@ function! ale_linters#d#dmd#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('d', {
call ale#linter#Define('d', {
\ 'name': 'dmd',
\ 'output_stream': 'stderr',
\ 'executable': 'dmd',

View File

@ -60,7 +60,7 @@ function! ale_linters#fortran#gcc#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('fortran', {
call ale#linter#Define('fortran', {
\ 'name': 'gcc',
\ 'output_stream': 'stderr',
\ 'executable': 'gcc',

View File

@ -58,7 +58,7 @@ function! ale_linters#haskell#ghc#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('haskell', {
call ale#linter#Define('haskell', {
\ 'name': 'ghc',
\ 'output_stream': 'stderr',
\ 'executable': 'ghc',
@ -66,7 +66,7 @@ call ALEAddLinter('haskell', {
\ 'callback': 'ale_linters#haskell#ghc#Handle',
\})
call ALEAddLinter('haskell', {
call ale#linter#Define('haskell', {
\ 'name': 'stack-ghc',
\ 'output_stream': 'stderr',
\ 'executable': 'stack',

View File

@ -40,7 +40,7 @@ function! ale_linters#html#htmlhint#Handle(buffer, lines) abort
return output
endfunction
call ALEAddLinter('html', {
call ale#linter#Define('html', {
\ 'name': 'htmlhint',
\ 'executable': 'htmlhint',
\ 'command': 'htmlhint --format=unix stdin',

View File

@ -71,7 +71,7 @@ function! ale_linters#html#tidy#Handle(buffer, lines) abort
return output
endfunction
call ALEAddLinter('html', {
call ale#linter#Define('html', {
\ 'name': 'tidy',
\ 'executable': g:ale_html_tidy_executable,
\ 'output_stream': 'stderr',

View File

@ -49,14 +49,14 @@ function! ale_linters#javascript#eslint#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('javascript', {
call ale#linter#Define('javascript', {
\ 'name': 'eslint',
\ 'executable': g:ale_javascript_eslint_executable,
\ 'command': g:ale_javascript_eslint_executable . ' -f unix --stdin --stdin-filename %s',
\ 'callback': 'ale_linters#javascript#eslint#Handle',
\})
call ALEAddLinter('javascript.jsx', {
call ale#linter#Define('javascript.jsx', {
\ 'name': 'eslint',
\ 'executable': g:ale_javascript_eslint_executable,
\ 'command': g:ale_javascript_eslint_executable . ' -f unix --stdin --stdin-filename %s',

View File

@ -43,14 +43,14 @@ function! ale_linters#javascript#jscs#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('javascript', {
call ale#linter#Define('javascript', {
\ 'name': 'jscs',
\ 'executable': 'jscs',
\ 'command': 'jscs -r unix -n -',
\ 'callback': 'ale_linters#javascript#jscs#Handle',
\})
call ALEAddLinter('javascript.jsx', {
call ale#linter#Define('javascript.jsx', {
\ 'name': 'jscs',
\ 'executable': 'jscs',
\ 'command': 'jscs -r unix -n -',

View File

@ -70,14 +70,14 @@ function! ale_linters#javascript#jshint#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('javascript', {
call ale#linter#Define('javascript', {
\ 'name': 'jshint',
\ 'executable': g:ale_javascript_jshint_executable,
\ 'command_callback': 'ale_linters#javascript#jshint#GetCommand',
\ 'callback': 'ale_linters#javascript#jshint#Handle',
\})
call ALEAddLinter('javascript.jsx', {
call ale#linter#Define('javascript.jsx', {
\ 'name': 'jshint',
\ 'executable': g:ale_javascript_jshint_executable,
\ 'command_callback': 'ale_linters#javascript#jshint#GetCommand',

View File

@ -35,7 +35,7 @@ function! ale_linters#json#jsonlint#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('json', {
call ale#linter#Define('json', {
\ 'name': 'jsonlint',
\ 'executable': 'jsonlint',
\ 'output_stream': 'stderr',

View File

@ -37,7 +37,7 @@ function! ale_linters#perl#perl#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('perl', {
call ale#linter#Define('perl', {
\ 'name': 'perl',
\ 'executable': 'perl',
\ 'output_stream': 'both',

View File

@ -37,7 +37,7 @@ function! ale_linters#perl#perlcritic#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('perl', {
call ale#linter#Define('perl', {
\ 'name': 'perlcritic',
\ 'executable': 'perlcritic',
\ 'output_stream': 'sdtout',

View File

@ -36,7 +36,7 @@ function! ale_linters#php#php#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('php', {
call ale#linter#Define('php', {
\ 'name': 'php',
\ 'executable': 'php',
\ 'output_stream': 'both',

View File

@ -50,7 +50,7 @@ function! ale_linters#php#phpcs#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('php', {
call ale#linter#Define('php', {
\ 'name': 'phpcs',
\ 'executable': 'phpcs',
\ 'command_callback': 'ale_linters#php#phpcs#GetCommand',

View File

@ -35,7 +35,7 @@ function! ale_linters#pug#puglint#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('pug', {
call ale#linter#Define('pug', {
\ 'name': 'puglint',
\ 'executable': 'pug-lint',
\ 'output_stream': 'stderr',

View File

@ -34,7 +34,7 @@ function! ale_linters#pyrex#cython#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('pyrex', {
call ale#linter#Define('pyrex', {
\ 'name': 'cython',
\ 'output_stream': 'stderr',
\ 'executable': 'cython',

View File

@ -47,7 +47,7 @@ function! ale_linters#python#flake8#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('python', {
call ale#linter#Define('python', {
\ 'name': 'flake8',
\ 'executable': 'flake8',
\ 'command': 'flake8 -',

View File

@ -40,7 +40,7 @@ function! ale_linters#ruby#rubocop#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('ruby', {
call ale#linter#Define('ruby', {
\ 'name': 'rubocop',
\ 'executable': 'rubocop',
\ 'command': 'rubocop --format emacs --stdin _',

View File

@ -6,7 +6,7 @@ endif
let g:loaded_ale_linters_sass_sasslint = 1
call ALEAddLinter('sass', {
call ale#linter#Define('sass', {
\ 'name': 'sasslint',
\ 'executable': 'sass-lint',
\ 'command': g:ale#util#stdin_wrapper . ' .sass sass-lint -v -q -f compact',

View File

@ -49,7 +49,7 @@ function! ale_linters#scala#scalac#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('scala', {
call ale#linter#Define('scala', {
\ 'name': 'scalac',
\ 'executable': 'scalac',
\ 'output_stream': 'stderr',

View File

@ -6,7 +6,7 @@ endif
let g:loaded_ale_linters_scss_sasslint = 1
call ALEAddLinter('scss', {
call ale#linter#Define('scss', {
\ 'name': 'sasslint',
\ 'executable': 'sass-lint',
\ 'command': g:ale#util#stdin_wrapper . ' .scss sass-lint -v -q -f compact',

View File

@ -41,7 +41,7 @@ function! ale_linters#scss#scsslint#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('scss', {
call ale#linter#Define('scss', {
\ 'name': 'scsslint',
\ 'executable': 'scss-lint',
\ 'command': 'scss-lint --stdin-file-path=%s',

View File

@ -74,7 +74,7 @@ function! ale_linters#sh#shell#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('sh', {
call ale#linter#Define('sh', {
\ 'name': 'shell',
\ 'output_stream': 'stderr',
\ 'executable_callback': 'ale_linters#sh#shell#GetExecutable',

View File

@ -22,7 +22,7 @@ else
let s:exclude_option = ''
endif
call ALEAddLinter('sh', {
call ale#linter#Define('sh', {
\ 'name': 'shellcheck',
\ 'executable': 'shellcheck',
\ 'command': 'shellcheck ' . s:exclude_option . ' -f gcc -',

View File

@ -43,7 +43,7 @@ function! ale_linters#typescript#tslint#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('typescript', {
call ale#linter#Define('typescript', {
\ 'name': 'tslint',
\ 'executable': 'tslint',
\ 'command': g:ale#util#stdin_wrapper . ' .ts tslint',

View File

@ -42,7 +42,7 @@ function! ale_linters#verilog#iverilog#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('verilog', {
call ale#linter#Define('verilog', {
\ 'name': 'iverilog',
\ 'output_stream': 'stderr',
\ 'executable': 'iverilog',

View File

@ -44,7 +44,7 @@ function! ale_linters#verilog#verilator#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('verilog', {
call ale#linter#Define('verilog', {
\ 'name': 'verilator',
\ 'output_stream': 'stderr',
\ 'executable': 'verilator',

View File

@ -9,7 +9,7 @@ let g:loaded_ale_linters_vim_vint = 1
let s:format = '-f "{file_path}:{line_number}:{column_number}: {severity}: {description} (see {reference})'
call ALEAddLinter('vim', {
call ale#linter#Define('vim', {
\ 'name': 'vint',
\ 'executable': 'vint',
\ 'command': g:ale#util#stdin_wrapper . ' .vim vint -w --no-color ' . s:format,

View File

@ -40,7 +40,7 @@ function! ale_linters#yaml#yamllint#Handle(buffer, lines)
return output
endfunction
call ALEAddLinter('yaml', {
call ale#linter#Define('yaml', {
\ 'name': 'yamllint',
\ 'executable': 'yamllint',
\ 'command': g:ale#util#stdin_wrapper . ' .yml yamllint -f parsable',

48
autoload/ale.vim Normal file
View File

@ -0,0 +1,48 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Primary code path for the plugin
" Manages execution of linters when requested by autocommands
let s:lint_timer = -1
function! ale#Queue(delay) abort
if s:lint_timer != -1
call timer_stop(s:lint_timer)
let s:lint_timer = -1
endif
let linters = ale#linter#Get(&filetype)
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('ale#Lint'))
else
call ale#Lint()
endif
endfunction
function! ale#Lint(...) abort
let buffer = bufnr('%')
let linters = ale#linter#Get(&filetype)
" 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 = ale#util#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 ale#engine#Invoke(buffer, linter)
endfor
endfunction

16
autoload/ale/cleanup.vim Normal file
View File

@ -0,0 +1,16 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Utility functions related to cleaning state.
function! ale#cleanup#Buffer(buffer) abort
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

View File

@ -1,12 +1,6 @@
" 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
@ -26,7 +20,7 @@ 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)
function! s:BinarySearch(loclist, line, column) abort
let min = 0
let max = len(a:loclist) - 1
let last_column_match = -1
@ -59,7 +53,7 @@ function! s:BinarySearch(loclist, line, column)
endwhile
endfunction
function! ale#cursor#TruncatedEcho(message)
function! ale#cursor#TruncatedEcho(message) abort
let message = a:message
" Change tabs to spaces.
let message = substitute(message, "\t", ' ', 'g')
@ -79,7 +73,7 @@ function! ale#cursor#TruncatedEcho(message)
endtry
endfunction
function! ale#cursor#EchoCursorWarning(...)
function! ale#cursor#EchoCursorWarning(...) abort
" Only echo the warnings in normal mode, otherwise we will get problems.
if mode() !=# 'n'
return
@ -108,7 +102,7 @@ endfunction
let s:cursor_timer = -1
function! ale#cursor#EchoCursorWarningWithDelay()
function! ale#cursor#EchoCursorWarningWithDelay() abort
if s:cursor_timer != -1
call timer_stop(s:cursor_timer)
let s:cursor_timer = -1

View File

@ -1,19 +1,6 @@
" 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
" Description: Backend execution and job management
" Executes linters in the background, using NeoVim or Vim 8 jobs
" Stores information for each job including:
"
@ -22,20 +9,18 @@ endif
" 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)
function! s:GetJobID(job) abort
if has('nvim')
"In NeoVim, job values are just IDs.
return a:job
endif
return a:string_or_ref
" 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:ClearJob(job)
function! s:ClearJob(job) abort
let job_id = s:GetJobID(a:job)
let linter = s:job_info_map[job_id].linter
@ -55,7 +40,7 @@ function! s:ClearJob(job)
call remove(linter, 'job')
endfunction
function! s:GatherOutput(job, data)
function! s:GatherOutput(job, data) abort
let job_id = s:GetJobID(a:job)
if !has_key(s:job_info_map, job_id)
@ -65,51 +50,15 @@ function! s:GatherOutput(job, data)
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)
function! s:GatherOutputVim(channel, data) abort
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
function! s:GatherOutputNeoVim(job, data, event) abort
call s:GatherOutput(a:job, a:data)
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)
function! s:HandleExit(job) abort
if a:job ==# 'no process'
" Stop right away when the job is not valid in Vim 8.
return
@ -129,15 +78,10 @@ function! s:HandleExit(job)
let output = job_info.output
let buffer = job_info.buffer
let linter_loclist = s:GetFunction(linter.callback)(buffer, output)
let linter_loclist = ale#util#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
call s:FixLocList(buffer, linter_loclist)
if g:ale_buffer_should_reset_map[buffer]
let g:ale_buffer_should_reset_map[buffer] = 0
@ -150,7 +94,7 @@ function! s:HandleExit(job)
" 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')
call sort(g:ale_buffer_loclist_map[buffer], 'ale#util#LocItemCompare')
if g:ale_set_loclist
call setloclist(0, g:ale_buffer_loclist_map[buffer])
@ -164,26 +108,31 @@ function! s:HandleExit(job)
" 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)
function! s:HandleExitNeoVim(job, data, event) abort
call s:HandleExit(a:job)
endfunction
function! s:HandleExitVim(channel)
function! s:HandleExitVim(channel) abort
call s:HandleExit(ch_getjob(a:channel))
endfunction
function! s:ApplyLinter(buffer, linter)
function! s:FixLocList(buffer, loclist) abort
" 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! ale#engine#Invoke(buffer, linter) abort
if has_key(a:linter, 'job')
" Stop previous jobs for the same linter.
call s:ClearJob(a:linter.job)
@ -191,7 +140,7 @@ function! s:ApplyLinter(buffer, linter)
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)
let command = ale#util#GetFunction(a:linter.command_callback)(a:buffer)
else
let command = a:linter.command
endif
@ -274,8 +223,7 @@ function! s:ApplyLinter(buffer, linter)
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'
" On some Vim versions, we have to send the buffer data ourselves.
let input = join(getbufline(a:buffer, 1, '$'), "\n") . "\n"
let channel = job_getchannel(job)
@ -286,152 +234,3 @@ function! s:ApplyLinter(buffer, linter)
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

View File

@ -4,13 +4,7 @@ scriptencoding utf-8
" 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)
function! ale#handlers#HandleGCCFormat(buffer, lines) abort
" Look for lines like the following.
"
" <stdin>:8:5: warning: conversion lacks type at end of format [-Wformat=]
@ -40,7 +34,7 @@ function! ale#handlers#HandleGCCFormat(buffer, lines)
return output
endfunction
function! ale#handlers#HandleCSSLintFormat(buffer, lines)
function! ale#handlers#HandleCSSLintFormat(buffer, lines) abort
" Matches patterns line the following:
"
" something.css: line 2, col 1, Error - Expected RBRACE at line 2, col 1. (errors)

66
autoload/ale/linter.vim Normal file
View File

@ -0,0 +1,66 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Linter registration and lazy-loading
" Retrieves linters as requested by the engine, loading them if needed.
let s:linters = {}
function! ale#linter#Define(filetype, linter) abort
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! ale#linter#Get(filetype) abort
if a:filetype ==# ''
" Empty filetype? Nothing to be done about that.
return []
endif
if has_key(s:linters, a:filetype)
" We already loaded a linter, great!
return s:linters[a:filetype]
endif
if has_key(g:ale_linters, a:filetype)
" Filter loaded linters according to list of linters specified in option.
for linter in g:ale_linters[a:filetype]
execute 'runtime! ale_linters/' . a:filetype . '/' . linter . '.vim'
endfor
else
execute 'runtime! ale_linters/' . a:filetype . '/*.vim'
endif
if !has_key(s:linters, a:filetype)
" If we couldn't load any linters, let everyone know.
let s:linters[a:filetype] = []
endif
return s:linters[a:filetype]
endfunction

View File

@ -1,12 +1,7 @@
scriptencoding utf-8
scriptencoding utf8
" 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')
@ -40,7 +35,7 @@ execute 'sign define ALEWarningSign text=' . g:ale_sign_warning
\ . ' texthl=ALEWarningSign'
sign define ALEDummySign
function! ale#sign#FindCurrentSigns(buffer)
function! ale#sign#FindCurrentSigns(buffer) abort
" Matches output like :
" line=4 id=1 name=ALEErrorSign
" строка=1 id=1000001 имя=ALEErrorSign
@ -66,7 +61,7 @@ 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)
function! ale#sign#CombineSigns(loclist) abort
let signlist = []
for obj in a:loclist
@ -98,7 +93,7 @@ function! ale#sign#CombineSigns(loclist)
endfunction
" This function will set the signs which show up on the left.
function! ale#sign#SetSigns(buffer, loclist)
function! ale#sign#SetSigns(buffer, loclist) abort
let signlist = ale#sign#CombineSigns(a:loclist)
if len(signlist) > 0 || g:ale_sign_column_always

View File

@ -1,7 +1,7 @@
" Author: KabbAmine <amine.kabb@gmail.com>
" Description: Statusline related function(s)
function! ALEGetStatusLine() abort
function! ale#statusline#Status() abort
" Returns a formatted string that can be integrated in the
" statusline

View File

@ -1,13 +1,7 @@
" 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()
function! s:FindWrapperScript() abort
for parent in split(&runtimepath, ',')
" Expand the path to deal with ~ issues.
let path = expand(parent . '/' . 'stdin-wrapper')
@ -24,20 +18,48 @@ 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
" Return the number of lines for a given buffer.
function! ale#util#GetLineCount(buffer) abort
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) abort
return findfile(a:filename, fnamemodify(bufname(a:buffer), ':p') . ';')
endfunction
function! ale#util#GetFunction(string_or_ref) abort
if type(a:string_or_ref) == type('')
return function(a:string_or_ref)
endif
return a:string_or_ref
endfunction
function! ale#util#LocItemCompare(left, right) abort
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

View File

@ -289,11 +289,30 @@ g:ale_statusline_format *g:ale_statusline_format*
Type: |List|
Default: `['%d error(s)', '%d warning(s)', 'OK']`
This variable defines the format of |`ALEGetStatusLine()`| output.
This variable defines the format of |`ale#statusline#status()`| output.
- The 1st element is for errors
- The 2nd element is for warnings
- The 3rd element is for when no errors are detected
g:airline#extensions#ale#enabled *g:airline#extensions#ale#enabled*
Type: |Number|
Default: `1`
Enables or disables the |airline|'s native extension for ale, which displays
warnings and errors in the status line, prefixed by
|airline#extensions#ale#error_symbol| and
|airline#extensions#ale#warning_symbol|.
g:airline#extensions#ale#enabled *g:airline#extensions#ale#enabled*
Type: |Number|
Default: `1`
Enables or disables the |airline|'s native extension for ale, which displays
warnings and errors in the status line, prefixed by
|airline#extensions#ale#error_symbol| and
|airline#extensions#ale#warning_symbol|.
===============================================================================
4. Linter Specific Options *ale-linter-options*
@ -413,14 +432,14 @@ g:ale_javascript_jshint_executable *g:ale_javascript_jshint_executable*
===============================================================================
5. API *ale-api*
ALELint(delay) *ALELint()*
ale#Queue(delay) *ale#Queue()*
Run linters for the current buffer, based on the filetype of the buffer,
with a given `delay`. A `delay` of `0` will run the linters immediately.
The linters will always be run in the background. Calling this function
again from the same buffer
ALEAddLinter(filetype, linter) *ALEAddLinter()*
ale#linter#Define(filetype, linter) *ale#linter#Define()*
Given a |String| for a filetype and a |Dictionary| Describing a linter
configuration, add a linter for the given filetype. The dictionaries each
offer the following options:
@ -492,21 +511,21 @@ ALEAddLinter(filetype, linter) *ALEAddLinter()*
'command': g:ale#util#stdin_wrapper . ' .hs ghc -fno-code -v0',
<
ALEGetLinters(filetype) *ALEGetLinters()*
ale#linter#Get(filetype) *ale#linter#Get()*
Return all of linters configured for a given filetype as a |List| of
|Dictionary| values in the format specified by |ALEAddLinter()|.
|Dictionary| values in the format specified by |ale#linter#Define()|.
ALEGetStatusLine() *ALEGetStatusLine()*
ale#statusline#Status() *ale#statusline#Status()*
Return a formatted string that can be added to the statusline.
The output's format is defined in |`g:ale_statusline_format`|.
To enable it, the following should be present in your |statusline| settings: >
%{ALEGetStatusLine()}
%{ale#statusline#status()}
g:ale#util#stdin_wrapper *g:ale#util#stdin_wrapper*
This variable names a wrapper script for sending stdin input to programs
which cannot accept input via stdin. See |ALEAddLinter| for more.
which cannot accept input via stdin. See |ale#linter#Define()| for more.
===============================================================================

View File

@ -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