diff options
-rw-r--r-- | CONTRIBUTING.md | 3 | ||||
-rw-r--r-- | ISSUE_TEMPLATE.md | 6 | ||||
-rw-r--r-- | Makefile | 11 | ||||
-rw-r--r-- | ale_linters/fortran/gcc.vim | 2 | ||||
-rw-r--r-- | autoload/ale.vim | 15 | ||||
-rw-r--r-- | autoload/ale/cleanup.vim | 5 | ||||
-rw-r--r-- | autoload/ale/cursor.vim | 3 | ||||
-rw-r--r-- | autoload/ale/debugging.vim | 117 | ||||
-rw-r--r-- | autoload/ale/engine.vim | 67 | ||||
-rw-r--r-- | autoload/ale/filetypes.vim | 60 | ||||
-rw-r--r-- | autoload/ale/highlight.vim | 75 | ||||
-rw-r--r-- | autoload/ale/linter.vim | 26 | ||||
-rw-r--r-- | autoload/ale/sign.vim | 8 | ||||
-rw-r--r-- | autoload/ale/statusline.vim | 12 | ||||
-rw-r--r-- | autoload/ale/util.vim | 16 | ||||
-rw-r--r-- | doc/ale.txt | 27 | ||||
-rw-r--r-- | plugin/ale.vim | 40 | ||||
-rw-r--r-- | test/test_ale_info.vader | 116 | ||||
-rw-r--r-- | test/test_ale_toggle.vader | 112 | ||||
-rw-r--r-- | test/test_filetype_mapping.vader | 29 | ||||
-rw-r--r-- | test/test_fortran_handler.vader | 118 | ||||
-rw-r--r-- | test/test_highlight_clearing.vader | 4 | ||||
-rw-r--r-- | test/test_highlight_placement.vader | 61 | ||||
-rw-r--r-- | test/test_history_saving.vader | 69 | ||||
-rw-r--r-- | test/test_sandbox_execution.vader | 51 | ||||
-rw-r--r-- | test/test_statusline_api_without_globals.vader | 19 |
26 files changed, 1013 insertions, 59 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8cd1007a..e40ed5dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,9 @@ Have fun, and work on whatever floats your boat. Take It Easy :tm:. Don't forget to **write documentation** for whatever it is you are doing. See the ["Writing Documentation"](#writing-documentation) section. +Remember to write Vader tests for most of the code you write. You can look at +existing Vader tests in the `test` directory for examples. + When writing code, follow the [Google Vimscript Style Guide](https://google.github.io/styleguide/vimscriptguide.xml), and run `vint -s` on your files to check for most of what the guide mentions and more. If you diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..492016b3 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,6 @@ +For bugs, paste output from your clipboard after running :ALEInfoToClipboard +here. If that doesn't work for some reason, try running :ALEInfo and copying +the output from that here instead. If everything is broken, run around in +circles and scream. + +Whatever the case, describe the your issue here. @@ -2,11 +2,20 @@ SHELL := /usr/bin/env bash IMAGE ?= w0rp/ale CURRENT_IMAGE_ID = 107e4efc4267 DOCKER_FLAGS = --rm -v $(PWD):/testplugin -v $(PWD)/test:/home "$(IMAGE)" +tests = test/* test-setup: docker images -q w0rp/ale | grep ^$(CURRENT_IMAGE_ID) > /dev/null || \ docker pull $(IMAGE) +vader: test-setup + @:; \ + vims=$$(docker run --rm $(IMAGE) ls /vim-build/bin | grep -E '^n?vim'); \ + if [ -z "$$vims" ]; then echo "No Vims found!"; exit 1; fi; \ + for vim in $$vims; do \ + docker run -a stderr $(DOCKER_FLAGS) $$vim '+Vader! $(tests)'; \ + done + test: test-setup @:; \ vims=$$(docker run --rm $(IMAGE) ls /vim-build/bin | grep -E '^n?vim'); \ @@ -18,7 +27,7 @@ test: test-setup echo "Running tests for $$vim"; \ echo '========================================'; \ echo; \ - docker run -a stderr $(DOCKER_FLAGS) $$vim '+Vader! test/*' || EXIT=$$?; \ + docker run -a stderr $(DOCKER_FLAGS) $$vim '+Vader! $(tests)' || EXIT=$$?; \ done; \ echo; \ echo '========================================'; \ diff --git a/ale_linters/fortran/gcc.vim b/ale_linters/fortran/gcc.vim index 73ec7886..8d15cc00 100644 --- a/ale_linters/fortran/gcc.vim +++ b/ale_linters/fortran/gcc.vim @@ -12,7 +12,7 @@ function! ale_linters#fortran#gcc#Handle(buffer, lines) abort " " :21.34: " Error: Expected comma in I/O list at (1) - let l:line_marker_pattern = '^:\(\d\+\)\.\(\d\+\):$' + let l:line_marker_pattern = ':\(\d\+\)[.:]\=\(\d\+\)\=:\=$' let l:message_pattern = '^\(Error\|Warning\): \(.\+\)$' let l:looking_for_message = 0 let l:last_loclist_obj = {} diff --git a/autoload/ale.vim b/autoload/ale.vim index a5251f64..c81a57a6 100644 --- a/autoload/ale.vim +++ b/autoload/ale.vim @@ -4,9 +4,17 @@ let s:lint_timer = -1 +" A function for checking various conditions whereby ALE just shouldn't +" attempt to do anything, say if particular buffer types are open in Vim. +function! ale#ShouldDoNothing() abort + " Do nothing for blacklisted files + " OR if ALE is running in the sandbox + return index(g:ale_filetype_blacklist, &filetype) >= 0 + \ || ale#util#InSandbox() +endfunction + function! ale#Queue(delay) abort - " Do nothing for blacklisted files. - if index(g:ale_filetype_blacklist, &filetype) >= 0 + if ale#ShouldDoNothing() return endif @@ -29,8 +37,7 @@ function! ale#Queue(delay) abort endfunction function! ale#Lint(...) abort - " Do nothing for blacklisted files. - if index(g:ale_filetype_blacklist, &filetype) >= 0 + if ale#ShouldDoNothing() return endif diff --git a/autoload/ale/cleanup.vim b/autoload/ale/cleanup.vim index d720cc96..3b0b1d90 100644 --- a/autoload/ale/cleanup.vim +++ b/autoload/ale/cleanup.vim @@ -10,6 +10,11 @@ function! ale#cleanup#Buffer(buffer) abort call ale#engine#ClearJob(l:job) endfor + " Clear delayed highlights for a buffer being removed. + if g:ale_set_highlights + call ale#highlight#UnqueueHighlights(a:buffer) + endif + call remove(g:ale_buffer_info, a:buffer) endif endfunction diff --git a/autoload/ale/cursor.vim b/autoload/ale/cursor.vim index 864f6f28..9aaa8e91 100644 --- a/autoload/ale/cursor.vim +++ b/autoload/ale/cursor.vim @@ -73,8 +73,7 @@ let s:cursor_timer = -1 let s:last_pos = [0, 0, 0] function! ale#cursor#EchoCursorWarningWithDelay() abort - " Do nothing for blacklisted files. - if index(g:ale_filetype_blacklist, &filetype) >= 0 + if ale#ShouldDoNothing() return endif diff --git a/autoload/ale/debugging.vim b/autoload/ale/debugging.vim new file mode 100644 index 00000000..737a90d7 --- /dev/null +++ b/autoload/ale/debugging.vim @@ -0,0 +1,117 @@ +" Author: w0rp <devw0rp@gmail.com> +" Description: This file implements debugging information for ALE + +let s:global_variable_list = [ +\ 'ale_echo_cursor', +\ 'ale_echo_msg_error_str', +\ 'ale_echo_msg_format', +\ 'ale_echo_msg_warning_str', +\ 'ale_enabled', +\ 'ale_keep_list_window_open', +\ 'ale_lint_delay', +\ 'ale_lint_on_enter', +\ 'ale_lint_on_save', +\ 'ale_lint_on_text_changed', +\ 'ale_linter_aliases', +\ 'ale_linters', +\ 'ale_open_list', +\ 'ale_set_highlights', +\ 'ale_set_loclist', +\ 'ale_set_quickfix', +\ 'ale_set_signs', +\ 'ale_sign_column_always', +\ 'ale_sign_error', +\ 'ale_sign_offset', +\ 'ale_sign_warning', +\ 'ale_statusline_format', +\ 'ale_warn_about_trailing_whitespace', +\] + +function! s:GetLinterVariables(filetype, linter_names) abort + let l:variable_list = [] + let l:filetype_parts = split(a:filetype, '\.') + + for l:key in keys(g:) + " Extract variable names like: 'ale_python_flake8_executable' + let l:match = matchlist(l:key, '\v^ale_([^_]+)_([^_]+)_.+$') + + " Include matching variables. + if !empty(l:match) + \&& index(l:filetype_parts, l:match[1]) >= 0 + \&& index(a:linter_names, l:match[2]) >= 0 + call add(l:variable_list, l:key) + endif + endfor + + call sort(l:variable_list) + + return l:variable_list +endfunction + +function! s:EchoLinterVariables(variable_list) abort + for l:key in a:variable_list + echom 'let g:' . l:key . ' = ' . string(g:[l:key]) + endfor +endfunction + +function! s:EchoGlobalVariables() abort + for l:key in s:global_variable_list + echom 'let g:' . l:key . ' = ' . string(get(g:, l:key, v:null)) + endfor +endfunction + +function! s:EchoCommandHistory() abort + let l:buffer = bufnr('%') + + if !has_key(g:ale_buffer_info, l:buffer) + return + endif + + for l:item in g:ale_buffer_info[l:buffer].history + echom '(' . l:item.status . ') ' . string(l:item.command) + endfor +endfunction + +function! ale#debugging#Info() abort + let l:filetype = &filetype + + " We get the list of enabled linters for free by the above function. + let l:enabled_linters = deepcopy(ale#linter#Get(l:filetype)) + + " But have to build the list of available linters ourselves. + let l:all_linters = [] + let l:linter_variable_list = [] + + for l:part in split(l:filetype, '\.') + let l:aliased_filetype = ale#linter#ResolveFiletype(l:part) + call extend(l:all_linters, ale#linter#GetAll(l:aliased_filetype)) + endfor + + let l:all_names = map(l:all_linters, 'v:val[''name'']') + let l:enabled_names = map(l:enabled_linters, 'v:val[''name'']') + + " Load linter variables to display + " This must be done after linters are loaded. + let l:variable_list = s:GetLinterVariables(l:filetype, l:enabled_names) + + echom ' Current Filetype: ' . l:filetype + echom 'Available Linters: ' . string(l:all_names) + echom ' Enabled Linters: ' . string(l:enabled_names) + echom ' Linter Variables:' + echom '' + call s:EchoLinterVariables(l:variable_list) + echom ' Global Variables:' + echom '' + call s:EchoGlobalVariables() + echom ' Command History:' + echom '' + call s:EchoCommandHistory() +endfunction + +function! ale#debugging#InfoToClipboard() abort + redir @+> + silent call ale#debugging#Info() + redir END + + echom 'ALEInfo copied to your clipboard' +endfunction diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index 7d22ad11..b54ad6ee 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -9,6 +9,30 @@ " output: The array of lines for the output of the job. let s:job_info_map = {} +function! ale#engine#AddToHistory(buffer, status, job_id, command) abort + if g:ale_max_buffer_history_size <= 0 + " Don't save anything if the history isn't a positive number. + let g:ale_buffer_info[a:buffer].history = [] + + return + endif + + let l:history = g:ale_buffer_info[a:buffer].history + + " Remove the first item if we hit the max history size. + if len(l:history) >= g:ale_max_buffer_history_size + let l:history = l:history[1:] + endif + + call add(l:history, { + \ 'status': a:status, + \ 'job_id': a:job_id, + \ 'command': a:command, + \}) + + let g:ale_buffer_info[a:buffer].history = l:history +endfunction + function! s:GetJobID(job) abort if has('nvim') "In NeoVim, job values are just IDs. @@ -19,6 +43,7 @@ function! s:GetJobID(job) abort " job. We'll use the ID of the channel instead as the job ID. try return ch_info(job_getchannel(a:job)).id + catch endtry " If we fail to get the channel ID for a job, just return a 0 ID. @@ -32,12 +57,14 @@ function! ale#engine#InitBufferInfo(buffer) abort " new_loclist holds loclist items while jobs are being run. " temporary_file_list holds temporary files to be cleaned up " temporary_directory_list holds temporary directories to be cleaned up + " history holds a list of previously run commands for this buffer let g:ale_buffer_info[a:buffer] = { \ 'job_list': [], \ 'loclist': [], \ 'new_loclist': [], \ 'temporary_file_list': [], \ 'temporary_directory_list': [], + \ 'history': [], \} endif endfunction @@ -154,6 +181,12 @@ function! ale#engine#RemoveManagedFiles(buffer) abort return endif + " We can't delete anything in a sandbox, so wait until we escape from + " it to delete temporary files and directories. + if ale#util#InSandbox() + return + endif + " Delete files with a call akin to a plan `rm` command. for l:filename in g:ale_buffer_info[a:buffer].temporary_file_list call delete(l:filename) @@ -194,6 +227,12 @@ function! s:HandleExit(job) abort " which just closed. call s:StopPreviousJobs(l:buffer, l:linter) + " Stop here if we land in the handle for a job completing if we're in + " a sandbox. + if ale#util#InSandbox() + return + endif + if l:next_chain_index < len(get(l:linter, 'command_chain', [])) call s:InvokeChain(l:buffer, l:linter, l:next_chain_index, l:output) return @@ -234,9 +273,6 @@ function! s:HandleExit(job) abort " Call user autocommands. This allows users to hook into ALE's lint cycle. silent doautocmd User ALELint - - " Mark line 200, column 17 with a squiggly line or something - " matchadd('ALEError', '\%200l\%17v') endfunction function! ale#engine#SetResults(buffer, loclist) abort @@ -252,6 +288,10 @@ function! ale#engine#SetResults(buffer, loclist) abort " Don't load/run if not already loaded. call ale#statusline#Update(a:buffer, a:loclist) endif + + if g:ale_set_highlights + call ale#highlight#SetHighlights(a:buffer, a:loclist) + endif endfunction function! s:HandleExitNeoVim(job, data, event) abort @@ -285,11 +325,17 @@ function! ale#engine#EscapeCommandPart(command_part) abort endfunction function! s:TemporaryFilename(buffer) abort + let l:filename = fnamemodify(bufname(a:buffer), ':t') + + if empty(l:filename) + " If the buffer's filename is empty, create a dummy filename. + let l:ft = getbufvar(a:buffer, '&filetype') + let l:filename = 'file' . ale#filetypes#GuessExtension(l:ft) + endif + " Create a temporary filename, <temp_dir>/<original_basename> " The file itself will not be created by this function. - return tempname() - \ . (has('win32') ? '\' : '/') - \ . fnamemodify(bufname(a:buffer), ':t') + return tempname() . (has('win32') ? '\' : '/') . l:filename endfunction " Given a command string, replace every... @@ -417,19 +463,26 @@ function! s:RunJob(options) abort let l:job = job_start(l:command, l:job_options) endif + let l:status = 'failed' + let l:job_id = 0 + " Only proceed if the job is being run. if has('nvim') || (l:job !=# 'no process' && job_status(l:job) ==# 'run') " Add the job to the list of jobs, so we can track them. call add(g:ale_buffer_info[l:buffer].job_list, l:job) + let l:status = 'ran' + let l:job_id = s:GetJobID(l:job) " Store the ID for the job in the map to read back again. - let s:job_info_map[s:GetJobID(l:job)] = { + let s:job_info_map[l:job_id] = { \ 'linter': l:linter, \ 'buffer': l:buffer, \ 'output': [], \ 'next_chain_index': l:next_chain_index, \} endif + + call ale#engine#AddToHistory(l:buffer, l:status, l:job_id, l:command) endfunction " Determine which commands to run for a link in a command chain, or diff --git a/autoload/ale/filetypes.vim b/autoload/ale/filetypes.vim new file mode 100644 index 00000000..6174aa0e --- /dev/null +++ b/autoload/ale/filetypes.vim @@ -0,0 +1,60 @@ +" Author: w0rp <devw0rp@gmail.com> +" Description: This file handles guessing file extensions for filetypes, etc. + +function! ale#filetypes#LoadExtensionMap() abort + " Output includes: + " '*.erl setf erlang' + redir => l:output + silent exec 'autocmd' + redir end + + let l:map = {} + + for l:line in split(l:output, "\n") + " Parse filetypes, like so: + " + " *.erl setf erlang + " *.md set filetype=markdown + " *.snippet setlocal filetype=snippets + let l:match = matchlist(l:line, '\v^ *\*(\.[^ ]+).*set(f *| *filetype=|local *filetype=)([^ ]+)') + + if !empty(l:match) + let l:map[substitute(l:match[3], '^=', '', '')] = l:match[1] + endif + endfor + + return l:map +endfunction + +let s:cached_map = {} + +function! s:GetCachedExtensionMap() abort + if empty(s:cached_map) + let s:cached_map = ale#filetypes#LoadExtensionMap() + endif + + return s:cached_map +endfunction + +function! ale#filetypes#GuessExtension(filetype) abort + let l:map = s:GetCachedExtensionMap() + let l:ext = get(l:map, a:filetype, '') + + " If we have an exact match, like something for javascript.jsx, use that. + if !empty(l:ext) + return l:ext + endif + + " If we don't have an exact match, use the first filetype in the compound + " filetype. + for l:part in split(a:filetype, '\.') + let l:ext = get(l:map, l:part, '') + + if !empty(l:ext) + return l:ext + endif + endfor + + " Return an empty string if we don't find anything. + return '' +endfunction diff --git a/autoload/ale/highlight.vim b/autoload/ale/highlight.vim new file mode 100644 index 00000000..1669ebd2 --- /dev/null +++ b/autoload/ale/highlight.vim @@ -0,0 +1,75 @@ +scriptencoding utf8 +" Author: w0rp <devw0rp@gmail.com> +" Description: This module implements error/warning highlighting. + +if !hlexists('ALEError') + highlight link ALEError SpellBad +endif + +if !hlexists('ALEWarning') + highlight link ALEWarning SpellCap +endif + +" This map holds highlights to be set when buffers are opened. +" We can only set highlights for whatever the current buffer is, so we will +" wait until the buffer is entered again to show the highlights, unless +" the buffer is in focus when linting completes. +let s:buffer_highlights = {} + +function! ale#highlight#UnqueueHighlights(buffer) abort + if has_key(s:buffer_highlights, a:buffer) + call remove(s:buffer_highlights, a:buffer) + endif +endfunction + +function! s:RemoveOldHighlights() abort + for l:match in getmatches() + if l:match['group'] ==# 'ALEError' || l:match['group'] ==# 'ALEWarning' + call matchdelete(l:match['id']) + endif + endfor +endfunction + +function! ale#highlight#UpdateHighlights() abort + let l:buffer = bufnr('%') + let l:has_new_items = has_key(s:buffer_highlights, l:buffer) + let l:loclist = l:has_new_items ? remove(s:buffer_highlights, l:buffer) : [] + + if l:has_new_items || !g:ale_enabled + call s:RemoveOldHighlights() + endif + + if l:has_new_items + for l:item in l:loclist + let l:col = l:item.col + let l:group = l:item.type ==# 'E' ? 'ALEError' : 'ALEWarning' + let l:line = l:item.lnum + let l:size = 1 + + call matchaddpos(l:group, [[l:line, l:col, l:size]]) + endfor + endif +endfunction + +augroup ALEHighlightBufferGroup + autocmd! + autocmd BufEnter * call ale#highlight#UpdateHighlights() +augroup END + +function! ale#highlight#SetHighlights(buffer, loclist) abort + " Only set set items for the buffer if ALE is enabled. + if g:ale_enabled + " Set a list of items to be set as highlights for a buffer when + " we next open it. + " + " We'll filter the loclist down to items we can set now. + let s:buffer_highlights[a:buffer] = filter( + \ deepcopy(a:loclist), + \ 'v:val.bufnr == a:buffer && v:val.col > 0' + \) + + " Update highlights for the current buffer, which may or may not + " be the buffer we just set highlights for. + call ale#highlight#UpdateHighlights() + endif +endfunction diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim index 260c145b..e586c1f3 100644 --- a/autoload/ale/linter.vim +++ b/autoload/ale/linter.vim @@ -180,7 +180,7 @@ function! ale#linter#GetAll(filetype) abort return s:linters[a:filetype] endfunction -function! s:ResolveFiletype(original_filetype) abort +function! ale#linter#ResolveFiletype(original_filetype) abort " Try and get an aliased file type either from the user's Dictionary, or " our default Dictionary, otherwise use the filetype as-is. let l:filetype = get( @@ -201,7 +201,7 @@ function! ale#linter#Get(original_filetypes) abort " Handle dot-seperated filetypes. for l:original_filetype in split(a:original_filetypes, '\.') - let l:filetype = s:ResolveFiletype(l:original_filetype) + let l:filetype = ale#linter#ResolveFiletype(l:original_filetype) " Try and get a list of linters to run, using the original file type, " not the aliased filetype. We have some linters to limit by default, @@ -235,25 +235,3 @@ function! ale#linter#Get(original_filetypes) abort return l:combined_linters endfunction - -function! ale#linter#Info() abort - let l:original_filetypes = &filetype - - " We get the list of enabled linters for free by the above function. - let l:enabled_linters = deepcopy(ale#linter#Get(l:original_filetypes)) - - " But have to build the list of available linters ourselves. - let l:all_linters = [] - for l:original_filetype in split(l:original_filetypes, '\.') - let l:filetype = s:ResolveFiletype(l:original_filetype) - let l:filetype_linters = ale#linter#GetAll(l:filetype) - call extend(l:all_linters, l:filetype_linters) - endfor - - let l:all_names = map(l:all_linters, 'v:val[''name'']') - let l:enabled_names = map(l:enabled_linters, 'v:val[''name'']') - - echom ' Current Filetype: ' . l:original_filetypes - echom 'Available Linters: ' . string(l:all_names) - echom ' Enabled Linters: ' . string(l:enabled_names) -endfunction diff --git a/autoload/ale/sign.vim b/autoload/ale/sign.vim index e2b3fa74..9d613480 100644 --- a/autoload/ale/sign.vim +++ b/autoload/ale/sign.vim @@ -12,14 +12,6 @@ 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. execute 'sign define ALEErrorSign text=' . g:ale_sign_error \ . ' texthl=ALEErrorSign' diff --git a/autoload/ale/statusline.vim b/autoload/ale/statusline.vim index 7269ddca..efb7e9e5 100644 --- a/autoload/ale/statusline.vim +++ b/autoload/ale/statusline.vim @@ -3,6 +3,10 @@ " Update the buffer error/warning count with data from loclist. function! ale#statusline#Update(buffer, loclist) abort + if !exists('g:ale_buffer_info') + return + endif + if !has_key(g:ale_buffer_info, a:buffer) return endif @@ -39,6 +43,10 @@ endfunction " Returns a tuple of errors and warnings for use in third-party integrations. function! ale#statusline#Count(buffer) abort + if !exists('g:ale_buffer_info') + return [0, 0] + endif + if !s:SetupCount(a:buffer) return [0, 0] endif @@ -48,6 +56,10 @@ endfunction " Returns a formatted string that can be integrated in the statusline. function! ale#statusline#Status() abort + if !exists('g:ale_buffer_info') + return 'OK' + endif + let [l:error_format, l:warning_format, l:no_errors] = g:ale_statusline_format let l:buffer = bufnr('%') diff --git a/autoload/ale/util.vim b/autoload/ale/util.vim index 6367489c..bf00051a 100644 --- a/autoload/ale/util.vim +++ b/autoload/ale/util.vim @@ -118,3 +118,19 @@ function! ale#util#BinarySearch(loclist, line, column) abort endif endwhile endfunction + +" A function for testing if a function is running inside a sandbox. +" See :help sandbox +function! ale#util#InSandbox() abort + try + call setbufvar('%', '', '') + catch /^Vim\%((\a\+)\)\=:E48/ + " E48 is the sandbox error. + return 1 + catch + " If we're not in a sandbox, we'll get another error about an + " invalid buffer variable name. + endtry + + return 0 +endfunction diff --git a/doc/ale.txt b/doc/ale.txt index abb0c000..93cd184b 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -176,6 +176,16 @@ g:ale_echo_msg_warning_str *g:ale_echo_msg_warning_str* Note |`g:ale_echo_msg_format`| should contain the `%severity%` handler +g:ale_enabled *g:ale_enabled* + + Type: |Number| + Default: `1` + + When set to `0`, this option will completely disable ALE, such that no + error checking will be performed, etc. ALE can be toggled on and off with + the |ALEToggle| command, which changes this option. + + g:ale_keep_list_window_open *g:ale_keep_list_window_open* Type: |Number| @@ -313,6 +323,17 @@ g:ale_open_list *g:ale_open_list* to `1`, in which case the window will be kept open until closed manually. +g:ale_set_highlights *g:ale_set_highlights* + + Type: |Number| + Default: `has('syntax')` + + When this option is set to `1`, highlights will be set in for erros and + warnings. The `ALEError` and `ALEWarning` highlight groups will be used to + provide highlights, and default to linking to `SpellBad` and `SpellCap` + respectively by default. + + g:ale_set_loclist *g:ale_set_loclist* Type: |Number| @@ -1060,6 +1081,12 @@ ALENextWrap *ALENextWrap* nmap <silent> <C-j> <Plug>(ale_next_wrap) < +ALEToggle *ALEToggle* + + Enable or disable ALE, including all of its autocmd events, loclist items, + quickfix items, signs, current jobs, etc. Calling this option will change + the |g:ale_enabled| variable. + =============================================================================== 7. API *ale-api* diff --git a/plugin/ale.vim b/plugin/ale.vim index d65a99cb..32aea550 100644 --- a/plugin/ale.vim +++ b/plugin/ale.vim @@ -4,11 +4,13 @@ " Sanity Checks -if exists('g:loaded_ale') +if exists('g:loaded_ale_dont_use_this_in_other_plugins_please') finish endif -let g:loaded_ale = 1 +" Set a special flag used only by this plugin for preventing doubly +" loading the script. +let g:loaded_ale_dont_use_this_in_other_plugins_please = 1 " A flag for detecting if the required features are set. if has('nvim') @@ -20,11 +22,19 @@ else endif if !s:has_features - echoerr 'ALE requires NeoVim >= 0.1.5 or Vim 8 with +timers +job +channel' - echoerr 'Please update your editor appropriately.' + " Only output a warning if editing some special files. + if index(['', 'gitcommit'], &filetype) == -1 + echoerr 'ALE requires NeoVim >= 0.1.5 or Vim 8 with +timers +job +channel' + echoerr 'Please update your editor appropriately.' + endif + + " Stop here, as it won't work. finish endif +" Set this flag so that other plugins can use it, like airline. +let g:loaded_ale = 1 + " Set the TMPDIR environment variable if it is not set automatically. " This can automatically fix some environments. if has('unix') && empty($TMPDIR) @@ -81,6 +91,9 @@ let g:ale_keep_list_window_open = get(g:, 'ale_keep_list_window_open', 0) " This is enabled by default only if the 'signs' feature exists. let g:ale_set_signs = get(g:, 'ale_set_signs', has('signs')) +" This flag can be set to 0 to disable setting error highlights. +let g:ale_set_highlights = get(g:, 'ale_set_highlights', has('syntax')) + " These variables dicatate what sign is used to indicate errors and warnings. let g:ale_sign_error = get(g:, 'ale_sign_error', '>>') let g:ale_sign_warning = get(g:, 'ale_sign_warning', '--') @@ -118,6 +131,9 @@ let g:ale_statusline_format = get(g:, 'ale_statusline_format', let g:ale_warn_about_trailing_whitespace = \ get(g:, 'ale_warn_about_trailing_whitespace', 1) +" A flag for controlling the maximum size of the command history to store. +let g:ale_max_buffer_history_size = get(g:, 'ale_max_buffer_history_size', 20) + function! s:ALEInitAuGroups() abort augroup ALERunOnTextChangedGroup autocmd! @@ -146,6 +162,13 @@ function! s:ALEInitAuGroups() abort autocmd CursorMoved,CursorHold * call ale#cursor#EchoCursorWarningWithDelay() endif augroup END + + if !g:ale_enabled + augroup! ALERunOnTextChangedGroup + augroup! ALERunOnEnterGroup + augroup! ALERunOnSaveGroup + augroup! ALECursorGroup + endif endfunction function! s:ALEToggle() abort @@ -161,6 +184,11 @@ function! s:ALEToggle() abort " Clear signs, loclist, quicklist call ale#engine#SetResults(l:buffer, []) endfor + + " Remove highlights for the current buffer now. + if g:ale_set_highlights + call ale#highlight#UpdateHighlights() + endif endif call s:ALEInitAuGroups() @@ -177,7 +205,9 @@ command! ALENextWrap :call ale#loclist_jumping#Jump('after', 1) command! ALEToggle :call s:ALEToggle() " Define command to get information about current filetype. -command! ALEInfo :call ale#linter#Info() +command! ALEInfo :call ale#debugging#Info() +" The same, but copy output to your clipboard. +command! ALEInfoToClipboard :call ale#debugging#InfoToClipboard() " <Plug> mappings for commands nnoremap <silent> <Plug>(ale_previous) :ALEPrevious<Return> diff --git a/test/test_ale_info.vader b/test/test_ale_info.vader index f8172c0c..76699d60 100644 --- a/test/test_ale_info.vader +++ b/test/test_ale_info.vader @@ -5,9 +5,46 @@ Before: call ale#linter#Reset() let g:ale_linters = {} let g:ale_linter_aliases = {} + let g:ale_buffer_info = {} + let g:globals_string = join([ + \ '', + \ ' Global Variables:', + \ '', + \ 'let g:ale_echo_cursor = 1', + \ 'let g:ale_echo_msg_error_str = ''Error''', + \ 'let g:ale_echo_msg_format = ''%s''', + \ 'let g:ale_echo_msg_warning_str = ''Warning''', + \ 'let g:ale_enabled = 1', + \ 'let g:ale_keep_list_window_open = 0', + \ 'let g:ale_lint_delay = 200', + \ 'let g:ale_lint_on_enter = 1', + \ 'let g:ale_lint_on_save = 0', + \ 'let g:ale_lint_on_text_changed = 1', + \ 'let g:ale_linter_aliases = {}', + \ 'let g:ale_linters = {}', + \ 'let g:ale_open_list = 0', + \ 'let g:ale_set_highlights = 1', + \ 'let g:ale_set_loclist = 1', + \ 'let g:ale_set_quickfix = 0', + \ 'let g:ale_set_signs = 1', + \ 'let g:ale_sign_column_always = 0', + \ 'let g:ale_sign_error = ''>>''', + \ 'let g:ale_sign_offset = 1000000', + \ 'let g:ale_sign_warning = ''--''', + \ 'let g:ale_statusline_format = [''%d error(s)'', ''%d warning(s)'', ''OK'']', + \ 'let g:ale_warn_about_trailing_whitespace = 1', + \], "\n") + let g:command_header = "\n Command History:\n" After: unlet! g:output + unlet! g:globals_string + unlet! g:command_header + let g:ale_buffer_info = {} + unlet! g:ale_testft_testlinter1_foo + unlet! g:ale_testft_testlinter1_bar + unlet! g:ale_testft2_testlinter2_foo + unlet! g:ale_testft2_testlinter2_bar Given nolintersft (Empty buffer with no linters): Execute (ALEInfo with no linters should return the right output): @@ -17,7 +54,9 @@ Execute (ALEInfo with no linters should return the right output): AssertEqual "\n \ Current Filetype: nolintersft\n \Available Linters: []\n - \ Enabled Linters: []", g:output + \ Enabled Linters: []\n + \ Linter Variables:\n + \" . g:globals_string . g:command_header, g:output Given (Empty buffer with no filetype): Execute (ALEInfo with no filetype should return the right output): @@ -27,7 +66,9 @@ Execute (ALEInfo with no filetype should return the right output): AssertEqual "\n \ Current Filetype: \n \Available Linters: []\n - \ Enabled Linters: []", g:output + \ Enabled Linters: []\n + \ Linter Variables:\n + \" . g:globals_string . g:command_header, g:output Given testft (Empty buffer): Execute (ALEInfo with a single linter should return the right output): @@ -38,7 +79,9 @@ Execute (ALEInfo with a single linter should return the right output): AssertEqual "\n \ Current Filetype: testft\n \Available Linters: ['testlinter1']\n - \ Enabled Linters: ['testlinter1']", g:output + \ Enabled Linters: ['testlinter1']\n + \ Linter Variables:\n + \" . g:globals_string . g:command_header, g:output Given testft (Empty buffer): Execute (ALEInfo with two linters should return the right output): @@ -50,7 +93,9 @@ Execute (ALEInfo with two linters should return the right output): AssertEqual "\n \ Current Filetype: testft\n \Available Linters: ['testlinter1', 'testlinter2']\n - \ Enabled Linters: ['testlinter1', 'testlinter2']", g:output + \ Enabled Linters: ['testlinter1', 'testlinter2']\n + \ Linter Variables:\n + \" . g:globals_string . g:command_header, g:output Given testft (Empty buffer): Execute (ALEInfo should calculate enabled linters correctly): @@ -63,7 +108,10 @@ Execute (ALEInfo should calculate enabled linters correctly): AssertEqual "\n \ Current Filetype: testft\n \Available Linters: ['testlinter1', 'testlinter2']\n - \ Enabled Linters: ['testlinter2']", g:output + \ Enabled Linters: ['testlinter2']\n + \ Linter Variables:\n + \", + \ "\n" . join(split(g:output, "\n")[:4], "\n") Given testft (Empty buffer): Execute (ALEInfo should only return linters for current filetype): @@ -75,7 +123,9 @@ Execute (ALEInfo should only return linters for current filetype): AssertEqual "\n \ Current Filetype: testft\n \Available Linters: ['testlinter1']\n - \ Enabled Linters: ['testlinter1']", g:output + \ Enabled Linters: ['testlinter1']\n + \ Linter Variables:\n + \" . g:globals_string . g:command_header, g:output Given testft.testft2 (Empty buffer with two filetypes): Execute (ALEInfo with compound filetypes should return linters for both of them): @@ -87,5 +137,57 @@ Execute (ALEInfo with compound filetypes should return linters for both of them) AssertEqual "\n \ Current Filetype: testft.testft2\n \Available Linters: ['testlinter1', 'testlinter2']\n - \ Enabled Linters: ['testlinter1', 'testlinter2']", g:output + \ Enabled Linters: ['testlinter1', 'testlinter2']\n + \ Linter Variables:\n + \" . g:globals_string . g:command_header, g:output +Given testft.testft2 (Empty buffer with two filetypes): +Execute (ALEInfo should return appropriately named global variables): + let g:ale_testft_testlinter1_foo = 'abc' + let g:ale_testft_testlinter1_bar = ['abc'] + let g:ale_testft2_testlinter2_foo = 123 + let g:ale_testft2_testlinter2_bar = {'x': 'y'} + + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + redir => g:output + silent ALEInfo + redir END + AssertEqual "\n + \ Current Filetype: testft.testft2\n + \Available Linters: ['testlinter1', 'testlinter2']\n + \ Enabled Linters: ['testlinter1', 'testlinter2']\n + \ Linter Variables:\n + \\n + \let g:ale_testft2_testlinter2_bar = {'x': 'y'}\n + \let g:ale_testft2_testlinter2_foo = 123\n + \let g:ale_testft_testlinter1_bar = ['abc']\n + \let g:ale_testft_testlinter1_foo = 'abc'" + \ . g:globals_string . g:command_header, g:output + +Given testft.testft2 (Empty buffer with two filetypes): +Execute (ALEInfo should return command history): + let g:ale_buffer_info[bufnr('%')] = { + \ 'history': [ + \ {'status': 'ran', 'job_id': 347, 'command': 'first command'}, + \ {'status': 'ran', 'job_id': 347, 'command': ['/bin/bash', '\c', 'last command']}, + \ ], + \} + + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + redir => g:output + silent ALEInfo + redir END + AssertEqual + \ join([ + \ '', + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Linter Variables:', + \ g:globals_string . g:command_header, + \ '(ran) ''first command''', + \ '(ran) [''/bin/bash'', ''\c'', ''last command'']', + \ ], "\n"), + \ g:output diff --git a/test/test_ale_toggle.vader b/test/test_ale_toggle.vader new file mode 100644 index 00000000..61557e8a --- /dev/null +++ b/test/test_ale_toggle.vader @@ -0,0 +1,112 @@ +Before: + let g:expected_loclist = [{ + \ 'bufnr': bufnr('%'), + \ 'lnum': 2, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'foo bar', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \}] + let g:expected_groups = [ + \ 'ALECleanupGroup', + \ 'ALECursorGroup', + \ 'ALEHighlightBufferGroup', + \ 'ALERunOnEnterGroup', + \ 'ALERunOnTextChangedGroup', + \] + + function! ToggleTestCallback(buffer, output) + return [{ + \ 'bufnr': a:buffer, + \ 'lnum': 2, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': a:output[0], + \ 'type': 'E', + \ 'nr': -1, + \}] + endfunction + + function! ParseAuGroups() + redir => l:output + silent exec 'autocmd' + redir end + + let l:results = [] + + for l:line in split(l:output, "\n") + let l:match = matchlist(l:line, '^ALE[a-zA-Z]\+Group') + + if !empty(l:match) + call add(l:results, l:match[0]) + endif + endfor + + call uniq(sort(l:results)) + + return l:results + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'ToggleTestCallback', + \ 'executable': 'echo', + \ 'command': 'echo foo bar', + \}) + +After: + unlet! g:expected_loclist + unlet! g:expected_groups + + let g:ale_buffer_info = {} + call ale#linter#Reset() + + " Toggle ALE back on if we fail when it's disabled. + if !g:ale_enabled + ALEToggle + endif + + delfunction ToggleTestCallback + delfunction ParseAuGroups + +Given foobar (Some imaginary filetype): + foo + bar + baz + +Execute(ALEToggle should reset everything and then run again): + AssertEqual 'foobar', &filetype + + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + " First check that everything is there... + AssertEqual g:expected_loclist, getloclist(0) + AssertEqual [1000001], ale#sign#FindCurrentSigns(bufnr('%')) + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + AssertEqual g:expected_groups, ParseAuGroups() + + " Now Toggle ALE off. + ALEToggle + + " Everything should be cleared. + AssertEqual [], getloclist(0) + AssertEqual [], ale#sign#FindCurrentSigns(bufnr('%')) + AssertEqual [], getmatches() + AssertEqual ['ALECleanupGroup', 'ALEHighlightBufferGroup'], ParseAuGroups() + + " Toggle ALE on, everything should be set up and run again. + ALEToggle + call ale#engine#WaitForJobs(2000) + + AssertEqual g:expected_loclist, getloclist(0) + AssertEqual [1000001], ale#sign#FindCurrentSigns(bufnr('%')) + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + AssertEqual g:expected_groups, ParseAuGroups() diff --git a/test/test_filetype_mapping.vader b/test/test_filetype_mapping.vader new file mode 100644 index 00000000..2d72491d --- /dev/null +++ b/test/test_filetype_mapping.vader @@ -0,0 +1,29 @@ +Before: + augroup TestFiletypeGroup + autocmd! + autocmd BufEnter,BufRead *.x setf xfiletype + autocmd BufEnter,BufRead *.y set filetype=yfiletype + autocmd BufEnter,BufRead *.z setlocal filetype=zfiletype + autocmd BufEnter,BufRead *.jsx set filetype=javascript.jsx + augroup END + +After: + unlet! g:map + augroup TestFiletypeGroup + autocmd! + augroup END + augroup! TestFiletypeGroup + +Execute(ALE should parse autocmd filetypes correctly): + let g:map = ale#filetypes#LoadExtensionMap() + + AssertEqual '.x', g:map['xfiletype'] + AssertEqual '.y', g:map['yfiletype'] + AssertEqual '.z', g:map['zfiletype'] + AssertEqual '.jsx', g:map['javascript.jsx'] + +Execute(ALE should guess file extensions appropriately): + " The whole string should be used, if there's a match. + AssertEqual '.jsx', ale#filetypes#GuessExtension('javascript.jsx') + " The first part should be used. + AssertEqual '.x', ale#filetypes#GuessExtension('xfiletype.yfiletype') diff --git a/test/test_fortran_handler.vader b/test/test_fortran_handler.vader new file mode 100644 index 00000000..7648ff66 --- /dev/null +++ b/test/test_fortran_handler.vader @@ -0,0 +1,118 @@ +Execute(The fortran handler should parse lines from GCC 4.1.2 correctly): + runtime ale_linters/fortran/gcc.vim + + AssertEqual + \ [ + \ { + \ 'bufnr': 357, + \ 'lnum': 4, + \ 'vcol': 0, + \ 'col': 0, + \ 'text': "Symbol ‘b’ at (1) has no IMPLICIT type", + \ 'type': 'E', + \ 'nr': -1, + \ }, + \ { + \ 'bufnr': 357, + \ 'lnum': 3, + \ 'vcol': 0, + \ 'col': 0, + \ 'text': "Symbol ‘a’ at (1) has no IMPLICIT type", + \ 'type': 'E', + \ 'nr': -1, + \ }, + \ ], + \ ale_linters#fortran#gcc#Handle(357, [ + \ " In file :4", + \ "", + \ "write(*,*) b", + \ " 1", + \ "Error: Symbol ‘b’ at (1) has no IMPLICIT type", + \ " In file :3", + \ "", + \ "write(*,*) a", + \ " 1", + \ "Error: Symbol ‘a’ at (1) has no IMPLICIT type", + \ ]) + +After: + call ale#linter#Reset() + + +Execute(The fortran handler should parse lines from GCC 4.9.3 correctly): + runtime ale_linters/fortran/gcc.vim + + AssertEqual + \ [ + \ { + \ 'bufnr': 357, + \ 'lnum': 3, + \ 'vcol': 0, + \ 'col': 12, + \ 'text': "Symbol ‘a’ at (1) has no IMPLICIT type", + \ 'type': 'E', + \ 'nr': -1, + \ }, + \ { + \ 'bufnr': 357, + \ 'lnum': 4, + \ 'vcol': 0, + \ 'col': 12, + \ 'text': "Symbol ‘b’ at (1) has no IMPLICIT type", + \ 'type': 'E', + \ 'nr': -1, + \ }, + \ ], + \ ale_linters#fortran#gcc#Handle(357, [ + \ ":3.12:", + \ "", + \ "write(*,*) a", + \ " 1", + \ "Error: Symbol ‘a’ at (1) has no IMPLICIT type", + \ ":4.12:", + \ "", + \ "write(*,*) b", + \ " 1", + \ "Error: Symbol ‘b’ at (1) has no IMPLICIT type", + \ ]) + +After: + call ale#linter#Reset() + + + +Execute(The fortran handler should parse lines from GCC 6.3.1 correctly): + runtime ale_linters/fortran/gcc.vim + + AssertEqual + \ [ + \ { + \ 'bufnr': 337, + \ 'lnum': 3, + \ 'vcol': 0, + \ 'col': 12, + \ 'text': "Symbol ‘a’ at (1) has no IMPLICIT type", + \ 'type': 'E', + \ 'nr': -1, + \ }, + \ { + \ 'bufnr': 337, + \ 'lnum': 4, + \ 'vcol': 0, + \ 'col': 12, + \ 'text': "Symbol ‘b’ at (1) has no IMPLICIT type", + \ 'type': 'E', + \ 'nr': -1, + \ }, + \ ], + \ ale_linters#fortran#gcc#Handle(337, [ + \ "<stdin>:3:12:", + \ "", + \ "Error: Symbol ‘a’ at (1) has no IMPLICIT type", + \ "<stdin>:4:12:", + \ "", + \ "Error: Symbol ‘b’ at (1) has no IMPLICIT type", + \ ]) + +After: + call ale#linter#Reset() diff --git a/test/test_highlight_clearing.vader b/test/test_highlight_clearing.vader new file mode 100644 index 00000000..bf805924 --- /dev/null +++ b/test/test_highlight_clearing.vader @@ -0,0 +1,4 @@ +Execute(ALE should be able to queue highlights and clear them for some other buffer): + " We'll just make sure that this doesn't blow up. + call ale#highlight#SetHighlights(bufnr('%') + 1, []) + call ale#highlight#UnqueueHighlights(bufnr('%') + 1) diff --git a/test/test_highlight_placement.vader b/test/test_highlight_placement.vader new file mode 100644 index 00000000..b3e40c18 --- /dev/null +++ b/test/test_highlight_placement.vader @@ -0,0 +1,61 @@ +Before: + function! GenerateResults(buffer, output) + return [ + \ { + \ 'lnum': 1, + \ 'col': 1, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'nr': -1, + \ 'type': 'E', + \ 'text': 'foo', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'nr': -1, + \ 'type': 'W', + \ 'text': 'bar', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 5, + \ 'bufnr': bufnr('%'), + \ 'vcol': 0, + \ 'nr': -1, + \ 'type': 'E', + \ 'text': 'wat', + \ }, + \] + endfunction + + call ale#linter#Define('testft', { + \ 'name': 'x', + \ 'executable': 'echo', + \ 'command': 'echo', + \ 'callback': 'GenerateResults', + \}) + +After: + delfunction GenerateResults + call ale#linter#Reset() + let g:ale_buffer_info = {} + +Given testft(A Javscript file with warnings/errors): + foo + bar + baz wat + +Execute(Highlights should be set when a linter runs): + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + AssertEqual + \ [ + \ {'group': 'ALEError', 'id': 4, 'priority': 10, 'pos1': [1, 1, 1]}, + \ {'group': 'ALEWarning', 'id': 5, 'priority': 10, 'pos1': [2, 1, 1]}, + \ {'group': 'ALEError', 'id': 6, 'priority': 10, 'pos1': [3, 5, 1]} + \ ], + \ getmatches() diff --git a/test/test_history_saving.vader b/test/test_history_saving.vader new file mode 100644 index 00000000..a06f9a78 --- /dev/null +++ b/test/test_history_saving.vader @@ -0,0 +1,69 @@ +Before: + let g:history = [] + let g:ale_buffer_info = {} + let g:ale_max_buffer_history_size = 20 + + function! CollectResults(buffer, output) + return [] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'CollectResults', + \ 'executable': 'echo', + \ 'command': 'echo command history test', + \ 'read_buffer': 0, + \}) + +After: + unlet g:history + let g:ale_buffer_info = {} + let g:ale_max_buffer_history_size = 20 + call ale#linter#Reset() + delfunction CollectResults + +Given foobar (Some imaginary filetype): + anything + +Execute(History should be set when commands are run): + AssertEqual 'foobar', &filetype + + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + let g:history = g:ale_buffer_info[bufnr('%')].history + + AssertEqual 1, len(g:history) + AssertEqual ['status', 'job_id', 'command'], keys(g:history[0]) + AssertEqual ['/bin/bash', '-c', 'echo command history test'], g:history[0].command + AssertEqual 'ran', g:history[0].status + " The Job ID will change each time, but we can check the type. + AssertEqual type(1), type(g:history[0].job_id) + +Execute(History items should be popped after going over the max): + let g:ale_buffer_info[1] = { + \ 'history': map(range(20), '{''status'': ''ran'', ''job_id'': v:val, ''command'': ''foobar''}'), + \} + + call ale#engine#AddToHistory(1, 'ran', 347, 'last command') + + AssertEqual + \ ( + \ map(range(1, 19), '{''status'': ''ran'', ''job_id'': v:val, ''command'': ''foobar''}') + \ + [{'status': 'ran', 'job_id': 347, 'command': 'last command'}] + \ ), + \ g:ale_buffer_info[1].history + +Execute(Nothing should be added to history if the size is too low): + let g:ale_max_buffer_history_size = 0 + let g:ale_buffer_info[1] = {'history': []} + + call ale#engine#AddToHistory(1, 'ran', 347, 'last command') + + AssertEqual [], g:ale_buffer_info[1].history + + let g:ale_max_buffer_history_size = -2 + + call ale#engine#AddToHistory(1, 'ran', 347, 'last command') + + AssertEqual [], g:ale_buffer_info[1].history diff --git a/test/test_sandbox_execution.vader b/test/test_sandbox_execution.vader new file mode 100644 index 00000000..a4cd84d6 --- /dev/null +++ b/test/test_sandbox_execution.vader @@ -0,0 +1,51 @@ +Before: + function! TestCallback(buffer, output) + return [ + \ { + \ 'lnum': 1, + \ 'bufnr': 1, + \ 'vcol': 0, + \ 'linter_name': 'testlinter', + \ 'nr': -1, + \ 'type': 'E', + \ 'col': 1, + \ 'text': 'Test Error', + \ }, + \] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': 'echo', + \ 'command': 'echo', + \}) + + let g:ale_buffer_info = {} + +After: + delfunction TestCallback + call ale#linter#Reset() + let g:ale_buffer_info = {} + +Given foobar (Some imaginary filetype): + foo + bar + baz + +Execute(ALE shouldn't blow up when run from a sandbox): + AssertEqual 'foobar', &filetype + + sandbox call ale#Queue(0) + sandbox call ale#Queue(1) + sandbox call ale#Lint() + +Execute(ALE shouldn't blow up if file cleanup happens in a sandbox): + let g:ale_buffer_info[3] = { + \ 'temporary_file_list': ['/tmp/foo'], + \ 'temporary_directory_list': ['/tmp/bar'], + \} + sandbox call ale#engine#RemoveManagedFiles(3) + + AssertEqual ['/tmp/foo'], g:ale_buffer_info[3].temporary_file_list + AssertEqual ['/tmp/bar'], g:ale_buffer_info[3].temporary_directory_list diff --git a/test/test_statusline_api_without_globals.vader b/test/test_statusline_api_without_globals.vader new file mode 100644 index 00000000..29677f3b --- /dev/null +++ b/test/test_statusline_api_without_globals.vader @@ -0,0 +1,19 @@ +" This file tests that statusline functions return meaningful output even +" when most of ALE itself has not been loaded. +" +" This is important for plugins which integrate with ALE like airline. + +Before: + unlet! g:ale_buffer_info + +After: + let g:ale_buffer_info = {} + +Execute(ale#statusline#Update shouldn't blow up when globals are undefined): + call ale#statusline#Update(1, []) + +Execute(ale#statusline#Count should return 0 counts when globals are undefined): + AssertEqual [0, 0], ale#statusline#Count(1) + +Execute(ale#statusline#Status should return 'OK' when globals are undefined): + AssertEqual 'OK', ale#statusline#Status() |