Fix #1619 - Rewrite lint on enter events so they behave better

This commit is contained in:
w0rp 2018-07-17 00:18:20 +01:00
parent 37df1f8ceb
commit a01fab2ee6
No known key found for this signature in database
GPG key ID: 0FC1ECAA8C81CD83
12 changed files with 156 additions and 116 deletions

View file

@ -39,35 +39,48 @@ function! ale#events#SaveEvent(buffer) abort
endif
endfunction
function! s:LintOnEnter(buffer) abort
if ale#Var(a:buffer, 'enabled')
\&& g:ale_lint_on_enter
\&& has_key(b:, 'ale_file_changed')
call remove(b:, 'ale_file_changed')
function! ale#events#LintOnEnter(buffer) abort
" Unmark a file as being changed outside of Vim after we try to check it.
call setbufvar(a:buffer, 'ale_file_changed', 0)
if ale#Var(a:buffer, 'enabled') && g:ale_lint_on_enter
call ale#Queue(0, 'lint_file', a:buffer)
endif
endfunction
function! ale#events#EnterEvent(buffer) abort
function! ale#events#ReadOrEnterEvent(buffer) abort
" Apply pattern options if the variable is set.
if get(g:, 'ale_pattern_options_enabled', 1)
\&& !empty(get(g:, 'ale_pattern_options'))
call ale#pattern_options#SetOptions(a:buffer)
endif
" When entering a buffer, we are no longer quitting it.
call setbufvar(a:buffer, 'ale_quitting', 0)
let l:filetype = getbufvar(a:buffer, '&filetype')
call setbufvar(a:buffer, 'ale_original_filetype', l:filetype)
call s:LintOnEnter(a:buffer)
" If the file changed outside of Vim, check it on BufEnter,BufRead
if getbufvar(a:buffer, 'ale_file_changed')
call ale#events#LintOnEnter(a:buffer)
endif
endfunction
function! ale#events#FileTypeEvent(buffer, new_filetype) abort
let l:filetype = getbufvar(a:buffer, 'ale_original_filetype', '')
" The old filetype will be set to an empty string by the BuFEnter event,
" and not linting when the old filetype hasn't been set yet prevents
" buffers being checked when you enter them when linting on enter is off.
let l:old_filetype = getbufvar(a:buffer, 'ale_original_filetype', v:null)
" If we're setting the filetype for the first time after it was blank,
" and the option for linting on enter is off, then we should set this
" filetype as the original filetype. Otherwise ALE will still appear to
" lint files because of the BufEnter event, etc.
if empty(l:filetype) && !ale#Var(a:buffer, 'lint_on_enter')
if l:old_filetype isnot v:null
\&& !empty(a:new_filetype)
\&& a:new_filetype isnot# l:old_filetype
" Remember what the new filetype is.
call setbufvar(a:buffer, 'ale_original_filetype', a:new_filetype)
elseif a:new_filetype isnot# l:filetype
call ale#Queue(300, 'lint_file', a:buffer)
if g:ale_lint_on_filetype_changed
call ale#Queue(300, 'lint_file', a:buffer)
endif
endif
endfunction
@ -75,7 +88,7 @@ function! ale#events#FileChangedEvent(buffer) abort
call setbufvar(a:buffer, 'ale_file_changed', 1)
if bufnr('') == a:buffer
call s:LintOnEnter(a:buffer)
call ale#events#LintOnEnter(a:buffer)
endif
endfunction
@ -87,7 +100,7 @@ function! ale#events#Init() abort
autocmd!
" These events always need to be set up.
autocmd BufEnter,BufRead * call ale#pattern_options#SetOptions(str2nr(expand('<abuf>')))
autocmd BufEnter,BufRead * call ale#events#ReadOrEnterEvent(str2nr(expand('<abuf>')))
autocmd BufWritePost * call ale#events#SaveEvent(str2nr(expand('<abuf>')))
if g:ale_enabled
@ -99,11 +112,8 @@ function! ale#events#Init() abort
autocmd TextChangedI * call ale#Queue(g:ale_lint_delay)
endif
" Handle everything that needs to happen when buffers are entered.
autocmd BufEnter * call ale#events#EnterEvent(str2nr(expand('<abuf>')))
if g:ale_lint_on_enter
autocmd BufWinEnter,BufRead * call ale#Queue(0, 'lint_file', str2nr(expand('<abuf>')))
autocmd BufWinEnter * call ale#events#LintOnEnter(str2nr(expand('<abuf>')))
" Track when the file is changed outside of Vim.
autocmd FileChangedShellPost * call ale#events#FileChangedEvent(str2nr(expand('<abuf>')))
endif

View file

@ -1,11 +1,6 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Set options in files based on regex patterns.
" A dictionary mapping regular expression patterns to arbitrary buffer
" variables to be set. Useful for configuring ALE based on filename patterns.
let g:ale_pattern_options = get(g:, 'ale_pattern_options', {})
let g:ale_pattern_options_enabled = get(g:, 'ale_pattern_options_enabled', !empty(g:ale_pattern_options))
" These variables are used to cache the sorting of patterns below.
let s:last_pattern_options = {}
let s:sorted_items = []
@ -23,17 +18,19 @@ function! s:CmpPatterns(left_item, right_item) abort
endfunction
function! ale#pattern_options#SetOptions(buffer) abort
if !get(g:, 'ale_pattern_options_enabled', 0)
\|| empty(get(g:, 'ale_pattern_options', 0))
let l:pattern_options = get(g:, 'ale_pattern_options', {})
if empty(l:pattern_options)
" Stop if no options are set.
return
endif
" The items will only be sorted whenever the patterns change.
if g:ale_pattern_options != s:last_pattern_options
let s:last_pattern_options = deepcopy(g:ale_pattern_options)
if l:pattern_options != s:last_pattern_options
let s:last_pattern_options = deepcopy(l:pattern_options)
" The patterns are sorted, so they are applied consistently.
let s:sorted_items = sort(
\ items(g:ale_pattern_options),
\ items(l:pattern_options),
\ function('s:CmpPatterns')
\)
endif

View file

@ -448,14 +448,20 @@ have even saved your changes. ALE will check your code in the following
circumstances, which can be configured with the associated options.
* When you modify a buffer. - |g:ale_lint_on_text_changed|
* On leaving insert mode. (off by default) - |g:ale_lint_on_insert_leave|
* When you open a new or modified buffer. - |g:ale_lint_on_enter|
* When you save a buffer. - |g:ale_lint_on_save|
* When the filetype changes for a buffer. - |g:ale_lint_on_filetype_changed|
* If ALE is used to check code manually. - |:ALELint|
In addition to the above options, ALE can also check buffers for errors when
you leave insert mode with |g:ale_lint_on_insert_leave|, which is off by
default. It is worth reading the documentation for every option.
*ale-lint-settings-on-startup*
It is worth reading the documentation for every option. You should configure
which events ALE will use before ALE is loaded, so it can optimize which
autocmd commands to run. You can force autocmd commands to be reloaded with
`:ALEDisable | ALEEnable`
This also applies to the autocmd commands used for |g:ale_echo_cursor|.
*ale-lint-file-linters*
@ -843,6 +849,9 @@ g:ale_echo_cursor *g:ale_echo_cursor*
this behaviour.
The format of the message can be customizable in |g:ale_echo_msg_format|.
You should set this setting once before ALE is loaded, and restart Vim if
you want to change your preferences. See |ale-lint-settings-on-startup|.
g:ale_echo_delay *g:ale_echo_delay*
*b:ale_echo_delay*
@ -1043,19 +1052,16 @@ g:ale_lint_on_enter *g:ale_lint_on_enter*
Type: |Number|
Default: `1`
When this option is set to `1`, the |BufWinEnter| and |BufRead| events will
be used to apply linters when buffers are first opened. If this is not
desired, this variable can be set to `0` in your vimrc file to disable this
behaviour.
When this option is set to `1`, the |BufWinEnter| event will be used to
apply linters when buffers are first opened. If this is not desired, this
variable can be set to `0` in your vimrc file to disable this behavior.
The |FileChangedShellPost| and |BufEnter| events will be used to check if
files have been changed outside of Vim. If a file is changed outside of
Vim, it will be checked when it is next opened.
A |BufWinLeave| event will be used to look for the |E924|, |E925|, or |E926|
errors after moving from a loclist or quickfix window to a new buffer. If
prompts for these errors are opened after moving to new buffers, then ALE
will automatically send the `<CR>` key needed to close the prompt.
You should set this setting once before ALE is loaded, and restart Vim if
you want to change your preferences. See |ale-lint-settings-on-startup|.
g:ale_lint_on_filetype_changed *g:ale_lint_on_filetype_changed*
@ -1063,14 +1069,13 @@ g:ale_lint_on_filetype_changed *g:ale_lint_on_filetype_changed*
Type: |Number|
Default: `1`
This option will cause ALE to run whenever the filetype is changed. A short
delay will be used before linting will be done, so the filetype can be
changed quickly several times in a row, but resulting in only one lint
cycle.
This option will cause ALE to run when the filetype for a file is changed
after a buffer has first been loaded. A short delay will be used before
linting will be done, so the filetype can be changed quickly several times
in a row, but resulting in only one lint cycle.
If |g:ale_lint_on_enter| is set to `0`, then ALE will not lint a file when
the filetype is initially set. Otherwise ALE would still lint files when
buffers are opened, and the option for doing so is turned off.
You should set this setting once before ALE is loaded, and restart Vim if
you want to change your preferences. See |ale-lint-settings-on-startup|.
g:ale_lint_on_save *g:ale_lint_on_save*
@ -1088,17 +1093,22 @@ g:ale_lint_on_save *g:ale_lint_on_save*
g:ale_lint_on_text_changed *g:ale_lint_on_text_changed*
Type: |String|
Default: `always`
Default: `'always'`
By default, ALE will check files with the various supported programs when
text is changed by using the |TextChanged| event. If this behaviour is not
desired, then this option can be disabled by setting it to `never`. The
|g:ale_lint_delay| variable will be used to set a |timer_start()| on a
delay, and each change to a file will continue to call |timer_stop()| and
|timer_start()| repeatedly until the timer ticks by, and the linters will be
run. The checking of files will run in the background, so it should not
inhibit editing files. This option can also be set to `insert` or `normal`
to lint when text is changed only in insert or normal mode respectively.
This option controls how ALE will check your files as you make changes.
The following values can be used.
`'always'`, `'1'`, or `1` - Check buffers on |TextChanged| or |TextChangedI|.
`'normal'` - Check buffers only on |TextChanged|.
`'insert'` - Check buffers only on |TextChangedI|.
`'never'`, `'0'`, or `0` - Never check buffers on changes.
ALE will check buffers after a short delay, with a timer which resets on
each change. The delay can be configured by adjusting the |g:ale_lint_delay|
variable.
You should set this setting once before ALE is loaded, and restart Vim if
you want to change your preferences. See |ale-lint-settings-on-startup|.
g:ale_lint_on_insert_leave *g:ale_lint_on_insert_leave*
@ -1116,6 +1126,9 @@ g:ale_lint_on_insert_leave *g:ale_lint_on_insert_leave*
" Make using Ctrl+C do the same as Escape, to trigger autocmd commands
inoremap <C-c> <Esc>
<
You should set this setting once before ALE is loaded, and restart Vim if
you want to change your preferences. See |ale-lint-settings-on-startup|.
g:ale_linter_aliases *g:ale_linter_aliases*
*b:ale_linter_aliases*
@ -1361,7 +1374,7 @@ g:ale_open_list *g:ale_open_list*
g:ale_pattern_options *g:ale_pattern_options*
Type: |Dictionary|
Default: `{}`
Default: undefined
This option maps regular expression patterns to |Dictionary| values for
buffer variables. This option can be set to automatically configure
@ -1390,12 +1403,10 @@ g:ale_pattern_options *g:ale_pattern_options*
g:ale_pattern_options_enabled *g:ale_pattern_options_enabled*
Type: |Number|
Default: `!empty(g:ale_pattern_options)`
Default: undefined
This option can be used for turning the behaviour of setting
|g:ale_pattern_options| on or off. By default, setting a single key for
|g:ale_pattern_options| will turn this option on, as long as the setting is
configured before ALE is loaded.
This option can be used for disabling pattern options. If set to `0`, ALE
will not set buffer variables per |g:ale_pattern_options|.
g:ale_set_balloons *g:ale_set_balloons*

View file

@ -31,6 +31,7 @@ Before:
call ale#test#SetDirectory('/testplugin/test')
call ale#test#SetFilename('test.txt')
call ale#linter#PreventLoading('testft')
function AddCarets(buffer, lines) abort
" map() is applied to the original lines here.

View file

@ -1,6 +1,6 @@
Given testft (An empty file):
Before:
Save g:ale_buffer_info
let g:job_started_success = 0
let g:ale_run_synchronously = 1
@ -10,6 +10,7 @@ Before:
return []
endfunction
call ale#linter#PreventLoading('testft')
call ale#linter#Define('testft', {
\ 'name': 'testlinter',
\ 'callback': 'TestCallback',
@ -18,8 +19,9 @@ Before:
\})
After:
Restore
let g:ale_run_synchronously = 0
let g:ale_buffer_info = {}
try
augroup! VaderTest
@ -31,12 +33,13 @@ After:
delfunction TestCallback
call ale#linter#Reset()
Given testft (An empty file):
Execute(Run a lint cycle with an actual job to check for ALEJobStarted):
augroup VaderTest
autocmd!
autocmd User ALEJobStarted let g:job_started_success = 1
augroup end
call ale#Lint()
ALELint
AssertEqual g:job_started_success, 1

View file

@ -79,11 +79,9 @@ Execute (All events should be set up when everything is on):
AssertEqual
\ [
\ 'BufEnter * call ale#pattern_options#SetOptions(str2nr(expand(''<abuf>'')))',
\ 'BufEnter call ale#events#EnterEvent(str2nr(expand(''<abuf>'')))',
\ 'BufReadPost * call ale#pattern_options#SetOptions(str2nr(expand(''<abuf>'')))',
\ 'BufReadPost call ale#Queue(0, ''lint_file'', str2nr(expand(''<abuf>'')))',
\ 'BufWinEnter * call ale#Queue(0, ''lint_file'', str2nr(expand(''<abuf>'')))',
\ 'BufEnter * call ale#events#ReadOrEnterEvent(str2nr(expand(''<abuf>'')))',
\ 'BufReadPost * call ale#events#ReadOrEnterEvent(str2nr(expand(''<abuf>'')))',
\ 'BufWinEnter * call ale#events#LintOnEnter(str2nr(expand(''<abuf>'')))',
\ 'BufWritePost * call ale#events#SaveEvent(str2nr(expand(''<abuf>'')))',
\ 'CursorHold * if exists(''*ale#engine#Cleanup'') | call ale#cursor#EchoCursorWarningWithDelay() | endif',
\ 'CursorMoved * if exists(''*ale#engine#Cleanup'') | call ale#cursor#EchoCursorWarningWithDelay() | endif',
@ -110,8 +108,8 @@ Execute (Only the required events should be bound even if various settings are o
AssertEqual
\ [
\ 'BufEnter * call ale#pattern_options#SetOptions(str2nr(expand(''<abuf>'')))',
\ 'BufReadPost * call ale#pattern_options#SetOptions(str2nr(expand(''<abuf>'')))',
\ 'BufEnter * call ale#events#ReadOrEnterEvent(str2nr(expand(''<abuf>'')))',
\ 'BufReadPost * call ale#events#ReadOrEnterEvent(str2nr(expand(''<abuf>'')))',
\ 'BufWritePost * call ale#events#SaveEvent(str2nr(expand(''<abuf>'')))',
\ ],
\ CheckAutocmd('ALEEvents')

View file

@ -1,9 +1,7 @@
Given testft (An empty file):
Before:
Save g:ale_run_synchronously
Save g:ale_buffer_info
let g:ale_run_synchronously = 1
let g:ale_buffer_info = {}
@ -15,6 +13,7 @@ Before:
return []
endfunction
call ale#linter#PreventLoading('testft')
call ale#linter#Define('testft', {
\ 'name': 'testlinter',
\ 'callback': 'TestCallback',
@ -26,32 +25,33 @@ After:
Restore
unlet! g:checking_buffer
delfunction TestCallback
call ale#linter#Reset()
augroup VaderTest
autocmd!
augroup end
augroup! VaderTest
Given testft (An empty file):
Execute(ALELintPre should not return success on ale#engine#IsCheckingBuffer):
augroup VaderTest
autocmd!
autocmd User ALELintPre let g:checking_buffer = ale#engine#IsCheckingBuffer(bufnr('')) ? 1 : 0
autocmd User ALELintPre let g:checking_buffer = ale#engine#IsCheckingBuffer(bufnr('')) ? 1 : 0
augroup end
call ale#Lint()
ALELint
AssertEqual g:checking_buffer, 0
Execute(ALEJobStarted should return success on ale#engine#IsCheckingBuffer):
augroup VaderTest
autocmd!
autocmd User ALEJobStarted let g:checking_buffer = ale#engine#IsCheckingBuffer(bufnr('')) ? 1 : 0
autocmd User ALEJobStarted let g:checking_buffer = ale#engine#IsCheckingBuffer(bufnr('')) ? 1 : 0
augroup end
call ale#Lint()
ALELint
AssertEqual g:checking_buffer, 1

View file

@ -67,7 +67,7 @@ Execute(The buffer should be checked after entering it after the file has change
let b:ale_file_changed = 1
set filetype=foobar
call ale#events#EnterEvent(bufnr(''))
call ale#events#ReadOrEnterEvent(bufnr(''))
AssertEqual [{
\ 'bufnr': bufnr(''),

View file

@ -1,10 +1,10 @@
Before:
Save &filetype
Save g:ale_lint_on_filetype_changed
let g:ale_lint_on_filetype_changed = 1
let g:queue_calls = []
unlet! b:ale_lint_on_enter
function! ale#Queue(...)
call add(g:queue_calls, a:000)
endfunction
@ -12,7 +12,6 @@ Before:
After:
Restore
unlet! b:ale_lint_on_enter
unlet! g:queue_calls
" Reload the ALE code to load the real function again.
@ -23,13 +22,13 @@ After:
Execute(The original filetype should be set on BufEnter):
let &filetype = 'foobar'
call ale#events#EnterEvent(bufnr(''))
call ale#events#ReadOrEnterEvent(bufnr(''))
AssertEqual 'foobar', b:ale_original_filetype
let &filetype = 'bazboz'
call ale#events#EnterEvent(bufnr(''))
call ale#events#ReadOrEnterEvent(bufnr(''))
AssertEqual 'bazboz', b:ale_original_filetype
@ -48,27 +47,31 @@ Execute(Linting should be queued when the filetype changes):
call ale#events#FileTypeEvent(bufnr(''), 'bazboz')
AssertEqual [[300, 'lint_file', bufnr('')]], g:queue_calls
" The original filetype should be updated, so we don't trigger linting
" by setting a filetype equal to what it already is.
AssertEqual 'bazboz', b:ale_original_filetype
Execute(Linting shouldn't be done when the original filetype was blank and linting on enter is off):
let b:ale_lint_on_enter = 0
let b:ale_original_filetype = ''
call ale#events#FileTypeEvent(bufnr(''), 'bazboz')
AssertEqual [], g:queue_calls
Execute(Linting should be done when the original filetype was blank and linting on enter is on):
let b:ale_lint_on_enter = 1
Execute(Linting should be done when the original filetype was blank):
let b:ale_original_filetype = ''
call ale#events#FileTypeEvent(bufnr(''), 'bazboz')
AssertEqual [[300, 'lint_file', bufnr('')]], g:queue_calls
AssertEqual 'bazboz', b:ale_original_filetype
Execute(The new filetype should become the "original" one if the original was blank and linting on enter is off):
let b:ale_lint_on_enter = 0
let b:ale_original_filetype = ''
Execute(Linting should not be done when the setting is off):
let b:ale_original_filetype = 'foobar'
let g:ale_lint_on_filetype_changed = 0
call ale#events#FileTypeEvent(bufnr(''), 'bazboz')
AssertEqual [], g:queue_calls
" We should still update the old filetype
AssertEqual 'bazboz', b:ale_original_filetype
Execute(Linting should be done when the original filetype was not set):
unlet! b:ale_original_filetype
call ale#events#FileTypeEvent(bufnr(''), 'bazboz')
AssertEqual [], g:queue_calls

View file

@ -3,9 +3,21 @@ Before:
Save g:ale_fix_on_save
Save g:ale_fixers
Save g:ale_lint_on_save
Save g:ale_set_highlights
Save g:ale_set_lists_synchronously
Save g:ale_set_loclist
Save g:ale_set_quickfix
Save g:ale_set_signs
let g:ale_echo_cursor = 0
let g:ale_run_synchronously = 1
let g:ale_set_lists_synchronously = 1
" Disable the things we don't need, but leave enabled what we do.
let g:ale_echo_cursor = 0
let g:ale_set_signs = 0
let g:ale_set_quickfix = 0
let g:ale_set_loclist = 1
let g:ale_set_highlights = 0
let g:ale_echo_cursor = 0
function! TestCallback(buffer, output)
return [{'lnum': 1, 'col': 1, 'text': 'xxx'}]
@ -19,6 +31,7 @@ Before:
\ 'testft': ['AddLine'],
\}
call ale#linter#PreventLoading('testft')
call ale#linter#Define('testft', {
\ 'name': 'testlinter',
\ 'callback': 'TestCallback',

View file

@ -1,8 +1,12 @@
Before:
Save g:ale_pattern_options
Save g:ale_pattern_options_enabled
Save b:ale_quitting
Save b:ale_original_filetype
Save &filetype
unlet! b:ale_file_changed
let g:ale_pattern_options_enabled = 1
let g:ale_pattern_options = {}
@ -21,7 +25,7 @@ After:
Execute(The pattern options function should work when there are no patterns):
call ale#test#SetFilename('foobar.js')
call ale#pattern_options#SetOptions(bufnr(''))
call ale#events#ReadOrEnterEvent(bufnr(''))
Execute(Buffer variables should be set when filename patterns match):
let g:ale_pattern_options = {
@ -33,13 +37,13 @@ Execute(Buffer variables should be set when filename patterns match):
\}
call ale#test#SetFilename('foobar.js')
call ale#pattern_options#SetOptions(bufnr(''))
call ale#events#ReadOrEnterEvent(bufnr(''))
AssertEqual 0, b:ale_enabled
AssertEqual 0, b:some_option
call ale#test#SetFilename('bazboz.js')
call ale#pattern_options#SetOptions(bufnr(''))
call ale#events#ReadOrEnterEvent(bufnr(''))
AssertEqual 1, b:ale_enabled
AssertEqual 347, b:some_option
@ -61,7 +65,7 @@ Execute(Multiple pattern matches should be applied):
\}
call ale#test#SetFilename('foobar.js')
call ale#pattern_options#SetOptions(bufnr(''))
call ale#events#ReadOrEnterEvent(bufnr(''))
AssertEqual 1, b:ale_enabled
AssertEqual 666, b:some_option
@ -71,7 +75,7 @@ Execute(Patterns should not be applied when the setting is disabled):
let g:ale_pattern_options = {'foo': {'some_option': 666}}
call ale#test#SetFilename('foobar.js')
call ale#pattern_options#SetOptions(bufnr(''))
call ale#events#ReadOrEnterEvent(bufnr(''))
AssertEqual 0, b:some_option
@ -81,13 +85,13 @@ Execute(Patterns should be applied after the Dictionary changes):
let g:ale_pattern_options = {}
call ale#pattern_options#SetOptions(bufnr(''))
call ale#events#ReadOrEnterEvent(bufnr(''))
AssertEqual 0, b:some_option
let g:ale_pattern_options['foo'] = {'some_option': 666}
call ale#pattern_options#SetOptions(bufnr(''))
call ale#events#ReadOrEnterEvent(bufnr(''))
AssertEqual 666, b:some_option
@ -96,8 +100,8 @@ Execute(SetOptions should tolerate settings being unset):
unlet! g:ale_pattern_options
unlet! g:ale_pattern_options_enabled
call ale#pattern_options#SetOptions(bufnr(''))
call ale#events#ReadOrEnterEvent(bufnr(''))
let g:ale_pattern_options_enabled = 1
call ale#pattern_options#SetOptions(bufnr(''))
call ale#events#ReadOrEnterEvent(bufnr(''))

View file

@ -18,10 +18,10 @@ Execute(QuitEvent should set b:ale_quitting some time from the clock):
Assert b:ale_quitting >= b:time_before
Assert b:ale_quitting <= ale#events#ClockMilliseconds()
Execute(EnterEvent should set b:ale_quitting to 0):
Execute(ReadOrEnterEvent should set b:ale_quitting to 0):
let b:ale_quitting = 1
call ale#events#EnterEvent(bufnr(''))
call ale#events#ReadOrEnterEvent(bufnr(''))
AssertEqual 0, b:ale_quitting