summaryrefslogtreecommitdiff
path: root/autoload
diff options
context:
space:
mode:
authorBjorn Neergaard <bjorn@neersighted.com>2016-10-10 13:51:29 -0500
committerw0rp <w0rp@users.noreply.github.com>2016-10-10 19:51:29 +0100
commit7f0ce89d2b574fd5bdd0c050eaad92deeb63086d (patch)
treec508480ff3ccbf36ef7283610fdcaefe476ae6e1 /autoload
parent0680f875fe9ef07daceb3e9a90224bee613730df (diff)
downloadale-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 'autoload')
-rw-r--r--autoload/ale.vim48
-rw-r--r--autoload/ale/cleanup.vim16
-rw-r--r--autoload/ale/cursor.vim119
-rw-r--r--autoload/ale/engine.vim236
-rw-r--r--autoload/ale/handlers.vim76
-rw-r--r--autoload/ale/linter.vim66
-rw-r--r--autoload/ale/sign.vim138
-rw-r--r--autoload/ale/statusline.vim39
-rw-r--r--autoload/ale/util.vim65
9 files changed, 803 insertions, 0 deletions
diff --git a/autoload/ale.vim b/autoload/ale.vim
new file mode 100644
index 00000000..b25bcc99
--- /dev/null
+++ b/autoload/ale.vim
@@ -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
diff --git a/autoload/ale/cleanup.vim b/autoload/ale/cleanup.vim
new file mode 100644
index 00000000..2f636eee
--- /dev/null
+++ b/autoload/ale/cleanup.vim
@@ -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
diff --git a/autoload/ale/cursor.vim b/autoload/ale/cursor.vim
new file mode 100644
index 00000000..eaa466b2
--- /dev/null
+++ b/autoload/ale/cursor.vim
@@ -0,0 +1,119 @@
+" Author: w0rp <devw0rp@gmail.com>
+" Description: Echoes lint message for the current line, if any
+
+" 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) abort
+ 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) abort
+ 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(...) abort
+ " 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() abort
+ 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/autoload/ale/engine.vim b/autoload/ale/engine.vim
new file mode 100644
index 00000000..3d7374c6
--- /dev/null
+++ b/autoload/ale/engine.vim
@@ -0,0 +1,236 @@
+" Author: w0rp <devw0rp@gmail.com>
+" Description: Backend execution and job management
+" Executes linters in the background, using NeoVim or Vim 8 jobs
+
+" 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 = {}
+
+function! s:GetJobID(job) abort
+ 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:ClearJob(job) abort
+ 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) abort
+ 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:GatherOutputVim(channel, data) abort
+ call s:GatherOutput(ch_getjob(a:channel), [a:data])
+endfunction
+
+function! s:GatherOutputNeoVim(job, data, event) abort
+ call s:GatherOutput(a:job, a:data)
+endfunction
+
+function! s:HandleExit(job) abort
+ 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 = ale#util#GetFunction(linter.callback)(buffer, output)
+
+ " Make some adjustments to the loclists to fix common problems.
+ call s:FixLocList(buffer, linter_loclist)
+
+ 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], 'ale#util#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:HandleExitNeoVim(job, data, event) abort
+ call s:HandleExit(a:job)
+endfunction
+
+function! s:HandleExitVim(channel) abort
+ call s:HandleExit(ch_getjob(a:channel))
+endfunction
+
+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)
+ endif
+
+ if has_key(a:linter, 'command_callback')
+ " If there is a callback for generating a command, call that instead.
+ let command = ale#util#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 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)
+
+ if ch_status(channel) ==# 'open'
+ call ch_sendraw(channel, input)
+ call ch_close_in(channel)
+ endif
+ endif
+ endif
+endfunction
diff --git a/autoload/ale/handlers.vim b/autoload/ale/handlers.vim
new file mode 100644
index 00000000..6a8bd246
--- /dev/null
+++ b/autoload/ale/handlers.vim
@@ -0,0 +1,76 @@
+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.
+
+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=]
+ " <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) abort
+ " 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/autoload/ale/linter.vim b/autoload/ale/linter.vim
new file mode 100644
index 00000000..c87fa8df
--- /dev/null
+++ b/autoload/ale/linter.vim
@@ -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
diff --git a/autoload/ale/sign.vim b/autoload/ale/sign.vim
new file mode 100644
index 00000000..5cdd97c1
--- /dev/null
+++ b/autoload/ale/sign.vim
@@ -0,0 +1,138 @@
+scriptencoding utf8
+" Author: w0rp <devw0rp@gmail.com>
+" Description: Draws error and warning signs into signcolumn
+
+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) abort
+ " 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) abort
+ 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) abort
+ 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/autoload/ale/statusline.vim b/autoload/ale/statusline.vim
new file mode 100644
index 00000000..ded5283b
--- /dev/null
+++ b/autoload/ale/statusline.vim
@@ -0,0 +1,39 @@
+" Author: KabbAmine <amine.kabb@gmail.com>
+" Description: Statusline related function(s)
+
+function! ale#statusline#Status() 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/autoload/ale/util.vim b/autoload/ale/util.vim
new file mode 100644
index 00000000..e292c990
--- /dev/null
+++ b/autoload/ale/util.vim
@@ -0,0 +1,65 @@
+" Author: w0rp <devw0rp@gmail.com>
+" Description: Contains miscellaneous functions
+
+function! s:FindWrapperScript() abort
+ 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()
+
+" 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