mirror of
https://github.com/dense-analysis/ale.git
synced 2025-12-06 12:44:23 +08:00
Implement pull diagnostics in the VimL implementation so ALE is able to track when servers are busy checking files. Only servers that support this feature will return diagnostics these ways.
497 lines
14 KiB
Plaintext
497 lines
14 KiB
Plaintext
Before:
|
|
Save g:ale_run_synchronously
|
|
|
|
let g:ale_run_synchronously = 1
|
|
unlet! g:ale_run_synchronously_callbacks
|
|
unlet! g:ale_run_synchronously_emulate_commands
|
|
|
|
runtime autoload/ale/lsp.vim
|
|
runtime autoload/ale/lsp_linter.vim
|
|
runtime autoload/ale/engine.vim
|
|
runtime autoload/ale/job.vim
|
|
runtime autoload/ale/socket.vim
|
|
|
|
let g:job_map = {}
|
|
let g:emulate_job_failure = 0
|
|
let g:next_job_id = 1
|
|
let g:lsp_started = 0
|
|
|
|
let g:socket_map = {}
|
|
let g:emulate_socket_failure = 0
|
|
let g:next_channel_id = 0
|
|
|
|
let g:message_buffer = ''
|
|
let g:calls = []
|
|
|
|
function! ale#engine#IsExecutable(buffer, executable) abort
|
|
return !empty(a:executable)
|
|
endfunction
|
|
|
|
function! ale#job#HasOpenChannel(job_id) abort
|
|
return has_key(g:job_map, a:job_id)
|
|
endfunction
|
|
|
|
function! ale#job#Stop(job_id) abort
|
|
if has_key(g:job_map, a:job_id)
|
|
call remove(g:job_map, a:job_id)
|
|
endif
|
|
endfunction
|
|
|
|
function! ale#job#Start(command, options) abort
|
|
if g:emulate_job_failure
|
|
return 0
|
|
endif
|
|
|
|
let l:job_id = g:next_job_id
|
|
let g:next_job_id += 1
|
|
let g:job_map[l:job_id] = [a:command, a:options]
|
|
|
|
return l:job_id
|
|
endfunction
|
|
|
|
function! ale#job#SendRaw(job_id, data) abort
|
|
let g:message_buffer .= a:data
|
|
endfunction
|
|
|
|
function! ale#socket#IsOpen(channel_id) abort
|
|
return has_key(g:socket_map, a:channel_id)
|
|
endfunction
|
|
|
|
function! ale#socket#Close(channel_id) abort
|
|
if has_key(g:socket_map, a:channel_id)
|
|
call remove(g:socket_map, a:channel_id)
|
|
endif
|
|
endfunction
|
|
|
|
function! ale#socket#Open(address, options) abort
|
|
if g:emulate_socket_failure
|
|
return -1
|
|
endif
|
|
|
|
let l:channel_id = g:next_channel_id
|
|
let g:next_channel_id += 1
|
|
let g:socket_map[l:channel_id] = [a:address, a:options]
|
|
|
|
return l:channel_id
|
|
endfunction
|
|
|
|
function! ale#socket#Send(channel_id, data) abort
|
|
let g:message_buffer .= a:data
|
|
endfunction
|
|
|
|
function! PopMessages() abort
|
|
let l:message_list = []
|
|
|
|
for l:line in split(g:message_buffer, '\(\r\|\n\|Content-Length\)\+')
|
|
if l:line[:0] is '{'
|
|
let l:data = json_decode(l:line)
|
|
|
|
call add(l:message_list, l:data)
|
|
endif
|
|
endfor
|
|
|
|
let g:message_buffer = ''
|
|
|
|
return l:message_list
|
|
endfunction
|
|
|
|
function! SendMessage(message) abort
|
|
let l:conn_id = keys(ale#lsp#GetConnections())[0]
|
|
let l:body = json_encode(a:message)
|
|
let l:data = 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body
|
|
|
|
call ale#lsp#HandleMessage(l:conn_id, l:data)
|
|
endfunction
|
|
|
|
function! Start(buffer) abort
|
|
let l:linter = values(ale#linter#GetLintersLoaded())[0][0]
|
|
|
|
return ale#lsp_linter#StartLSP(
|
|
\ a:buffer,
|
|
\ l:linter,
|
|
\ {linter, details -> add(g:calls, [linter.name, details])},
|
|
\)
|
|
endfunction
|
|
|
|
function! AssertInitSuccess(linter_name, conn_prefix, language, root, command, buffer) abort
|
|
let l:messages = PopMessages()
|
|
|
|
if a:linter_name is# 'tsserver'
|
|
AssertEqual
|
|
\ [
|
|
\ {
|
|
\ 'seq': v:null,
|
|
\ 'arguments': {
|
|
\ 'file': expand('#' . a:buffer . ':p'),
|
|
\ },
|
|
\ 'type': 'request',
|
|
\ 'command': 'open',
|
|
\ },
|
|
\ ],
|
|
\ l:messages
|
|
else
|
|
AssertEqual
|
|
\ [
|
|
\ {
|
|
\ 'method': 'initialize',
|
|
\ 'jsonrpc': '2.0',
|
|
\ 'id': 1,
|
|
\ 'params': {
|
|
\ 'initializationOptions': {},
|
|
\ 'rootUri': ale#path#ToFileURI(a:root),
|
|
\ 'rootPath': a:root,
|
|
\ 'processId': getpid(),
|
|
\ 'capabilities': {
|
|
\ 'workspace': {
|
|
\ 'applyEdit': v:false,
|
|
\ 'didChangeConfiguration': {
|
|
\ 'dynamicRegistration': v:false,
|
|
\ },
|
|
\ 'symbol': {
|
|
\ 'dynamicRegistration': v:false,
|
|
\ },
|
|
\ 'workspaceFolders': v:false,
|
|
\ 'configuration': v:false,
|
|
\ },
|
|
\ 'textDocument': {
|
|
\ 'synchronization': {
|
|
\ 'dynamicRegistration': v:false,
|
|
\ 'willSave': v:false,
|
|
\ 'willSaveWaitUntil': v:false,
|
|
\ 'didSave': v:true,
|
|
\ },
|
|
\ 'completion': {
|
|
\ 'dynamicRegistration': v:false,
|
|
\ 'completionItem': {
|
|
\ 'snippetSupport': v:false,
|
|
\ 'commitCharactersSupport': v:false,
|
|
\ 'documentationFormat': ['plaintext', 'markdown'],
|
|
\ 'deprecatedSupport': v:false,
|
|
\ 'preselectSupport': v:false,
|
|
\ },
|
|
\ 'contextSupport': v:false,
|
|
\ },
|
|
\ 'hover': {
|
|
\ 'dynamicRegistration': v:false,
|
|
\ 'contentFormat': ['plaintext', 'markdown'],
|
|
\ },
|
|
\ 'references': {
|
|
\ 'dynamicRegistration': v:false,
|
|
\ },
|
|
\ 'documentSymbol': {
|
|
\ 'dynamicRegistration': v:false,
|
|
\ 'hierarchicalDocumentSymbolSupport': v:false,
|
|
\ },
|
|
\ 'definition': {
|
|
\ 'dynamicRegistration': v:false,
|
|
\ 'linkSupport': v:false,
|
|
\ },
|
|
\ 'typeDefinition': {
|
|
\ 'dynamicRegistration': v:false,
|
|
\ },
|
|
\ 'implementation': {
|
|
\ 'dynamicRegistration': v:false,
|
|
\ 'linkSupport': v:false,
|
|
\ },
|
|
\ 'diagnostic': {
|
|
\ 'dynamicRegistration': v:true,
|
|
\ 'relatedDocumentSupport': v:true,
|
|
\ },
|
|
\ 'publishDiagnostics': {
|
|
\ 'relatedInformation': v:true,
|
|
\ },
|
|
\ 'codeAction': {
|
|
\ 'dynamicRegistration': v:false,
|
|
\ 'codeActionLiteralSupport': {
|
|
\ 'codeActionKind': {
|
|
\ 'valueSet': []
|
|
\ }
|
|
\ }
|
|
\ },
|
|
\ 'rename': {
|
|
\ 'dynamicRegistration': v:false,
|
|
\ },
|
|
\ },
|
|
\ },
|
|
\ },
|
|
\ },
|
|
\ ],
|
|
\ l:messages
|
|
|
|
call SendMessage({
|
|
\ 'jsonrpc': '2.0',
|
|
\ 'id': 1,
|
|
\ 'result': {
|
|
\ 'capabilities': {
|
|
\ 'renameProvider': v:true,
|
|
\ 'executeCommandProvider': {
|
|
\ 'commands': [],
|
|
\ },
|
|
\ 'hoverProvider': v:true,
|
|
\ 'documentSymbolProvider': v:true,
|
|
\ 'documentRangeFormattingProvider': v:true,
|
|
\ 'codeLensProvider': {
|
|
\ 'resolveProvider': v:false
|
|
\ },
|
|
\ 'referencesProvider': v:true,
|
|
\ 'textDocumentSync': 2,
|
|
\ 'documentFormattingProvider': v:true,
|
|
\ 'codeActionProvider': v:true,
|
|
\ 'signatureHelpProvider': {
|
|
\ 'triggerCharacters': ['(', ','],
|
|
\ },
|
|
\ 'completionProvider': {
|
|
\ 'triggerCharacters': ['.'],
|
|
\ 'resolveProvider': v:false
|
|
\ },
|
|
\ 'definitionProvider': v:true,
|
|
\ 'experimental': {},
|
|
\ 'documentHighlightProvider': v:true,
|
|
\ 'workspaceSymbolProvider': v:true,
|
|
\ },
|
|
\ },
|
|
\})
|
|
|
|
let l:messages = PopMessages()
|
|
|
|
AssertEqual
|
|
\ [
|
|
\ {
|
|
\ 'method': 'initialized',
|
|
\ 'jsonrpc': '2.0',
|
|
\ 'params': {},
|
|
\ },
|
|
\ {
|
|
\ 'method': 'textDocument/didOpen',
|
|
\ 'jsonrpc': '2.0',
|
|
\ 'params': {
|
|
\ 'textDocument': {
|
|
\ 'uri': ale#path#ToFileURI(expand('#' . a:buffer . ':p')),
|
|
\ 'version': ale#lsp#message#GetNextVersionID() - 1,
|
|
\ 'languageId': a:language,
|
|
\ 'text': "\n",
|
|
\ },
|
|
\ },
|
|
\ },
|
|
\ ],
|
|
\ l:messages
|
|
endif
|
|
|
|
AssertEqual
|
|
\ [
|
|
\ [
|
|
\ a:linter_name,
|
|
\ {
|
|
\ 'connection_id': a:conn_prefix . ':' . a:root,
|
|
\ 'project_root': a:root,
|
|
\ 'buffer': a:buffer,
|
|
\ 'command': !empty(a:command) ? ale#job#PrepareCommand(a:buffer, a:command) : '',
|
|
\ },
|
|
\ ],
|
|
\ ],
|
|
\ g:calls
|
|
endfunction
|
|
|
|
function! AssertInitFailure() abort
|
|
let l:messages = PopMessages()
|
|
|
|
AssertEqual [], l:messages
|
|
AssertEqual [], g:calls
|
|
endfunction
|
|
|
|
call ale#linter#Reset()
|
|
|
|
After:
|
|
Restore
|
|
|
|
call ale#linter#Reset()
|
|
call ale#lsp#ResetConnections()
|
|
|
|
unlet! g:ale_run_synchronously_callbacks
|
|
unlet! g:job_map
|
|
unlet! g:emulate_job_failure
|
|
unlet! g:next_job_id
|
|
unlet! g:lsp_started
|
|
|
|
unlet! g:socket_map
|
|
unlet! g:emulate_socket_failure
|
|
unlet! g:next_channel_id
|
|
|
|
unlet! g:message_buffer
|
|
unlet! g:calls
|
|
|
|
augroup VaderTest
|
|
autocmd!
|
|
augroup END
|
|
|
|
augroup! VaderTest
|
|
|
|
delfunction PopMessages
|
|
delfunction Start
|
|
delfunction AssertInitSuccess
|
|
delfunction AssertInitFailure
|
|
|
|
runtime autoload/ale/engine.vim
|
|
runtime autoload/ale/job.vim
|
|
runtime autoload/ale/socket.vim
|
|
|
|
Execute(tsserver should be started correctly):
|
|
runtime ale_linters/typescript/tsserver.vim
|
|
|
|
Assert Start(bufnr(''))
|
|
call AssertInitSuccess('tsserver', 'tsserver', '', '', ale#Escape('tsserver'), bufnr(''))
|
|
|
|
Execute(tsserver failures should be handled appropriately):
|
|
runtime ale_linters/typescript/tsserver.vim
|
|
|
|
let g:emulate_job_failure = 1
|
|
|
|
Assert !Start(bufnr(''))
|
|
call AssertInitFailure()
|
|
|
|
Execute(LSP jobs should start correctly):
|
|
call ale#linter#Define('foobar', {
|
|
\ 'name': 'foo',
|
|
\ 'lsp': 'stdio',
|
|
\ 'executable': 'foo',
|
|
\ 'command': 'foo',
|
|
\ 'project_root': '/foo/bar',
|
|
\ 'initialization_options': {},
|
|
\})
|
|
|
|
Assert Start(bufnr(''))
|
|
call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', 'foo', bufnr(''))
|
|
|
|
Execute(LSP job failures should be handled):
|
|
call ale#linter#Define('foobar', {
|
|
\ 'name': 'foo',
|
|
\ 'lsp': 'stdio',
|
|
\ 'executable': 'foo',
|
|
\ 'command': 'foo',
|
|
\ 'project_root': '/foo/bar',
|
|
\ 'initialization_options': {},
|
|
\})
|
|
|
|
let g:emulate_job_failure = 1
|
|
|
|
Assert !Start(bufnr(''))
|
|
call AssertInitFailure()
|
|
|
|
Execute(LSP TCP connections should start correctly):
|
|
call ale#linter#Define('foobar', {
|
|
\ 'name': 'foo',
|
|
\ 'lsp': 'socket',
|
|
\ 'address': 'foo',
|
|
\ 'project_root': '/foo/bar',
|
|
\ 'initialization_options': {},
|
|
\})
|
|
|
|
Assert Start(bufnr(''))
|
|
call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', '', bufnr(''))
|
|
|
|
Execute(LSP TCP connection failures should be handled):
|
|
call ale#linter#Define('foobar', {
|
|
\ 'name': 'foo',
|
|
\ 'lsp': 'socket',
|
|
\ 'address': 'foo',
|
|
\ 'project_root': '/foo/bar',
|
|
\ 'initialization_options': {},
|
|
\})
|
|
|
|
let g:emulate_socket_failure = 1
|
|
|
|
Assert !Start(bufnr(''))
|
|
call AssertInitFailure()
|
|
|
|
Execute(Deferred executables should be handled correctly):
|
|
call ale#linter#Define('foobar', {
|
|
\ 'name': 'foo',
|
|
\ 'lsp': 'stdio',
|
|
\ 'executable': {b -> ale#command#Run(b, 'echo', {-> 'foo'})},
|
|
\ 'command': '%e -c',
|
|
\ 'project_root': '/foo/bar',
|
|
\ 'initialization_options': {},
|
|
\})
|
|
|
|
Assert Start(bufnr(''))
|
|
call ale#test#FlushJobs()
|
|
call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', ale#Escape('foo') . ' -c', bufnr(''))
|
|
|
|
Execute(Deferred commands should be handled correctly):
|
|
call ale#linter#Define('foobar', {
|
|
\ 'name': 'foo',
|
|
\ 'lsp': 'stdio',
|
|
\ 'executable': 'foo',
|
|
\ 'command': {b -> ale#command#Run(b, 'echo', {-> '%e -c'})},
|
|
\ 'project_root': '/foo/bar',
|
|
\ 'initialization_options': {},
|
|
\})
|
|
|
|
Assert Start(bufnr(''))
|
|
call ale#test#FlushJobs()
|
|
call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', ale#Escape('foo') . ' -c', bufnr(''))
|
|
|
|
Execute(Deferred addresses should be handled correctly):
|
|
call ale#linter#Define('foobar', {
|
|
\ 'name': 'foo',
|
|
\ 'lsp': 'socket',
|
|
\ 'address': {b -> ale#command#Run(b, 'echo', {-> 'localhost:1234'})},
|
|
\ 'project_root': '/foo/bar',
|
|
\ 'initialization_options': {},
|
|
\})
|
|
|
|
Assert Start(bufnr(''))
|
|
call ale#test#FlushJobs()
|
|
call AssertInitSuccess('foo', 'localhost:1234', 'foobar', '/foo/bar', '', bufnr(''))
|
|
|
|
Execute(Servers that have crashed should be restarted):
|
|
call ale#lsp#Register('foo', '/foo/bar', '', {})
|
|
call extend(ale#lsp#GetConnections()['foo:/foo/bar'], {'initialized': 1})
|
|
|
|
" Starting the program again should reset initialized to `0`.
|
|
call ale#lsp#StartProgram('foo:/foo/bar', 'foobar', 'foobar --start')
|
|
|
|
AssertEqual 0, ale#lsp#GetConnections()['foo:/foo/bar']['initialized']
|
|
AssertEqual ['initialize'], map(PopMessages(), 'v:val[''method'']')
|
|
|
|
Execute(Current LSP buffer should receive ALELSPStarted):
|
|
call ale#linter#Define('foobar', {
|
|
\ 'name': 'foo',
|
|
\ 'lsp': 'socket',
|
|
\ 'address': 'foo',
|
|
\ 'project_root': '/foo/bar',
|
|
\ 'initialization_options': {},
|
|
\})
|
|
|
|
augroup VaderTest
|
|
autocmd!
|
|
autocmd User ALELSPStarted let g:lsp_started = 1
|
|
augroup END
|
|
|
|
Assert Start(bufnr(''))
|
|
call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', '', bufnr(''))
|
|
AssertEqual g:lsp_started, 1
|
|
|
|
Execute(Target LSP buffer should receive ALELSPStarted):
|
|
call ale#linter#Define('foobar', {
|
|
\ 'name': 'foo',
|
|
\ 'lsp': 'socket',
|
|
\ 'address': 'foo',
|
|
\ 'project_root': '/foo/bar',
|
|
\ 'initialization_options': {},
|
|
\})
|
|
|
|
augroup VaderTest
|
|
autocmd!
|
|
autocmd User ALELSPStarted let g:lsp_started = 1
|
|
augroup END
|
|
|
|
let buffer = bufnr('')
|
|
|
|
enew!
|
|
Assert Start(buffer)
|
|
call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', '', buffer)
|
|
execute 'buffer' . buffer
|
|
|
|
AssertEqual g:lsp_started, 1
|