Add linting with eslint in NeoVim, with a few bugs.
This commit is contained in:
commit
11c11e578f
8 changed files with 420 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/init.vim
|
22
LICENSE
Normal file
22
LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
Copyright (c) 2016, w0rp <devw0rp@gmail.com>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
18
README.md
Normal file
18
README.md
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# ALE - Asynchronous Lint Engine
|
||||||
|
|
||||||
|
ALE (Asynchronous Lint Engine) is a plugin for providing linting in NeoVim
|
||||||
|
and Vim 8 while you edit your text files.
|
||||||
|
|
||||||
|
ALE makes use of NeoVim and Vim 8 job control functions and timers to
|
||||||
|
run linters on the contents of text buffers and return errors as
|
||||||
|
text is changed in Vim. This allows for displaying warnings and
|
||||||
|
errors in files being edited in Vim before file has been saved
|
||||||
|
back to disk.
|
||||||
|
|
||||||
|
**NOTE:** This Vim plugin has been written pretty quickly so far,
|
||||||
|
and is still in rapid development. Documentation and stable APIs will
|
||||||
|
follow later.
|
||||||
|
|
||||||
|
## Known Bugs
|
||||||
|
|
||||||
|
1. Warnings are cleared when a syntax error is hit with eslint.
|
41
ale_linters/javascript/eslint.vim
Normal file
41
ale_linters/javascript/eslint.vim
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
if exists('g:loaded_ale_linters_javascript_eslint')
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
|
||||||
|
let g:loaded_ale_linters_javascript_eslint = 1
|
||||||
|
|
||||||
|
function! ale_linters#javascript#eslint#Handle(lines)
|
||||||
|
" Matches patterns line the following:
|
||||||
|
"
|
||||||
|
" <text>:47:14: Missing trailing comma. [Warning/comma-dangle]
|
||||||
|
" <text>:56:41: Missing semicolon. [Error/semi]
|
||||||
|
let pattern = '^<text>:\(\d\+\):\(\d\+\): \(.\+\) \[\(.\+\)/\(.\+\)\]'
|
||||||
|
let output = []
|
||||||
|
|
||||||
|
for line in a:lines
|
||||||
|
let match = matchlist(line, pattern)
|
||||||
|
|
||||||
|
if len(match) == 0
|
||||||
|
break
|
||||||
|
endif
|
||||||
|
|
||||||
|
" vcol is Needed to indicate that the column is a character.
|
||||||
|
call add(output, {
|
||||||
|
\ 'bufnr': bufnr('%'),
|
||||||
|
\ 'lnum': match[1] + 0,
|
||||||
|
\ 'vcol': 0,
|
||||||
|
\ 'col': match[2] + 0,
|
||||||
|
\ 'text': match[3] . '(' . match[5] . ')',
|
||||||
|
\ 'type': match[4] ==# 'Warning' ? 'W' : 'E',
|
||||||
|
\ 'nr': -1,
|
||||||
|
\})
|
||||||
|
endfor
|
||||||
|
|
||||||
|
return output
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
call ALEAddLinter('javascript', {
|
||||||
|
\ 'executable': 'eslint',
|
||||||
|
\ 'command': 'eslint -f unix --stdin',
|
||||||
|
\ 'callback': 'ale_linters#javascript#eslint#Handle',
|
||||||
|
\})
|
42
plugin/ale/aaflags.vim
Normal file
42
plugin/ale/aaflags.vim
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
" This file sets up configuration settings for the ALE plugin.
|
||||||
|
" Flags can be set in vimrc files and so on to disable particular features,
|
||||||
|
" etc.
|
||||||
|
|
||||||
|
if exists('g:loaded_ale_flags')
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
|
||||||
|
let g:loaded_ale_flags = 1
|
||||||
|
|
||||||
|
" This flag can be set to 0 to disable linting when text is changed.
|
||||||
|
if !exists('g:ale_lint_on_text_changed')
|
||||||
|
let g:ale_lint_on_text_changed = 1
|
||||||
|
endif
|
||||||
|
|
||||||
|
" This flag can be set with a number of milliseconds for delaying the
|
||||||
|
" execution of a linter when text is changed. The timeout will be set and
|
||||||
|
" cleared each time text is changed, so repeated edits won't trigger the
|
||||||
|
" jobs for linting until enough time has passed after editing is done.
|
||||||
|
if !exists('g:ale_lint_delay')
|
||||||
|
let g:ale_lint_delay = 100
|
||||||
|
endif
|
||||||
|
|
||||||
|
" This flag can be set to 0 to disable linting when the buffer is entered.
|
||||||
|
if !exists('g:ale_lint_on_enter')
|
||||||
|
let g:ale_lint_on_enter = 1
|
||||||
|
endif
|
||||||
|
|
||||||
|
" This flag can be set to 0 to disable setting the loclist.
|
||||||
|
if !exists('g:ale_set_loclist')
|
||||||
|
let g:ale_set_loclist = 1
|
||||||
|
endif
|
||||||
|
|
||||||
|
" This flag can be set to 0 to disable setting signs.
|
||||||
|
if !exists('g:ale_set_signs')
|
||||||
|
let g:ale_set_signs = 1
|
||||||
|
endif
|
||||||
|
|
||||||
|
" This flag can be set to 0 to disable echoing when the cursor moves.
|
||||||
|
if !exists('g:ale_echo_cursor')
|
||||||
|
let g:ale_echo_cursor = 1
|
||||||
|
endif
|
72
plugin/ale/cursor.vim
Normal file
72
plugin/ale/cursor.vim
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
if exists('g:loaded_ale_cursor')
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
|
||||||
|
let g:loaded_ale_cursor = 1
|
||||||
|
|
||||||
|
" 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')
|
||||||
|
|
||||||
|
let truncated_message = join(split(message, '\zs')[:&columns - 2], '')
|
||||||
|
|
||||||
|
" Echo the message truncated to fit without creating a prompt.
|
||||||
|
echo truncated_message
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! ale#cursor#EchoCursorWarning()
|
||||||
|
let pos = getcurpos()
|
||||||
|
|
||||||
|
let index = s:BinarySearch(b:ale_loclist, pos[1], pos[2])
|
||||||
|
|
||||||
|
if index >= 0
|
||||||
|
call ale#cursor#TruncatedEcho(b:ale_loclist[index]['text'])
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
if g:ale_echo_cursor
|
||||||
|
augroup ALECursorGroup
|
||||||
|
autocmd!
|
||||||
|
autocmd CursorMoved * call ale#cursor#EchoCursorWarning()
|
||||||
|
augroup END
|
||||||
|
endif
|
42
plugin/ale/sign.vim
Normal file
42
plugin/ale/sign.vim
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
if exists('g:loaded_ale_sign')
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
|
||||||
|
let g:loaded_ale_sign = 1
|
||||||
|
|
||||||
|
if !hlexists('ALEErrorSign')
|
||||||
|
highlight link ALErrorSign 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
|
||||||
|
|
||||||
|
" Signs show up on the left for error markers.
|
||||||
|
sign define ALEErrorSign text=>> texthl=ALEErrorSign
|
||||||
|
sign define ALEWarningSign text=-- texthl=ALEWarningSign
|
||||||
|
|
||||||
|
" This function will set the signs which show up on the left.
|
||||||
|
function! ale#sign#SetSigns(loclist)
|
||||||
|
sign unplace *
|
||||||
|
|
||||||
|
for i in range(0, len(a:loclist) - 1)
|
||||||
|
let obj = a:loclist[i]
|
||||||
|
let name = obj['type'] ==# 'W' ? 'ALEWarningSign' : 'ALEErrorSign'
|
||||||
|
|
||||||
|
let sign_line = 'sign place ' . (i + 1)
|
||||||
|
\. ' line=' . obj['lnum']
|
||||||
|
\. ' name=' . name
|
||||||
|
\. ' buffer=' . obj['bufnr']
|
||||||
|
|
||||||
|
exec sign_line
|
||||||
|
endfor
|
||||||
|
endfunction
|
182
plugin/ale/zmain.vim
Normal file
182
plugin/ale/zmain.vim
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
" Always set buffer variables for each buffer
|
||||||
|
let b:ale_should_reset_loclist = 0
|
||||||
|
let b:ale_loclist = []
|
||||||
|
|
||||||
|
if exists('g:loaded_ale_zmain')
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
|
||||||
|
let g:loaded_ale_zmain = 1
|
||||||
|
|
||||||
|
let s:lint_timer = -1
|
||||||
|
let s:linters = {}
|
||||||
|
let s:job_linter_map = {}
|
||||||
|
let s:job_output_map = {}
|
||||||
|
|
||||||
|
function! s:ClearJob(job)
|
||||||
|
if a:job != -1
|
||||||
|
let linter = s:job_linter_map[a:job]
|
||||||
|
|
||||||
|
call jobstop(a:job)
|
||||||
|
call remove(s:job_output_map, a:job)
|
||||||
|
call remove(s:job_linter_map, a:job)
|
||||||
|
|
||||||
|
let linter.job = -1
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:GatherOutput(job, data, event)
|
||||||
|
if !has_key(s:job_output_map, a:job)
|
||||||
|
return
|
||||||
|
endif
|
||||||
|
|
||||||
|
call extend(s:job_output_map[a:job], 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:HandleExit(job, data, event)
|
||||||
|
if !has_key(s:job_linter_map, a:job)
|
||||||
|
return
|
||||||
|
endif
|
||||||
|
|
||||||
|
let linter = s:job_linter_map[a:job]
|
||||||
|
let output = s:job_output_map[a:job]
|
||||||
|
|
||||||
|
call s:ClearJob(a:job)
|
||||||
|
|
||||||
|
let linter_loclist = function(linter.callback)(output)
|
||||||
|
|
||||||
|
if b:ale_should_reset_loclist
|
||||||
|
let b:ale_should_reset_loclist = 0
|
||||||
|
let b:ale_loclist = []
|
||||||
|
endif
|
||||||
|
|
||||||
|
" Add the loclist items from the linter.
|
||||||
|
call extend(b:ale_loclist, 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(b:ale_loclist, 's:LocItemCompare')
|
||||||
|
|
||||||
|
if g:ale_set_loclist
|
||||||
|
call setloclist(0, b:ale_loclist)
|
||||||
|
endif
|
||||||
|
|
||||||
|
if g:ale_set_signs
|
||||||
|
call ale#sign#SetSigns(b:ale_loclist)
|
||||||
|
endif
|
||||||
|
|
||||||
|
" Mark line 200, column 17 with a squiggly line or something
|
||||||
|
" matchadd('ALEError', '\%200l\%17v')
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:ApplyLinter(linter)
|
||||||
|
" Stop previous jobs for the same linter.
|
||||||
|
call s:ClearJob(a:linter.job)
|
||||||
|
|
||||||
|
let a:linter.job = jobstart(a:linter.command, {
|
||||||
|
\ 'on_stdout': 's:GatherOutput',
|
||||||
|
\ 'on_exit': 's:HandleExit',
|
||||||
|
\})
|
||||||
|
|
||||||
|
let s:job_linter_map[a:linter.job] = a:linter
|
||||||
|
let s:job_output_map[a:linter.job] = []
|
||||||
|
|
||||||
|
call jobsend(a:linter.job, join(getline(1, '$'), "\n") . "\n")
|
||||||
|
call jobclose(a:linter.job, 'stdin')
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:TimerHandler()
|
||||||
|
let filetype = &filetype
|
||||||
|
let linters = ALEGetLinters(filetype)
|
||||||
|
|
||||||
|
" Set a variable telling us to clear the loclist later.
|
||||||
|
let b:ale_should_reset_loclist = 1
|
||||||
|
|
||||||
|
for linter in linters
|
||||||
|
call s:ApplyLinter(linter)
|
||||||
|
endfor
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! ALEAddLinter(filetype, linter)
|
||||||
|
" Check if the linter program is executable before adding it.
|
||||||
|
if !executable(a:linter.executable)
|
||||||
|
return
|
||||||
|
endif
|
||||||
|
|
||||||
|
if !has_key(s:linters, a:filetype)
|
||||||
|
let s:linters[a:filetype] = []
|
||||||
|
endif
|
||||||
|
|
||||||
|
call add(s:linters[a:filetype], {
|
||||||
|
\ 'job': -1,
|
||||||
|
\ 'command': a:linter.command,
|
||||||
|
\ 'callback': a:linter.callback,
|
||||||
|
\})
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! ALEGetLinters(filetype)
|
||||||
|
if !has_key(s:linters, a:filetype)
|
||||||
|
return []
|
||||||
|
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, 's:TimerHandler')
|
||||||
|
else
|
||||||
|
call s:TimerHandler()
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" Load all of the linters for each filetype.
|
||||||
|
runtime ale_linters/*/*.vim
|
||||||
|
|
||||||
|
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 * call ALELint(0)
|
||||||
|
augroup END
|
||||||
|
endif
|
Reference in a new issue