1
0
mirror of https://github.com/amix/vimrc synced 2025-06-16 01:25:00 +08:00

Updated plugins

This commit is contained in:
Amir Salihefendic
2018-07-04 12:53:25 +02:00
parent 993ed55304
commit 2f164fee9b
70 changed files with 2044 additions and 761 deletions

View File

@ -6,35 +6,12 @@
let g:ale_echo_msg_error_str = get(g:, 'ale_echo_msg_error_str', 'Error')
let g:ale_echo_msg_info_str = get(g:, 'ale_echo_msg_info_str', 'Info')
let g:ale_echo_msg_warning_str = get(g:, 'ale_echo_msg_warning_str', 'Warning')
" Ignoring linters, for disabling some, or ignoring LSP diagnostics.
let g:ale_linters_ignore = get(g:, 'ale_linters_ignore', {})
let s:lint_timer = -1
let s:queued_buffer_number = -1
let s:should_lint_file_for_buffer = {}
let s:error_delay_ms = 1000 * 60 * 2
let s:timestamp_map = {}
" Given a key for a script variable for tracking the time to wait until
" a given function should be called, a funcref for a function to call, and
" a List of arguments, call the function and return whatever value it returns.
"
" If the function throws an exception, then the function will not be called
" for a while, and 0 will be returned instead.
function! ale#CallWithCooldown(timestamp_key, func, arglist) abort
let l:now = ale#util#ClockMilliseconds()
if l:now < get(s:timestamp_map, a:timestamp_key, -1)
return 0
endif
let s:timestamp_map[a:timestamp_key] = l:now + s:error_delay_ms
let l:return_value = call(a:func, a:arglist)
let s:timestamp_map[a:timestamp_key] = -1
return l:return_value
endfunction
" Return 1 if a file is too large for ALE to handle.
function! ale#FileTooLarge(buffer) abort
@ -114,30 +91,22 @@ function! ale#Queue(delay, ...) abort
let l:linting_flag = get(a:000, 0, '')
let l:buffer = get(a:000, 1, bufnr(''))
return ale#CallWithCooldown(
\ 'dont_queue_until',
\ function('s:ALEQueueImpl'),
\ [a:delay, l:linting_flag, l:buffer],
\)
endfunction
function! s:ALEQueueImpl(delay, linting_flag, buffer) abort
if a:linting_flag isnot# '' && a:linting_flag isnot# 'lint_file'
if l:linting_flag isnot# '' && l:linting_flag isnot# 'lint_file'
throw "linting_flag must be either '' or 'lint_file'"
endif
if type(a:buffer) != type(0)
if type(l:buffer) != type(0)
throw 'buffer_number must be a Number'
endif
if ale#ShouldDoNothing(a:buffer)
if ale#ShouldDoNothing(l:buffer)
return
endif
" Remember that we want to check files for this buffer.
" We will remember this until we finally run the linters, via any event.
if a:linting_flag is# 'lint_file'
let s:should_lint_file_for_buffer[a:buffer] = 1
if l:linting_flag is# 'lint_file'
let s:should_lint_file_for_buffer[l:buffer] = 1
endif
if s:lint_timer != -1
@ -145,24 +114,24 @@ function! s:ALEQueueImpl(delay, linting_flag, buffer) abort
let s:lint_timer = -1
endif
let l:linters = ale#linter#Get(getbufvar(a:buffer, '&filetype'))
let l:linters = ale#linter#Get(getbufvar(l:buffer, '&filetype'))
" Don't set up buffer data and so on if there are no linters to run.
if empty(l:linters)
" If we have some previous buffer data, then stop any jobs currently
" running and clear everything.
if has_key(g:ale_buffer_info, a:buffer)
call ale#engine#RunLinters(a:buffer, [], 1)
if has_key(g:ale_buffer_info, l:buffer)
call ale#engine#RunLinters(l:buffer, [], 1)
endif
return
endif
if a:delay > 0
let s:queued_buffer_number = a:buffer
let s:queued_buffer_number = l:buffer
let s:lint_timer = timer_start(a:delay, function('ale#Lint'))
else
call ale#Lint(-1, a:buffer)
call ale#Lint(-1, l:buffer)
endif
endfunction
@ -178,30 +147,29 @@ function! ale#Lint(...) abort
let l:buffer = bufnr('')
endif
return ale#CallWithCooldown(
\ 'dont_lint_until',
\ function('s:ALELintImpl'),
\ [l:buffer],
\)
endfunction
function! s:ALELintImpl(buffer) abort
if ale#ShouldDoNothing(a:buffer)
if ale#ShouldDoNothing(l:buffer)
return
endif
" Use the filetype from the buffer
let l:linters = ale#linter#Get(getbufvar(a:buffer, '&filetype'))
let l:filetype = getbufvar(l:buffer, '&filetype')
let l:linters = ale#linter#Get(l:filetype)
let l:should_lint_file = 0
" Check if we previously requested checking the file.
if has_key(s:should_lint_file_for_buffer, a:buffer)
unlet s:should_lint_file_for_buffer[a:buffer]
if has_key(s:should_lint_file_for_buffer, l:buffer)
unlet s:should_lint_file_for_buffer[l:buffer]
" Lint files if they exist.
let l:should_lint_file = filereadable(expand('#' . a:buffer . ':p'))
let l:should_lint_file = filereadable(expand('#' . l:buffer . ':p'))
endif
call ale#engine#RunLinters(a:buffer, l:linters, l:should_lint_file)
" Apply ignore lists for linters only if needed.
let l:ignore_config = ale#Var(l:buffer, 'linters_ignore')
let l:linters = !empty(l:ignore_config)
\ ? ale#engine#ignore#Exclude(l:filetype, l:linters, l:ignore_config)
\ : l:linters
call ale#engine#RunLinters(l:buffer, l:linters, l:should_lint_file)
endfunction
" Reset flags indicating that files should be checked for all buffers.
@ -209,10 +177,6 @@ function! ale#ResetLintFileMarkers() abort
let s:should_lint_file_for_buffer = {}
endfunction
function! ale#ResetErrorDelays() abort
let s:timestamp_map = {}
endfunction
let g:ale_has_override = get(g:, 'ale_has_override', {})
" Call has(), but check a global Dictionary so we can force flags on or off

View File

@ -1,77 +0,0 @@
function! ale#autocmd#InitAuGroups() abort
" This value used to be a Boolean as a Number, and is now a String.
let l:text_changed = '' . g:ale_lint_on_text_changed
augroup ALEPatternOptionsGroup
autocmd!
autocmd BufEnter,BufRead * call ale#pattern_options#SetOptions(str2nr(expand('<abuf>')))
augroup END
augroup ALERunOnTextChangedGroup
autocmd!
if g:ale_enabled
if l:text_changed is? 'always' || l:text_changed is# '1'
autocmd TextChanged,TextChangedI * call ale#Queue(g:ale_lint_delay)
elseif l:text_changed is? 'normal'
autocmd TextChanged * call ale#Queue(g:ale_lint_delay)
elseif l:text_changed is? 'insert'
autocmd TextChangedI * call ale#Queue(g:ale_lint_delay)
endif
endif
augroup END
augroup ALERunOnEnterGroup
autocmd!
if g:ale_enabled
" Handle everything that needs to happen when buffers are entered.
autocmd BufEnter * call ale#events#EnterEvent(str2nr(expand('<abuf>')))
endif
if g:ale_enabled && g:ale_lint_on_enter
autocmd BufWinEnter,BufRead * call ale#Queue(0, 'lint_file', str2nr(expand('<abuf>')))
" Track when the file is changed outside of Vim.
autocmd FileChangedShellPost * call ale#events#FileChangedEvent(str2nr(expand('<abuf>')))
endif
augroup END
augroup ALERunOnFiletypeChangeGroup
autocmd!
if g:ale_enabled && g:ale_lint_on_filetype_changed
" Only start linting if the FileType actually changes after
" opening a buffer. The FileType will fire when buffers are opened.
autocmd FileType * call ale#events#FileTypeEvent(
\ str2nr(expand('<abuf>')),
\ expand('<amatch>')
\)
endif
augroup END
augroup ALERunOnSaveGroup
autocmd!
autocmd BufWritePost * call ale#events#SaveEvent(str2nr(expand('<abuf>')))
augroup END
augroup ALERunOnInsertLeave
autocmd!
if g:ale_enabled && g:ale_lint_on_insert_leave
autocmd InsertLeave * call ale#Queue(0)
endif
augroup END
augroup ALECursorGroup
autocmd!
if g:ale_enabled && g:ale_echo_cursor
autocmd CursorMoved,CursorHold * call ale#cursor#EchoCursorWarningWithDelay()
" Look for a warning to echo as soon as we leave Insert mode.
" The script's position variable used when moving the cursor will
" not be changed here.
autocmd InsertLeave * call ale#cursor#EchoCursorWarning()
endif
augroup END
if !g:ale_enabled
augroup! ALERunOnTextChangedGroup
augroup! ALERunOnEnterGroup
augroup! ALERunOnInsertLeave
augroup! ALECursorGroup
endif
endfunction

View File

@ -34,7 +34,11 @@ function! ale#balloon#Expr() abort
endfunction
function! ale#balloon#Disable() abort
set noballooneval noballoonevalterm
if has('balloon_eval_term')
set noballoonevalterm
endif
set noballooneval
set balloonexpr=
endfunction

View File

@ -134,7 +134,11 @@ function! s:ReplaceCompleteopt() abort
let b:ale_old_completopt = &l:completeopt
endif
let &l:completeopt = 'menu,menuone,preview,noselect,noinsert'
if &l:completeopt =~# 'preview'
let &l:completeopt = 'menu,menuone,preview,noselect,noinsert'
else
let &l:completeopt = 'menu,menuone,noselect,noinsert'
endif
endfunction
function! ale#completion#OmniFunc(findstart, base) abort
@ -389,14 +393,13 @@ function! s:GetLSPCompletions(linter) abort
\ ? function('ale#completion#HandleTSServerResponse')
\ : function('ale#completion#HandleLSPResponse')
let l:lsp_details = ale#linter#StartLSP(l:buffer, a:linter, l:Callback)
let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
if empty(l:lsp_details)
return 0
endif
let l:id = l:lsp_details.connection_id
let l:root = l:lsp_details.project_root
if a:linter.lsp is# 'tsserver'
let l:message = ale#lsp#tsserver_message#Completions(
@ -408,7 +411,7 @@ function! s:GetLSPCompletions(linter) abort
else
" Send a message saying the buffer has changed first, otherwise
" completions won't know what text is nearby.
call ale#lsp#Send(l:id, ale#lsp#message#DidChange(l:buffer), l:root)
call ale#lsp#NotifyForChanges(l:lsp_details)
" For LSP completions, we need to clamp the column to the length of
" the line. python-language-server and perhaps others do not implement
@ -424,7 +427,7 @@ function! s:GetLSPCompletions(linter) abort
\)
endif
let l:request_id = ale#lsp#Send(l:id, l:message, l:root)
let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root)
if l:request_id
let b:ale_completion_info.conn_id = l:id

View File

@ -56,10 +56,6 @@ function! s:StopCursorTimer() abort
endfunction
function! ale#cursor#EchoCursorWarning(...) abort
return ale#CallWithCooldown('dont_echo_until', function('s:EchoImpl'), [])
endfunction
function! s:EchoImpl() abort
if !g:ale_echo_cursor
return
endif

View File

@ -214,10 +214,15 @@ function! ale#debugging#Info() abort
" This must be done after linters are loaded.
let l:variable_list = s:GetLinterVariables(l:filetype, l:enabled_names)
let l:fixers = ale#fix#registry#SuggestedFixers(l:filetype)
let l:fixers = uniq(sort(l:fixers[0] + l:fixers[1]))
let l:fixers_string = join(map(copy(l:fixers), '"\n " . v:val'), '')
call s:Echo(' Current Filetype: ' . l:filetype)
call s:Echo('Available Linters: ' . string(l:all_names))
call s:EchoLinterAliases(l:all_linters)
call s:Echo(' Enabled Linters: ' . string(l:enabled_names))
call s:Echo(' Suggested Fixers: ' . l:fixers_string)
call s:Echo(' Linter Variables:')
call s:Echo('')
call s:EchoLinterVariables(l:variable_list)

View File

@ -65,14 +65,13 @@ function! s:GoToLSPDefinition(linter, options) abort
\ ? function('ale#definition#HandleTSServerResponse')
\ : function('ale#definition#HandleLSPResponse')
let l:lsp_details = ale#linter#StartLSP(l:buffer, a:linter, l:Callback)
let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
if empty(l:lsp_details)
return 0
endif
let l:id = l:lsp_details.connection_id
let l:root = l:lsp_details.project_root
if a:linter.lsp is# 'tsserver'
let l:message = ale#lsp#tsserver_message#Definition(
@ -83,7 +82,7 @@ function! s:GoToLSPDefinition(linter, options) abort
else
" Send a message saying the buffer has changed first, or the
" definition position probably won't make sense.
call ale#lsp#Send(l:id, ale#lsp#message#DidChange(l:buffer), l:root)
call ale#lsp#NotifyForChanges(l:lsp_details)
let l:column = min([l:column, len(getline(l:line))])
@ -93,7 +92,7 @@ function! s:GoToLSPDefinition(linter, options) abort
let l:message = ale#lsp#message#Definition(l:buffer, l:line, l:column)
endif
let l:request_id = ale#lsp#Send(l:id, l:message, l:root)
let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root)
let s:go_to_definition_map[l:request_id] = {
\ 'open_in_tab': get(a:options, 'open_in_tab', 0),

View File

@ -14,11 +14,6 @@ if !has_key(s:, 'job_info_map')
let s:job_info_map = {}
endif
" Associates LSP connection IDs with linter names.
if !has_key(s:, 'lsp_linter_map')
let s:lsp_linter_map = {}
endif
if !has_key(s:, 'executable_cache_map')
let s:executable_cache_map = {}
endif
@ -79,16 +74,6 @@ function! ale#engine#InitBufferInfo(buffer) abort
return 0
endfunction
" Clear LSP linter data for the linting engine.
function! ale#engine#ClearLSPData() abort
let s:lsp_linter_map = {}
endfunction
" Just for tests.
function! ale#engine#SetLSPLinterMap(replacement_map) abort
let s:lsp_linter_map = a:replacement_map
endfunction
" This function is documented and part of the public API.
"
" Return 1 if ALE is busy checking a given buffer
@ -241,88 +226,6 @@ function! s:HandleExit(job_id, exit_code) abort
call ale#engine#HandleLoclist(l:linter.name, l:buffer, l:loclist)
endfunction
function! s:HandleLSPDiagnostics(conn_id, response) abort
let l:linter_name = s:lsp_linter_map[a:conn_id]
let l:filename = ale#path#FromURI(a:response.params.uri)
let l:buffer = bufnr(l:filename)
if l:buffer <= 0
return
endif
let l:loclist = ale#lsp#response#ReadDiagnostics(a:response)
call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist)
endfunction
function! s:HandleTSServerDiagnostics(response, error_type) abort
let l:buffer = bufnr(a:response.body.file)
let l:info = get(g:ale_buffer_info, l:buffer, {})
if empty(l:info)
return
endif
let l:thislist = ale#lsp#response#ReadTSServerDiagnostics(a:response)
" tsserver sends syntax and semantic errors in separate messages, so we
" have to collect the messages separately for each buffer and join them
" back together again.
if a:error_type is# 'syntax'
let l:info.syntax_loclist = l:thislist
else
let l:info.semantic_loclist = l:thislist
endif
let l:loclist = get(l:info, 'semantic_loclist', [])
\ + get(l:info, 'syntax_loclist', [])
call ale#engine#HandleLoclist('tsserver', l:buffer, l:loclist)
endfunction
function! s:HandleLSPErrorMessage(linter_name, response) abort
if !g:ale_history_enabled || !g:ale_history_log_output
return
endif
if empty(a:linter_name)
return
endif
let l:message = ale#lsp#response#GetErrorMessage(a:response)
if empty(l:message)
return
endif
" This global variable is set here so we don't load the debugging.vim file
" until someone uses :ALEInfo.
let g:ale_lsp_error_messages = get(g:, 'ale_lsp_error_messages', {})
if !has_key(g:ale_lsp_error_messages, a:linter_name)
let g:ale_lsp_error_messages[a:linter_name] = []
endif
call add(g:ale_lsp_error_messages[a:linter_name], l:message)
endfunction
function! ale#engine#HandleLSPResponse(conn_id, response) abort
let l:method = get(a:response, 'method', '')
let l:linter_name = get(s:lsp_linter_map, a:conn_id, '')
if get(a:response, 'jsonrpc', '') is# '2.0' && has_key(a:response, 'error')
call s:HandleLSPErrorMessage(l:linter_name, a:response)
elseif l:method is# 'textDocument/publishDiagnostics'
call s:HandleLSPDiagnostics(a:conn_id, a:response)
elseif get(a:response, 'type', '') is# 'event'
\&& get(a:response, 'event', '') is# 'semanticDiag'
call s:HandleTSServerDiagnostics(a:response, 'semantic')
elseif get(a:response, 'type', '') is# 'event'
\&& get(a:response, 'event', '') is# 'syntaxDiag'
call s:HandleTSServerDiagnostics(a:response, 'syntax')
endif
endfunction
function! ale#engine#SetResults(buffer, loclist) abort
let l:linting_is_done = !ale#engine#IsCheckingBuffer(a:buffer)
@ -367,9 +270,6 @@ function! ale#engine#SetResults(buffer, loclist) abort
" Call user autocommands. This allows users to hook into ALE's lint cycle.
silent doautocmd <nomodeline> User ALELintPost
" remove in 2.0
" Old DEPRECATED name; call it for backwards compatibility.
silent doautocmd <nomodeline> User ALELint
endif
endfunction
@ -739,44 +639,6 @@ function! s:StopCurrentJobs(buffer, include_lint_file_jobs) abort
let l:info.active_linter_list = l:new_active_linter_list
endfunction
function! s:CheckWithLSP(buffer, linter) abort
let l:info = g:ale_buffer_info[a:buffer]
let l:lsp_details = ale#linter#StartLSP(
\ a:buffer,
\ a:linter,
\ function('ale#engine#HandleLSPResponse'),
\)
if empty(l:lsp_details)
return 0
endif
let l:id = l:lsp_details.connection_id
let l:root = l:lsp_details.project_root
" Remember the linter this connection is for.
let s:lsp_linter_map[l:id] = a:linter.name
let l:change_message = a:linter.lsp is# 'tsserver'
\ ? ale#lsp#tsserver_message#Geterr(a:buffer)
\ : ale#lsp#message#DidChange(a:buffer)
let l:request_id = ale#lsp#Send(l:id, l:change_message, l:root)
" If this was a file save event, also notify the server of that.
if a:linter.lsp isnot# 'tsserver'
\&& getbufvar(a:buffer, 'ale_save_event_fired', 0)
let l:save_message = ale#lsp#message#DidSave(a:buffer)
let l:request_id = ale#lsp#Send(l:id, l:save_message, l:root)
endif
if l:request_id != 0
if index(l:info.active_linter_list, a:linter.name) < 0
call add(l:info.active_linter_list, a:linter.name)
endif
endif
return l:request_id != 0
endfunction
function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort
" Figure out which linters are still enabled, and remove
@ -832,7 +694,7 @@ endfunction
" Returns 1 if the linter was successfully run.
function! s:RunLinter(buffer, linter) abort
if !empty(a:linter.lsp)
return s:CheckWithLSP(a:buffer, a:linter)
return ale#lsp_linter#CheckWithLSP(a:buffer, a:linter)
else
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
@ -914,7 +776,7 @@ endfunction
" The time taken will be a very rough approximation, and more time may be
" permitted than is specified.
function! ale#engine#WaitForJobs(deadline) abort
let l:start_time = ale#util#ClockMilliseconds()
let l:start_time = ale#events#ClockMilliseconds()
if l:start_time == 0
throw 'Failed to read milliseconds from the clock!'
@ -945,7 +807,7 @@ function! ale#engine#WaitForJobs(deadline) abort
for l:job_id in l:job_list
if ale#job#IsRunning(l:job_id)
let l:now = ale#util#ClockMilliseconds()
let l:now = ale#events#ClockMilliseconds()
if l:now - l:start_time > a:deadline
" Stop waiting after a timeout, so we don't wait forever.
@ -982,7 +844,7 @@ function! ale#engine#WaitForJobs(deadline) abort
if l:has_new_jobs
" We have to wait more. Offset the timeout by the time taken so far.
let l:now = ale#util#ClockMilliseconds()
let l:now = ale#events#ClockMilliseconds()
let l:new_deadline = a:deadline - (l:now - l:start_time)
if l:new_deadline <= 0

View File

@ -0,0 +1,46 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Code for ignoring linters. Only loaded and if configured.
" Given a filetype and a configuration for ignoring linters, return a List of
" Strings for linter names to ignore.
function! ale#engine#ignore#GetList(filetype, config) abort
if type(a:config) is type([])
return a:config
endif
if type(a:config) is type({})
let l:names_to_remove = []
for l:part in split(a:filetype , '\.')
call extend(l:names_to_remove, get(a:config, l:part, []))
endfor
return l:names_to_remove
endif
return []
endfunction
" Given a List of linter descriptions, exclude the linters to be ignored.
function! ale#engine#ignore#Exclude(filetype, all_linters, config) abort
let l:names_to_remove = ale#engine#ignore#GetList(a:filetype, a:config)
let l:filtered_linters = []
for l:linter in a:all_linters
let l:name_list = [l:linter.name] + l:linter.aliases
let l:should_include = 1
for l:name in l:name_list
if index(l:names_to_remove, l:name) >= 0
let l:should_include = 0
break
endif
endfor
if l:should_include
call add(l:filtered_linters, l:linter)
endif
endfor
return l:filtered_linters
endfunction

View File

@ -1,14 +1,25 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: ALE functions for autocmd events.
" Get the number of milliseconds since some vague, but consistent, point in
" the past.
"
" This function can be used for timing execution, etc.
"
" The time will be returned as a Number.
function! ale#events#ClockMilliseconds() abort
return float2nr(reltimefloat(reltime()) * 1000)
endfunction
function! ale#events#QuitEvent(buffer) abort
" Remember when ALE is quitting for BufWrite, etc.
call setbufvar(a:buffer, 'ale_quitting', ale#util#ClockMilliseconds())
call setbufvar(a:buffer, 'ale_quitting', ale#events#ClockMilliseconds())
endfunction
function! ale#events#QuitRecently(buffer) abort
let l:time = getbufvar(a:buffer, 'ale_quitting', 0)
return l:time && ale#util#ClockMilliseconds() - l:time < 1000
return l:time && ale#events#ClockMilliseconds() - l:time < 1000
endfunction
function! ale#events#SaveEvent(buffer) abort
@ -67,3 +78,56 @@ function! ale#events#FileChangedEvent(buffer) abort
call s:LintOnEnter(a:buffer)
endif
endfunction
function! ale#events#Init() abort
" This value used to be a Boolean as a Number, and is now a String.
let l:text_changed = '' . g:ale_lint_on_text_changed
augroup ALEEvents
autocmd!
" These events always need to be set up.
autocmd BufEnter,BufRead * call ale#pattern_options#SetOptions(str2nr(expand('<abuf>')))
autocmd BufWritePost * call ale#events#SaveEvent(str2nr(expand('<abuf>')))
if g:ale_enabled
if l:text_changed is? 'always' || l:text_changed is# '1'
autocmd TextChanged,TextChangedI * call ale#Queue(g:ale_lint_delay)
elseif l:text_changed is? 'normal'
autocmd TextChanged * call ale#Queue(g:ale_lint_delay)
elseif l:text_changed is? 'insert'
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>')))
" Track when the file is changed outside of Vim.
autocmd FileChangedShellPost * call ale#events#FileChangedEvent(str2nr(expand('<abuf>')))
endif
if g:ale_lint_on_filetype_changed
" Only start linting if the FileType actually changes after
" opening a buffer. The FileType will fire when buffers are opened.
autocmd FileType * call ale#events#FileTypeEvent(
\ str2nr(expand('<abuf>')),
\ expand('<amatch>')
\)
endif
if g:ale_lint_on_insert_leave
autocmd InsertLeave * call ale#Queue(0)
endif
if g:ale_echo_cursor
autocmd CursorMoved,CursorHold * if exists('*ale#engine#Cleanup') | call ale#cursor#EchoCursorWarningWithDelay() | endif
" Look for a warning to echo as soon as we leave Insert mode.
" The script's position variable used when moving the cursor will
" not be changed here.
autocmd InsertLeave * if exists('*ale#engine#Cleanup') | call ale#cursor#EchoCursorWarning() | endif
endif
endif
augroup END
endfunction

View File

@ -356,9 +356,21 @@ function! s:RunFixer(options) abort
call ale#fix#ApplyFixes(l:buffer, l:input)
endfunction
function! s:GetCallbacks(buffer, linters) abort
if len(a:linters)
let l:callback_list = a:linters
function! s:AddSubCallbacks(full_list, callbacks) abort
if type(a:callbacks) == type('')
call add(a:full_list, a:callbacks)
elseif type(a:callbacks) == type([])
call extend(a:full_list, a:callbacks)
else
return 0
endif
return 1
endfunction
function! s:GetCallbacks(buffer, fixers) abort
if len(a:fixers)
let l:callback_list = a:fixers
elseif type(get(b:, 'ale_fixers')) is type([])
" Lists can be used for buffer-local variables only
let l:callback_list = b:ale_fixers
@ -367,16 +379,18 @@ function! s:GetCallbacks(buffer, linters) abort
" callbacks to run.
let l:fixers = ale#Var(a:buffer, 'fixers')
let l:callback_list = []
let l:matched = 0
for l:sub_type in split(&filetype, '\.')
let l:sub_type_callacks = get(l:fixers, l:sub_type, [])
if type(l:sub_type_callacks) == type('')
call add(l:callback_list, l:sub_type_callacks)
else
call extend(l:callback_list, l:sub_type_callacks)
if s:AddSubCallbacks(l:callback_list, get(l:fixers, l:sub_type))
let l:matched = 1
endif
endfor
" If we couldn't find fixers for a filetype, default to '*' fixers.
if !l:matched
call s:AddSubCallbacks(l:callback_list, get(l:fixers, '*'))
endif
endif
if empty(l:callback_list)

View File

@ -22,6 +22,11 @@ let s:default_registry = {
\ 'suggested_filetypes': ['python'],
\ 'description': 'Fix PEP8 issues with black.',
\ },
\ 'tidy': {
\ 'function': 'ale#fixers#tidy#Fix',
\ 'suggested_filetypes': ['html'],
\ 'description': 'Fix HTML files with tidy.',
\ },
\ 'prettier_standard': {
\ 'function': 'ale#fixers#prettier_standard#Fix',
\ 'suggested_filetypes': ['javascript'],
@ -51,7 +56,7 @@ let s:default_registry = {
\ },
\ 'prettier': {
\ 'function': 'ale#fixers#prettier#Fix',
\ 'suggested_filetypes': ['javascript', 'typescript', 'json', 'css', 'scss', 'less', 'markdown', 'graphql', 'vue'],
\ 'suggested_filetypes': ['javascript', 'typescript', 'css', 'less', 'scss', 'json', 'json5', 'graphql', 'markdown', 'vue'],
\ 'description': 'Apply prettier to a file.',
\ },
\ 'prettier_eslint': {
@ -205,6 +210,11 @@ let s:default_registry = {
\ 'suggested_filetypes': ['qml'],
\ 'description': 'Fix QML files with qmlfmt.',
\ },
\ 'dartfmt': {
\ 'function': 'ale#fixers#dartfmt#Fix',
\ 'suggested_filetypes': ['dart'],
\ 'description': 'Fix Dart files with dartfmt.',
\ },
\}
" Reset the function registry to the default entries.
@ -345,8 +355,7 @@ function! ale#fix#registry#CompleteFixers(ArgLead, CmdLine, CursorPos) abort
return filter(ale#fix#registry#GetApplicableFixers(&filetype), 'v:val =~? a:ArgLead')
endfunction
" Suggest functions to use from the registry.
function! ale#fix#registry#Suggest(filetype) abort
function! ale#fix#registry#SuggestedFixers(filetype) abort
let l:type_list = split(a:filetype, '\.')
let l:filetype_fixer_list = []
@ -372,6 +381,15 @@ function! ale#fix#registry#Suggest(filetype) abort
endif
endfor
return [l:filetype_fixer_list, l:generic_fixer_list]
endfunction
" Suggest functions to use from the registry.
function! ale#fix#registry#Suggest(filetype) abort
let l:suggested = ale#fix#registry#SuggestedFixers(a:filetype)
let l:filetype_fixer_list = l:suggested[0]
let l:generic_fixer_list = l:suggested[1]
let l:filetype_fixer_header = !empty(l:filetype_fixer_list)
\ ? ['Try the following fixers appropriate for the filetype:', '']
\ : []

View File

@ -0,0 +1,18 @@
" Author: reisub0 <reisub0@gmail.com>
" Description: Integration of dartfmt with ALE.
call ale#Set('dart_dartfmt_executable', 'dartfmt')
call ale#Set('dart_dartfmt_options', '')
function! ale#fixers#dartfmt#Fix(buffer) abort
let l:executable = ale#Var(a:buffer, 'dart_dartfmt_executable')
let l:options = ale#Var(a:buffer, 'dart_dartfmt_options')
return {
\ 'command': ale#Escape(l:executable)
\ . ' -w'
\ . (empty(l:options) ? '' : ' ' . l:options)
\ . ' %t',
\ 'read_temporary_file': 1,
\}
endfunction

View File

@ -30,8 +30,26 @@ endfunction
function! ale#fixers#prettier#ApplyFixForVersion(buffer, version_output) abort
let l:executable = ale#fixers#prettier#GetExecutable(a:buffer)
let l:options = ale#Var(a:buffer, 'javascript_prettier_options')
let l:version = ale#semver#GetVersion(l:executable, a:version_output)
let l:parser = ''
" Append the --parser flag depending on the current filetype (unless it's
" already set in g:javascript_prettier_options).
if empty(expand('#' . a:buffer . ':e')) && match(l:options, '--parser') == -1
let l:prettier_parsers = ['typescript', 'css', 'less', 'scss', 'json', 'json5', 'graphql', 'markdown', 'vue']
let l:parser = 'babylon'
for l:filetype in split(getbufvar(a:buffer, '&filetype'), '\.')
if index(l:prettier_parsers, l:filetype) > -1
let l:parser = l:filetype
break
endif
endfor
endif
if !empty(l:parser)
let l:options = (!empty(l:options) ? l:options . ' ' : '') . '--parser ' . l:parser
endif
" 1.4.0 is the first version with --stdin-filepath
if ale#semver#GTE(l:version, [1, 4, 0])

View File

@ -0,0 +1,26 @@
" Author: meain <abinsimon10@gmail.com>
" Description: Fixing HTML files with tidy.
call ale#Set('html_tidy_executable', 'tidy')
call ale#Set('html_tidy_use_global', get(g:, 'ale_use_global_executables', 0))
function! ale#fixers#tidy#Fix(buffer) abort
let l:executable = ale#node#FindExecutable(
\ a:buffer,
\ 'html_tidy',
\ ['tidy'],
\)
if !executable(l:executable)
return 0
endif
let l:config = ale#path#FindNearestFile(a:buffer, '.tidyrc')
let l:config_options = !empty(l:config)
\ ? ' -q --tidy-mark no --show-errors 0 --show-warnings 0 -config ' . ale#Escape(l:config)
\ : ' -q --tidy-mark no --show-errors 0 --show-warnings 0'
return {
\ 'command': ale#Escape(l:executable) . l:config_options,
\}
endfunction

View File

@ -97,14 +97,14 @@ function! s:ShowDetails(linter, buffer, line, column, opt) abort
\ ? function('ale#hover#HandleTSServerResponse')
\ : function('ale#hover#HandleLSPResponse')
let l:lsp_details = ale#linter#StartLSP(a:buffer, a:linter, l:Callback)
let l:lsp_details = ale#lsp_linter#StartLSP(a:buffer, a:linter, l:Callback)
if empty(l:lsp_details)
return 0
endif
let l:id = l:lsp_details.connection_id
let l:root = l:lsp_details.project_root
let l:language_id = l:lsp_details.language_id
if a:linter.lsp is# 'tsserver'
let l:column = a:column
@ -117,14 +117,14 @@ function! s:ShowDetails(linter, buffer, line, column, opt) abort
else
" Send a message saying the buffer has changed first, or the
" hover position probably won't make sense.
call ale#lsp#Send(l:id, ale#lsp#message#DidChange(a:buffer), l:root)
call ale#lsp#NotifyForChanges(l:lsp_details)
let l:column = min([a:column, len(getbufline(a:buffer, a:line)[0])])
let l:message = ale#lsp#message#Hover(a:buffer, a:line, l:column)
endif
let l:request_id = ale#lsp#Send(l:id, l:message, l:root)
let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root)
let s:hover_map[l:request_id] = {
\ 'buffer': a:buffer,

View File

@ -26,34 +26,11 @@ function! s:KillHandler(timer) abort
call job_stop(l:job, 'kill')
endfunction
" Note that jobs and IDs are the same thing on NeoVim.
function! ale#job#JoinNeovimOutput(job, last_line, data, mode, callback) abort
if a:mode is# 'raw'
call a:callback(a:job, join(a:data, "\n"))
return ''
endif
let l:lines = a:data[:-2]
if len(a:data) > 1
let l:lines[0] = a:last_line . l:lines[0]
let l:new_last_line = a:data[-1]
else
let l:new_last_line = a:last_line . get(a:data, 0, '')
endif
for l:line in l:lines
call a:callback(a:job, l:line)
endfor
return l:new_last_line
endfunction
function! s:NeoVimCallback(job, data, event) abort
let l:info = s:job_map[a:job]
if a:event is# 'stdout'
let l:info.out_cb_line = ale#job#JoinNeovimOutput(
let l:info.out_cb_line = ale#util#JoinNeovimOutput(
\ a:job,
\ l:info.out_cb_line,
\ a:data,
@ -61,7 +38,7 @@ function! s:NeoVimCallback(job, data, event) abort
\ ale#util#GetFunction(l:info.out_cb),
\)
elseif a:event is# 'stderr'
let l:info.err_cb_line = ale#job#JoinNeovimOutput(
let l:info.err_cb_line = ale#util#JoinNeovimOutput(
\ a:job,
\ l:info.err_cb_line,
\ a:data,

View File

@ -15,6 +15,7 @@ let s:default_ale_linter_aliases = {
\ 'csh': 'sh',
\ 'plaintex': 'tex',
\ 'systemverilog': 'verilog',
\ 'verilog_systemverilog': ['verilog_systemverilog', 'verilog'],
\ 'vimwiki': 'markdown',
\ 'zsh': 'sh',
\}
@ -451,81 +452,3 @@ function! ale#linter#GetAddress(buffer, linter) abort
\ ? ale#util#GetFunction(a:linter.address_callback)(a:buffer)
\ : a:linter.address
endfunction
" Given a buffer, an LSP linter, and a callback to register for handling
" messages, start up an LSP linter and get ready to receive errors or
" completions.
function! ale#linter#StartLSP(buffer, linter, callback) abort
let l:command = ''
let l:address = ''
let l:root = ale#util#GetFunction(a:linter.project_root_callback)(a:buffer)
if empty(l:root) && a:linter.lsp isnot# 'tsserver'
" If there's no project root, then we can't check files with LSP,
" unless we are using tsserver, which doesn't use project roots.
return {}
endif
let l:initialization_options = {}
if has_key(a:linter, 'initialization_options_callback')
let l:initialization_options = ale#util#GetFunction(a:linter.initialization_options_callback)(a:buffer)
elseif has_key(a:linter, 'initialization_options')
let l:initialization_options = a:linter.initialization_options
endif
if a:linter.lsp is# 'socket'
let l:address = ale#linter#GetAddress(a:buffer, a:linter)
let l:conn_id = ale#lsp#ConnectToAddress(
\ l:address,
\ l:root,
\ a:callback,
\ l:initialization_options,
\)
else
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
if !executable(l:executable)
return {}
endif
let l:command = ale#job#PrepareCommand(
\ a:buffer,
\ ale#linter#GetCommand(a:buffer, a:linter),
\)
let l:conn_id = ale#lsp#StartProgram(
\ l:executable,
\ l:command,
\ l:root,
\ a:callback,
\ l:initialization_options,
\)
endif
let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer)
if !l:conn_id
if g:ale_history_enabled && !empty(l:command)
call ale#history#Add(a:buffer, 'failed', l:conn_id, l:command)
endif
return {}
endif
if ale#lsp#OpenDocumentIfNeeded(l:conn_id, a:buffer, l:root, l:language_id)
if g:ale_history_enabled && !empty(l:command)
call ale#history#Add(a:buffer, 'started', l:conn_id, l:command)
endif
endif
" The change message needs to be sent for tsserver before doing anything.
if a:linter.lsp is# 'tsserver'
call ale#lsp#Send(l:conn_id, ale#lsp#tsserver_message#Change(a:buffer))
endif
return {
\ 'connection_id': l:conn_id,
\ 'command': l:command,
\ 'project_root': l:root,
\ 'language_id': l:language_id,
\}
endfunction

View File

@ -3,20 +3,23 @@
" A List of connections, used for tracking servers which have been connected
" to, and programs which are run.
let s:connections = []
let s:connections = get(s:, 'connections', [])
let g:ale_lsp_next_message_id = 1
function! s:NewConnection(initialization_options) abort
" Exposed only so tests can get at it.
" Do not call this function basically anywhere.
function! ale#lsp#NewConnection(initialization_options) abort
" id: The job ID as a Number, or the server address as a string.
" data: The message data received so far.
" executable: An executable only set for program connections.
" open_documents: A list of buffers we told the server we opened.
" open_documents: A Dictionary mapping buffers to b:changedtick, keeping
" track of when documents were opened, and when we last changed them.
" callback_list: A list of callbacks for handling LSP responses.
let l:conn = {
\ 'id': '',
\ 'data': '',
\ 'projects': {},
\ 'open_documents': [],
\ 'open_documents': {},
\ 'callback_list': [],
\ 'initialization_options': a:initialization_options,
\}
@ -26,9 +29,14 @@ function! s:NewConnection(initialization_options) abort
return l:conn
endfunction
" Remove an LSP connection with a given ID. This is only for tests.
function! ale#lsp#RemoveConnectionWithID(id) abort
call filter(s:connections, 'v:val.id isnot a:id')
endfunction
function! s:FindConnection(key, value) abort
for l:conn in s:connections
if has_key(l:conn, a:key) && get(l:conn, a:key) == a:value
if has_key(l:conn, a:key) && get(l:conn, a:key) is# a:value
return l:conn
endif
endfor
@ -233,9 +241,8 @@ function! ale#lsp#HandleMessage(conn, message) abort
endfor
endfunction
function! s:HandleChannelMessage(channel, message) abort
let l:info = ch_info(a:channel)
let l:address = l:info.hostname . l:info.address
function! s:HandleChannelMessage(channel_id, message) abort
let l:address = ale#socket#GetAddress(a:channel_id)
let l:conn = s:FindConnection('id', l:address)
call ale#lsp#HandleMessage(l:conn, a:message)
@ -280,7 +287,7 @@ function! ale#lsp#StartProgram(executable, command, project_root, callback, init
let l:conn = s:FindConnection('executable', a:executable)
" Get the current connection or a new one.
let l:conn = !empty(l:conn) ? l:conn : s:NewConnection(a:initialization_options)
let l:conn = !empty(l:conn) ? l:conn : ale#lsp#NewConnection(a:initialization_options)
let l:conn.executable = a:executable
if !has_key(l:conn, 'id') || !ale#job#IsRunning(l:conn.id)
@ -309,18 +316,16 @@ endfunction
function! ale#lsp#ConnectToAddress(address, project_root, callback, initialization_options) abort
let l:conn = s:FindConnection('id', a:address)
" Get the current connection or a new one.
let l:conn = !empty(l:conn) ? l:conn : s:NewConnection(a:initialization_options)
let l:conn = !empty(l:conn) ? l:conn : ale#lsp#NewConnection(a:initialization_options)
if !has_key(l:conn, 'channel') || ch_status(l:conn.channel) isnot# 'open'
let l:conn.channnel = ch_open(a:address, {
\ 'mode': 'raw',
\ 'waittime': 0,
if !has_key(l:conn, 'channel_id') || !ale#socket#IsOpen(l:conn.channel_id)
let l:conn.channel_id = ale#socket#Open(a:address, {
\ 'callback': function('s:HandleChannelMessage'),
\})
endif
if ch_status(l:conn.channnel) is# 'fail'
return 0
if l:conn.channel_id < 0
return ''
endif
let l:conn.id = a:address
@ -328,15 +333,15 @@ function! ale#lsp#ConnectToAddress(address, project_root, callback, initializati
call uniq(sort(add(l:conn.callback_list, a:callback)))
call ale#lsp#RegisterProject(l:conn, a:project_root)
return 1
return a:address
endfunction
" Stop all LSP connections, closing all jobs and channels, and removing any
" queued messages.
function! ale#lsp#StopAll() abort
for l:conn in s:connections
if has_key(l:conn, 'channel')
call ch_close(l:conn.channel)
if has_key(l:conn, 'channel_id')
call ale#socket#Close(l:conn.channel_id)
else
call ale#job#Stop(l:conn.id)
endif
@ -348,9 +353,9 @@ endfunction
function! s:SendMessageData(conn, data) abort
if has_key(a:conn, 'executable')
call ale#job#SendRaw(a:conn.id, a:data)
elseif has_key(a:conn, 'channel') && ch_status(a:conn.channnel) is# 'open'
elseif has_key(a:conn, 'channel_id') && ale#socket#IsOpen(a:conn.channel_id)
" Send the message to the server
call ch_sendraw(a:conn.channel, a:data)
call ale#socket#Send(a:conn.channel_id, a:data)
else
return 0
endif
@ -406,21 +411,72 @@ function! ale#lsp#Send(conn_id, message, ...) abort
return l:id == 0 ? -1 : l:id
endfunction
function! ale#lsp#OpenDocumentIfNeeded(conn_id, buffer, project_root, language_id) abort
let l:conn = s:FindConnection('id', a:conn_id)
" The Document details Dictionary should contain the following keys.
"
" buffer - The buffer number for the document.
" connection_id - The connection ID for the LSP server.
" command - The command to run to start the LSP connection.
" project_root - The project root for the LSP project.
" language_id - The language ID for the project, like 'python', 'rust', etc.
" Create a new Dictionary containing more connection details, with the
" following information added:
"
" conn - An existing LSP connection for the document.
" document_open - 1 if the document is currently open, 0 otherwise.
function! s:ExtendDocumentDetails(details) abort
let l:extended = copy(a:details)
let l:conn = s:FindConnection('id', a:details.connection_id)
let l:extended.conn = l:conn
let l:extended.document_open = !empty(l:conn)
\ && has_key(l:conn.open_documents, a:details.buffer)
return l:extended
endfunction
" Notify LSP servers or tsserver if a document is opened, if needed.
" If a document is opened, 1 will be returned, otherwise 0 will be returned.
function! ale#lsp#OpenDocument(basic_details) abort
let l:d = s:ExtendDocumentDetails(a:basic_details)
let l:opened = 0
if !empty(l:conn) && index(l:conn.open_documents, a:buffer) < 0
if empty(a:language_id)
let l:message = ale#lsp#tsserver_message#Open(a:buffer)
if !empty(l:d.conn) && !l:d.document_open
if empty(l:d.language_id)
let l:message = ale#lsp#tsserver_message#Open(l:d.buffer)
else
let l:message = ale#lsp#message#DidOpen(a:buffer, a:language_id)
let l:message = ale#lsp#message#DidOpen(l:d.buffer, l:d.language_id)
endif
call ale#lsp#Send(a:conn_id, l:message, a:project_root)
call add(l:conn.open_documents, a:buffer)
call ale#lsp#Send(l:d.connection_id, l:message, l:d.project_root)
let l:d.conn.open_documents[l:d.buffer] = getbufvar(l:d.buffer, 'changedtick')
let l:opened = 1
endif
return l:opened
endfunction
" Notify LSP servers or tsserver that a document has changed, if needed.
" If a notification is sent, 1 will be returned, otherwise 0 will be returned.
function! ale#lsp#NotifyForChanges(basic_details) abort
let l:d = s:ExtendDocumentDetails(a:basic_details)
let l:notified = 0
if l:d.document_open
let l:new_tick = getbufvar(l:d.buffer, 'changedtick')
if l:d.conn.open_documents[l:d.buffer] < l:new_tick
if empty(l:d.language_id)
let l:message = ale#lsp#tsserver_message#Change(l:d.buffer)
else
let l:message = ale#lsp#message#DidChange(l:d.buffer)
endif
call ale#lsp#Send(l:d.connection_id, l:message, l:d.project_root)
let l:d.conn.open_documents[l:d.buffer] = l:new_tick
let l:notified = 1
endif
endif
return l:notified
endfunction

View File

@ -7,9 +7,9 @@ function! ale#lsp#reset#StopAllLSPs() abort
call ale#definition#ClearLSPData()
endif
if exists('*ale#engine#ClearLSPData')
if exists('*ale#lsp_linter#ClearLSPData')
" Clear the mapping for connections, etc.
call ale#engine#ClearLSPData()
call ale#lsp_linter#ClearLSPData()
" Remove the problems for all of the LSP linters in every buffer.
for l:buffer_string in keys(g:ale_buffer_info)

View File

@ -0,0 +1,252 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Integration between linters and LSP/tsserver.
" This code isn't loaded if a user never users LSP features or linters.
" Associates LSP connection IDs with linter names.
if !has_key(s:, 'lsp_linter_map')
let s:lsp_linter_map = {}
endif
" Check if diagnostics for a particular linter should be ignored.
function! s:ShouldIgnore(buffer, linter_name) abort
let l:config = ale#Var(a:buffer, 'linters_ignore')
" Don't load code for ignoring diagnostics if there's nothing to ignore.
if empty(l:config)
return 0
endif
let l:filetype = getbufvar(a:buffer, '&filetype')
let l:ignore_list = ale#engine#ignore#GetList(l:filetype, l:config)
return index(l:ignore_list, a:linter_name) >= 0
endfunction
function! s:HandleLSPDiagnostics(conn_id, response) abort
let l:linter_name = s:lsp_linter_map[a:conn_id]
let l:filename = ale#path#FromURI(a:response.params.uri)
let l:buffer = bufnr(l:filename)
if s:ShouldIgnore(l:buffer, l:linter_name)
return
endif
if l:buffer <= 0
return
endif
let l:loclist = ale#lsp#response#ReadDiagnostics(a:response)
call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist)
endfunction
function! s:HandleTSServerDiagnostics(response, error_type) abort
let l:linter_name = 'tsserver'
let l:buffer = bufnr(a:response.body.file)
let l:info = get(g:ale_buffer_info, l:buffer, {})
if empty(l:info)
return
endif
if s:ShouldIgnore(l:buffer, l:linter_name)
return
endif
let l:thislist = ale#lsp#response#ReadTSServerDiagnostics(a:response)
" tsserver sends syntax and semantic errors in separate messages, so we
" have to collect the messages separately for each buffer and join them
" back together again.
if a:error_type is# 'syntax'
let l:info.syntax_loclist = l:thislist
else
let l:info.semantic_loclist = l:thislist
endif
let l:loclist = get(l:info, 'semantic_loclist', [])
\ + get(l:info, 'syntax_loclist', [])
call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist)
endfunction
function! s:HandleLSPErrorMessage(linter_name, response) abort
if !g:ale_history_enabled || !g:ale_history_log_output
return
endif
if empty(a:linter_name)
return
endif
let l:message = ale#lsp#response#GetErrorMessage(a:response)
if empty(l:message)
return
endif
" This global variable is set here so we don't load the debugging.vim file
" until someone uses :ALEInfo.
let g:ale_lsp_error_messages = get(g:, 'ale_lsp_error_messages', {})
if !has_key(g:ale_lsp_error_messages, a:linter_name)
let g:ale_lsp_error_messages[a:linter_name] = []
endif
call add(g:ale_lsp_error_messages[a:linter_name], l:message)
endfunction
function! ale#lsp_linter#HandleLSPResponse(conn_id, response) abort
let l:method = get(a:response, 'method', '')
let l:linter_name = get(s:lsp_linter_map, a:conn_id, '')
if get(a:response, 'jsonrpc', '') is# '2.0' && has_key(a:response, 'error')
call s:HandleLSPErrorMessage(l:linter_name, a:response)
elseif l:method is# 'textDocument/publishDiagnostics'
call s:HandleLSPDiagnostics(a:conn_id, a:response)
elseif get(a:response, 'type', '') is# 'event'
\&& get(a:response, 'event', '') is# 'semanticDiag'
call s:HandleTSServerDiagnostics(a:response, 'semantic')
elseif get(a:response, 'type', '') is# 'event'
\&& get(a:response, 'event', '') is# 'syntaxDiag'
call s:HandleTSServerDiagnostics(a:response, 'syntax')
endif
endfunction
" Given a buffer, an LSP linter, and a callback to register for handling
" messages, start up an LSP linter and get ready to receive errors or
" completions.
function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort
let l:command = ''
let l:address = ''
let l:root = ale#util#GetFunction(a:linter.project_root_callback)(a:buffer)
if empty(l:root) && a:linter.lsp isnot# 'tsserver'
" If there's no project root, then we can't check files with LSP,
" unless we are using tsserver, which doesn't use project roots.
return {}
endif
let l:initialization_options = {}
if has_key(a:linter, 'initialization_options_callback')
let l:initialization_options = ale#util#GetFunction(a:linter.initialization_options_callback)(a:buffer)
elseif has_key(a:linter, 'initialization_options')
let l:initialization_options = a:linter.initialization_options
endif
if a:linter.lsp is# 'socket'
let l:address = ale#linter#GetAddress(a:buffer, a:linter)
let l:conn_id = ale#lsp#ConnectToAddress(
\ l:address,
\ l:root,
\ a:callback,
\ l:initialization_options,
\)
else
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
if !executable(l:executable)
return {}
endif
let l:command = ale#job#PrepareCommand(
\ a:buffer,
\ ale#linter#GetCommand(a:buffer, a:linter),
\)
let l:conn_id = ale#lsp#StartProgram(
\ l:executable,
\ l:command,
\ l:root,
\ a:callback,
\ l:initialization_options,
\)
endif
let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer)
if empty(l:conn_id)
if g:ale_history_enabled && !empty(l:command)
call ale#history#Add(a:buffer, 'failed', l:conn_id, l:command)
endif
return {}
endif
let l:details = {
\ 'buffer': a:buffer,
\ 'connection_id': l:conn_id,
\ 'command': l:command,
\ 'project_root': l:root,
\ 'language_id': l:language_id,
\}
if ale#lsp#OpenDocument(l:details)
if g:ale_history_enabled && !empty(l:command)
call ale#history#Add(a:buffer, 'started', l:conn_id, l:command)
endif
endif
" The change message needs to be sent for tsserver before doing anything.
if a:linter.lsp is# 'tsserver'
call ale#lsp#NotifyForChanges(l:details)
endif
return l:details
endfunction
function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort
let l:info = g:ale_buffer_info[a:buffer]
let l:lsp_details = ale#lsp_linter#StartLSP(
\ a:buffer,
\ a:linter,
\ function('ale#lsp_linter#HandleLSPResponse'),
\)
if empty(l:lsp_details)
return 0
endif
let l:id = l:lsp_details.connection_id
let l:root = l:lsp_details.project_root
" Remember the linter this connection is for.
let s:lsp_linter_map[l:id] = a:linter.name
if a:linter.lsp is# 'tsserver'
let l:message = ale#lsp#tsserver_message#Geterr(a:buffer)
let l:request_id = ale#lsp#Send(l:id, l:message, l:root)
let l:notified = l:request_id != 0
else
let l:notified = ale#lsp#NotifyForChanges(l:lsp_details)
endif
" If this was a file save event, also notify the server of that.
if a:linter.lsp isnot# 'tsserver'
\&& getbufvar(a:buffer, 'ale_save_event_fired', 0)
let l:save_message = ale#lsp#message#DidSave(a:buffer)
let l:request_id = ale#lsp#Send(l:id, l:save_message, l:root)
let l:notified = l:request_id != 0
endif
if l:notified
if index(l:info.active_linter_list, a:linter.name) < 0
call add(l:info.active_linter_list, a:linter.name)
endif
endif
return l:notified
endfunction
" Clear LSP linter data for the linting engine.
function! ale#lsp_linter#ClearLSPData() abort
let s:lsp_linter_map = {}
endfunction
" Just for tests.
function! ale#lsp_linter#SetLSPLinterMap(replacement_map) abort
let s:lsp_linter_map = a:replacement_map
endfunction

View File

@ -2,22 +2,41 @@
" Description: Preview windows for showing whatever information in.
" Open a preview window and show some lines in it.
" An optional second argument can set an alternative filetype for the window.
" A second argument can be passed as a Dictionary with options. They are...
"
" filetype - The filetype to use, defaulting to 'ale-preview'
" stay_here - If 1, stay in the window you came from.
function! ale#preview#Show(lines, ...) abort
let l:filetype = get(a:000, 0, 'ale-preview')
let l:options = get(a:000, 0, {})
silent pedit ALEPreviewWindow
wincmd P
setlocal modifiable
setlocal noreadonly
setlocal nobuflisted
let &l:filetype = l:filetype
let &l:filetype = get(l:options, 'filetype', 'ale-preview')
setlocal buftype=nofile
setlocal bufhidden=wipe
:%d
call setline(1, a:lines)
setlocal nomodifiable
setlocal readonly
if get(l:options, 'stay_here')
wincmd p
endif
endfunction
" Close the preview window if the filetype matches the given one.
function! ale#preview#CloseIfTypeMatches(filetype) abort
for l:win in getwininfo()
let l:wintype = gettabwinvar(l:win.tabnr, l:win.winnr, '&filetype')
if l:wintype is# a:filetype
silent! pclose!
endif
endfor
endfunction
" Show a location selection preview window, given some items.
@ -35,7 +54,7 @@ function! ale#preview#ShowSelection(item_list) abort
\)
endfor
call ale#preview#Show(l:lines, 'ale-preview-selection')
call ale#preview#Show(l:lines, {'filetype': 'ale-preview-selection'})
let b:ale_preview_item_list = a:item_list
endfunction

View File

@ -72,14 +72,13 @@ function! s:FindReferences(linter) abort
\ ? function('ale#references#HandleTSServerResponse')
\ : function('ale#references#HandleLSPResponse')
let l:lsp_details = ale#linter#StartLSP(l:buffer, a:linter, l:Callback)
let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback)
if empty(l:lsp_details)
return 0
endif
let l:id = l:lsp_details.connection_id
let l:root = l:lsp_details.project_root
if a:linter.lsp is# 'tsserver'
let l:message = ale#lsp#tsserver_message#References(
@ -90,14 +89,14 @@ function! s:FindReferences(linter) abort
else
" Send a message saying the buffer has changed first, or the
" references position probably won't make sense.
call ale#lsp#Send(l:id, ale#lsp#message#DidChange(l:buffer), l:root)
call ale#lsp#NotifyForChanges(l:lsp_details)
let l:column = min([l:column, len(getline(l:line))])
let l:message = ale#lsp#message#References(l:buffer, l:line, l:column)
endif
let l:request_id = ale#lsp#Send(l:id, l:message, l:root)
let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root)
let s:references_map[l:request_id] = {}
endfunction

View File

@ -0,0 +1,144 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: APIs for working with asynchronous sockets, with an API
" normalised between Vim 8 and NeoVim. Socket connections only work in NeoVim
" 0.3+, and silently do nothing in earlier NeoVim versions.
"
" Important functions are described below. They are:
"
" ale#socket#Open(address, options) -> channel_id (>= 0 if successful)
" ale#socket#IsOpen(channel_id) -> 1 if open, 0 otherwise
" ale#socket#Close(channel_id)
" ale#socket#Send(channel_id, data)
" ale#socket#GetAddress(channel_id) -> Return the address for a job
let s:channel_map = get(s:, 'channel_map', {})
function! s:VimOutputCallback(channel, data) abort
let l:channel_id = ch_info(a:channel).id
" Only call the callbacks for jobs which are valid.
if l:channel_id >= 0 && has_key(s:channel_map, l:channel_id)
call ale#util#GetFunction(s:channel_map[l:channel_id].callback)(l:channel_id, a:data)
endif
endfunction
function! s:NeoVimOutputCallback(channel_id, data, event) abort
let l:info = s:channel_map[a:channel_id]
if a:event is# 'data'
let l:info.last_line = ale#util#JoinNeovimOutput(
\ a:channel_id,
\ l:info.last_line,
\ a:data,
\ l:info.mode,
\ ale#util#GetFunction(l:info.callback),
\)
endif
endfunction
" Open a socket for a given address. The following options are accepted:
"
" callback - A callback for receiving input. (required)
"
" A non-negative number representing a channel ID will be returned is the
" connection was successful. 0 is a valid channel ID in Vim, so test if the
" connection ID is >= 0.
function! ale#socket#Open(address, options) abort
let l:mode = get(a:options, 'mode', 'raw')
let l:Callback = a:options.callback
let l:channel_info = {
\ 'address': a:address,
\ 'mode': l:mode,
\ 'callback': a:options.callback,
\}
if !has('nvim')
" Vim
let l:channel_info.channel = ch_open(a:address, {
\ 'mode': l:mode,
\ 'waittime': 0,
\ 'callback': function('s:VimOutputCallback'),
\})
let l:vim_info = ch_info(l:channel_info.channel)
let l:channel_id = !empty(l:vim_info) ? l:vim_info.id : -1
elseif exists('*chansend') && exists('*sockconnect')
" NeoVim 0.3+
try
let l:channel_id = sockconnect('tcp', a:address, {
\ 'on_data': function('s:NeoVimOutputCallback'),
\})
let l:channel_info.last_line = ''
catch /connection failed/
let l:channel_id = -1
endtry
" 0 means the connection failed some times in NeoVim, so make the ID
" invalid to match Vim.
if l:channel_id is 0
let l:channel_id = -1
endif
let l:channel_info.channel = l:channel_id
else
" Other Vim versions.
let l:channel_id = -1
endif
if l:channel_id >= 0
let s:channel_map[l:channel_id] = l:channel_info
endif
return l:channel_id
endfunction
" Return 1 is a channel is open, 0 otherwise.
function! ale#socket#IsOpen(channel_id) abort
if !has_key(s:channel_map, a:channel_id)
return 0
endif
if has('nvim')
" In NeoVim, we have to check if this channel is in the global list.
return index(map(nvim_list_chans(), 'v:val.id'), a:channel_id) >= 0
endif
let l:channel = s:channel_map[a:channel_id].channel
return ch_status(l:channel) is# 'open'
endfunction
" Close a socket, if it's still open.
function! ale#socket#Close(channel_id) abort
" IsRunning isn't called here, so we don't check nvim_list_chans()
if !has_key(s:channel_map, a:channel_id)
return 0
endif
let l:channel = remove(s:channel_map, a:channel_id).channel
if has('nvim')
silent! call chanclose(l:channel)
elseif ch_status(l:channel) is# 'open'
call ch_close(l:channel)
endif
endfunction
" Send some data to a socket.
function! ale#socket#Send(channel_id, data) abort
if !has_key(s:channel_map, a:channel_id)
return
endif
let l:channel = s:channel_map[a:channel_id].channel
if has('nvim')
call chansend(l:channel, a:data)
else
call ch_sendraw(l:channel, a:data)
endif
endfunction
" Get an address for a channel, or an empty string.
function! ale#socket#GetAddress(channel_id) abort
return get(get(s:channel_map, a:channel_id, {}), 'address', '')
endfunction

View File

@ -1,14 +1,6 @@
" Author: KabbAmine <amine.kabb@gmail.com>
" Description: Statusline related function(s)
" remove in 2.0
"
" A deprecated setting for ale#statusline#Status()
" See :help ale#statusline#Count() for getting status reports.
let g:ale_statusline_format = get(g:, 'ale_statusline_format',
\ ['%d error(s)', '%d warning(s)', 'OK']
\)
function! s:CreateCountDict() abort
" Keys 0 and 1 are for backwards compatibility.
" The count object used to be a List of [error_count, warning_count].
@ -76,47 +68,3 @@ function! ale#statusline#Count(buffer) abort
" The Dictionary is copied here before exposing it to other plugins.
return copy(s:GetCounts(a:buffer))
endfunction
" This is the historical format setting which could be configured before.
function! s:StatusForListFormat() abort
let [l:error_format, l:warning_format, l:no_errors] = g:ale_statusline_format
let l:counts = s:GetCounts(bufnr(''))
" Build strings based on user formatting preferences.
let l:errors = l:counts[0] ? printf(l:error_format, l:counts[0]) : ''
let l:warnings = l:counts[1] ? printf(l:warning_format, l:counts[1]) : ''
" Different formats based on the combination of errors and warnings.
if empty(l:errors) && empty(l:warnings)
let l:res = l:no_errors
elseif !empty(l:errors) && !empty(l:warnings)
let l:res = printf('%s %s', l:errors, l:warnings)
else
let l:res = empty(l:errors) ? l:warnings : l:errors
endif
return l:res
endfunction
" remove in 2.0
"
" Returns a formatted string that can be integrated in the statusline.
"
" This function is deprecated, and should not be used. Use the airline plugin
" instead, or write your own status function with ale#statusline#Count()
function! ale#statusline#Status() abort
if !get(g:, 'ale_deprecation_ale_statusline_status', 0)
execute 'echom ''ale#statusline#Status() is deprecated, use ale#statusline#Count() to write your own function.'''
let g:ale_deprecation_ale_statusline_status = 1
endif
if !exists('g:ale_statusline_format')
return 'OK'
endif
if type(g:ale_statusline_format) == type([])
return s:StatusForListFormat()
endif
return ''
endfunction

View File

@ -48,7 +48,7 @@ function! ale#toggle#Toggle() abort
endif
endif
call ale#autocmd#InitAuGroups()
call ale#events#Init()
endfunction
function! ale#toggle#Enable() abort

View File

@ -17,11 +17,18 @@ endfunction
" but NeoVim does. Small messages can be echoed in Vim 8, and larger messages
" have to be shown in preview windows.
function! ale#util#ShowMessage(string) abort
if !has('nvim')
call ale#preview#CloseIfTypeMatches('ale-preview.message')
endif
" We have to assume the user is using a monospace font.
if has('nvim') || (a:string !~? "\n" && len(a:string) < &columns)
execute 'echo a:string'
else
call ale#preview#Show(split(a:string, "\n"))
call ale#preview#Show(split(a:string, "\n"), {
\ 'filetype': 'ale-preview.message',
\ 'stay_here': 1,
\})
endif
endfunction
@ -39,6 +46,33 @@ if !exists('g:ale#util#nul_file')
endif
endif
" Given a job, a buffered line of data, a list of parts of lines, a mode data
" is being read in, and a callback, join the lines of output for a NeoVim job
" or socket together, and call the callback with the joined output.
"
" Note that jobs and IDs are the same thing on NeoVim.
function! ale#util#JoinNeovimOutput(job, last_line, data, mode, callback) abort
if a:mode is# 'raw'
call a:callback(a:job, join(a:data, "\n"))
return ''
endif
let l:lines = a:data[:-2]
if len(a:data) > 1
let l:lines[0] = a:last_line . l:lines[0]
let l:new_last_line = a:data[-1]
else
let l:new_last_line = a:last_line . get(a:data, 0, '')
endif
for l:line in l:lines
call a:callback(a:job, l:line)
endfor
return l:new_last_line
endfunction
" Return the number of lines for a given buffer.
function! ale#util#GetLineCount(buffer) abort
return len(getbufline(a:buffer, 1, '$'))
@ -56,7 +90,10 @@ function! ale#util#Open(filename, line, column, options) abort
if get(a:options, 'open_in_tab', 0)
call ale#util#Execute('tabedit ' . fnameescape(a:filename))
else
call ale#util#Execute('edit ' . fnameescape(a:filename))
" Open another file only if we need to.
if bufnr(a:filename) isnot bufnr('')
call ale#util#Execute('edit ' . fnameescape(a:filename))
endif
endif
call cursor(a:line, a:column)
@ -241,16 +278,6 @@ function! ale#util#InSandbox() abort
return 0
endfunction
" Get the number of milliseconds since some vague, but consistent, point in
" the past.
"
" This function can be used for timing execution, etc.
"
" The time will be returned as a Number.
function! ale#util#ClockMilliseconds() abort
return float2nr(reltimefloat(reltime()) * 1000)
endfunction
" Given a single line, or a List of lines, and a single pattern, or a List
" of patterns, return all of the matches for the lines(s) from the given
" patterns, using matchlist().