summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--LICENSE22
-rw-r--r--README.md18
-rw-r--r--ale_linters/javascript/eslint.vim41
-rw-r--r--plugin/ale/aaflags.vim42
-rw-r--r--plugin/ale/cursor.vim72
-rw-r--r--plugin/ale/sign.vim42
-rw-r--r--plugin/ale/zmain.vim182
8 files changed, 420 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..7f499f80
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/init.vim
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..a8162f2e
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..00720e4a
--- /dev/null
+++ b/README.md
@@ -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.
diff --git a/ale_linters/javascript/eslint.vim b/ale_linters/javascript/eslint.vim
new file mode 100644
index 00000000..d6763eef
--- /dev/null
+++ b/ale_linters/javascript/eslint.vim
@@ -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',
+\})
diff --git a/plugin/ale/aaflags.vim b/plugin/ale/aaflags.vim
new file mode 100644
index 00000000..70021a6e
--- /dev/null
+++ b/plugin/ale/aaflags.vim
@@ -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
diff --git a/plugin/ale/cursor.vim b/plugin/ale/cursor.vim
new file mode 100644
index 00000000..0159302a
--- /dev/null
+++ b/plugin/ale/cursor.vim
@@ -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
diff --git a/plugin/ale/sign.vim b/plugin/ale/sign.vim
new file mode 100644
index 00000000..29753011
--- /dev/null
+++ b/plugin/ale/sign.vim
@@ -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
diff --git a/plugin/ale/zmain.vim b/plugin/ale/zmain.vim
new file mode 100644
index 00000000..2db0d028
--- /dev/null
+++ b/plugin/ale/zmain.vim
@@ -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