From 0a0535546f4d9a0dfe02671630fdaba72ea5828d Mon Sep 17 00:00:00 2001 From: w0rp Date: Tue, 6 Mar 2018 10:23:49 +0000 Subject: [PATCH] Add a command for stopping all LSP clients --- autoload/ale/definition.vim | 4 ++ autoload/ale/engine.vim | 13 +++-- autoload/ale/lsp.vim | 14 ++++++ autoload/ale/lsp/reset.vim | 25 ++++++++++ doc/ale.txt | 9 ++++ plugin/ale.vim | 2 + test/lsp/test_reset_lsp.vader | 90 +++++++++++++++++++++++++++++++++++ 7 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 autoload/ale/lsp/reset.vim create mode 100644 test/lsp/test_reset_lsp.vader diff --git a/autoload/ale/definition.vim b/autoload/ale/definition.vim index b20c01a0..521cd0b1 100644 --- a/autoload/ale/definition.vim +++ b/autoload/ale/definition.vim @@ -19,6 +19,10 @@ function! ale#definition#Execute(expr) abort execute a:expr endfunction +function! ale#definition#ClearLSPData() abort + let s:go_to_definition_map = {} +endfunction + function! ale#definition#Open(options, filename, line, column) abort if a:options.open_in_tab call ale#definition#Execute('tabedit ' . fnameescape(a:filename)) diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index 6ccc3a34..89169874 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -76,6 +76,11 @@ 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 + " This function is documented and part of the public API. " " Return 1 if ALE is busy checking a given buffer @@ -144,7 +149,7 @@ function! s:GatherOutput(job_id, line) abort endif endfunction -function! s:HandleLoclist(linter_name, buffer, loclist) abort +function! ale#engine#HandleLoclist(linter_name, buffer, loclist) abort let l:info = get(g:ale_buffer_info, a:buffer, {}) if empty(l:info) @@ -223,7 +228,7 @@ function! s:HandleExit(job_id, exit_code) abort let l:loclist = ale#util#GetFunction(l:linter.callback)(l:buffer, l:output) - call s:HandleLoclist(l:linter.name, l:buffer, l:loclist) + call ale#engine#HandleLoclist(l:linter.name, l:buffer, l:loclist) endfunction function! s:HandleLSPDiagnostics(conn_id, response) abort @@ -237,7 +242,7 @@ function! s:HandleLSPDiagnostics(conn_id, response) abort let l:loclist = ale#lsp#response#ReadDiagnostics(a:response) - call s:HandleLoclist(l:linter_name, l:buffer, l:loclist) + call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist) endfunction function! s:HandleTSServerDiagnostics(response, error_type) abort @@ -262,7 +267,7 @@ function! s:HandleTSServerDiagnostics(response, error_type) abort let l:loclist = get(l:info, 'semantic_loclist', []) \ + get(l:info, 'syntax_loclist', []) - call s:HandleLoclist('tsserver', l:buffer, l:loclist) + call ale#engine#HandleLoclist('tsserver', l:buffer, l:loclist) endfunction function! s:HandleLSPErrorMessage(error_message) abort diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index 126d6c18..8db9348f 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -325,6 +325,20 @@ function! ale#lsp#ConnectToAddress(address, project_root, callback) abort return 1 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) + else + call ale#job#Stop(l:conn.id) + endif + endfor + + let s:connections = [] +endfunction + function! s:SendMessageData(conn, data) abort if has_key(a:conn, 'executable') call ale#job#SendRaw(a:conn.id, a:data) diff --git a/autoload/ale/lsp/reset.vim b/autoload/ale/lsp/reset.vim new file mode 100644 index 00000000..c206ed08 --- /dev/null +++ b/autoload/ale/lsp/reset.vim @@ -0,0 +1,25 @@ +" Stop all LSPs and remove all of the data for them. +function! ale#lsp#reset#StopAllLSPs() abort + call ale#lsp#StopAll() + + if exists('*ale#definition#ClearLSPData') + " Clear the mapping for connections, etc. + call ale#definition#ClearLSPData() + endif + + if exists('*ale#engine#ClearLSPData') + " Clear the mapping for connections, etc. + call ale#engine#ClearLSPData() + + " Remove the problems for all of the LSP linters in every buffer. + for l:buffer_string in keys(g:ale_buffer_info) + let l:buffer = str2nr(l:buffer_string) + + for l:linter in ale#linter#Get(getbufvar(l:buffer, '&filetype')) + if !empty(l:linter.lsp) + call ale#engine#HandleLoclist(l:linter.name, l:buffer, []) + endif + endfor + endfor + endif +endfunction diff --git a/doc/ale.txt b/doc/ale.txt index 95c3c0e1..2e98cd71 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -1871,6 +1871,15 @@ ALEResetBuffer *ALEResetBuffer* |ALEDisableBuffer|. +ALEStopAllLSPs *ALEStopAllLSPs* + + `ALEStopAllLSPs` will close and stop all channels and jobs for all LSP-like + clients, including tsserver, remove all of the data stored for them, and + delete all of the problems found for them, updating every linted buffer. + + This command can be used when LSP clients mess up and need to be restarted. + + =============================================================================== 9. API *ale-api* diff --git a/plugin/ale.vim b/plugin/ale.vim index 69c08496..1aa35826 100644 --- a/plugin/ale.vim +++ b/plugin/ale.vim @@ -249,6 +249,8 @@ command! -bar ALEToggleBuffer :call ale#toggle#ToggleBuffer(bufnr('')) command! -bar ALEEnableBuffer :call ale#toggle#EnableBuffer(bufnr('')) command! -bar ALEDisableBuffer :call ale#toggle#DisableBuffer(bufnr('')) command! -bar ALEResetBuffer :call ale#toggle#ResetBuffer(bufnr('')) +" A command to stop all LSP-like clients, including tsserver. +command! -bar ALEStopAllLSPs :call ale#lsp#reset#StopAllLSPs() " A command for linting manually. command! -bar ALELint :call ale#Queue(0, 'lint_file') diff --git a/test/lsp/test_reset_lsp.vader b/test/lsp/test_reset_lsp.vader new file mode 100644 index 00000000..2bec13dc --- /dev/null +++ b/test/lsp/test_reset_lsp.vader @@ -0,0 +1,90 @@ +Before: + Save g:ale_enabled + Save g:ale_set_signs + Save g:ale_set_quickfix + Save g:ale_set_loclist + Save g:ale_set_highlights + Save g:ale_echo_cursor + + let g:ale_enabled = 0 + let g:ale_set_signs = 0 + let g:ale_set_quickfix = 0 + let g:ale_set_loclist = 0 + let g:ale_set_highlights = 0 + let g:ale_echo_cursor = 0 + + function EmptyString() abort + return '' + endfunction + + call ale#engine#InitBufferInfo(bufnr('')) + + call ale#linter#Define('testft', { + \ 'name': 'lsplinter', + \ 'lsp': 'tsserver', + \ 'executable_callback': 'EmptyString', + \ 'command_callback': 'EmptyString', + \ 'project_root_callback': 'EmptyString', + \ 'language_callback': 'EmptyString', + \}) + + call ale#linter#Define('testft', { + \ 'name': 'otherlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd': 'true', + \ 'command': 'true', + \ 'read_buffer': 0, + \}) + +After: + Restore + + unlet! b:ale_save_event_fired + + delfunction EmptyString + call ale#linter#Reset() + +Given testft(Some file with an imaginary filetype): +Execute(ALEStopAllLSPs should clear the loclist): + let g:ale_buffer_info[bufnr('')].loclist = [ + \ { + \ 'text': 'a', + \ 'lnum': 10, + \ 'col': 0, + \ 'bufnr': bufnr(''), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'lsplinter', + \ }, + \ { + \ 'text': 'a', + \ 'lnum': 10, + \ 'col': 0, + \ 'bufnr': bufnr(''), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'otherlinter', + \ }, + \] + let g:ale_buffer_info[bufnr('')].active_linter_list = ['lsplinter', 'otherlinter'] + + ALEStopAllLSPs + + " The loclist should be updated. + AssertEqual g:ale_buffer_info[bufnr('')].loclist, [ + \ { + \ 'text': 'a', + \ 'lnum': 10, + \ 'col': 0, + \ 'bufnr': bufnr(''), + \ 'vcol': 0, + \ 'type': 'E', + \ 'nr': -1, + \ 'linter_name': 'otherlinter', + \ }, + \] + + " The LSP linter should be removed from the active linter list. + AssertEqual g:ale_buffer_info[bufnr('')].active_linter_list, ['otherlinter']