summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorw0rp <devw0rp@gmail.com>2023-09-16 17:03:02 +0100
committerw0rp <devw0rp@gmail.com>2023-09-16 17:03:02 +0100
commit1799f8bec63508fb103236cacefb60a5e5100be1 (patch)
treed06e57b5a0f0854abe66a2f0aad62535c0739d78
parentbe69af270547b0d1dd5fe947efb3f61455f709ed (diff)
downloadale-1799f8bec63508fb103236cacefb60a5e5100be1.zip
Close #4458 - Add an ALEStopLSP command
Add an ALEStopLSP command to stop all language servers that match a given name. Completions are available for the command. This makes it possible to keep other language servers running other than the one you're interested in stopping.
-rw-r--r--autoload/ale/lsp/reset.vim67
-rw-r--r--autoload/ale/lsp_linter.vim34
-rw-r--r--doc/ale.txt16
-rw-r--r--plugin/ale.vim2
-rw-r--r--test/lsp/test_reset_lsp.vader143
5 files changed, 216 insertions, 46 deletions
diff --git a/autoload/ale/lsp/reset.vim b/autoload/ale/lsp/reset.vim
index 85188b5a..1801db01 100644
--- a/autoload/ale/lsp/reset.vim
+++ b/autoload/ale/lsp/reset.vim
@@ -1,9 +1,16 @@
+" Author: w0rp <dev@w0rp.com>
+" Description: Functions for resetting LSP servers.
+
+function! s:Message(message) abort
+ call ale#util#Execute('echom ' . string(a:message))
+endfunction
+
" 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.
+ " Clear the go to definition mapping for everything.
call ale#definition#ClearLSPData()
endif
@@ -25,3 +32,61 @@ function! ale#lsp#reset#StopAllLSPs() abort
endfor
endif
endfunction
+
+function! ale#lsp#reset#Complete(arg, line, pos) abort
+ let l:linter_map = ale#lsp_linter#GetLSPLinterMap()
+ let l:candidates = map(values(l:linter_map), {_, linter -> linter.name})
+ call uniq(sort(l:candidates))
+ call filter(l:candidates, {_, name -> name =~? a:arg})
+
+ return l:candidates
+endfunction
+
+function! ale#lsp#reset#StopLSP(name, bang) abort
+ let l:linter_map = ale#lsp_linter#GetLSPLinterMap()
+ let l:matched = filter(
+ \ items(l:linter_map),
+ \ {_, item -> item[1].name is# a:name}
+ \)
+
+ if empty(l:matched)
+ if a:bang isnot# '!'
+ call s:Message('No running language server with name: ' . a:name)
+ endif
+
+ return
+ endif
+
+ " Stop LSP connections first.
+ for [l:conn_id, l:linter] in l:matched
+ call ale#lsp#Stop(l:conn_id)
+ endfor
+
+ if exists('*ale#definition#ClearLSPData')
+ " Clear the go to definition mapping for everything.
+ call ale#definition#ClearLSPData()
+ endif
+
+ " Remove connections from the lsp_linter map.
+ for [l:conn_id, l:linter] in l:matched
+ call remove(l:linter_map, l:conn_id)
+ endfor
+
+ " Remove the problems for the LSP linters in every buffer.
+ for [l:buffer_string, l:info] in items(g:ale_buffer_info)
+ let l:buffer = str2nr(l:buffer_string)
+ let l:should_clear_buffer = 0
+
+ for l:item in l:info.loclist
+ if l:item.linter_name is# a:name
+ let l:should_clear_buffer = 1
+
+ break
+ endif
+ endfor
+
+ if l:should_clear_buffer
+ call ale#engine#HandleLoclist(a:name, l:buffer, [], 0)
+ endif
+ endfor
+endfunction
diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim
index 05a0294c..a3c8b24a 100644
--- a/autoload/ale/lsp_linter.vim
+++ b/autoload/ale/lsp_linter.vim
@@ -8,13 +8,9 @@ if !has_key(s:, 'lsp_linter_map')
let s:lsp_linter_map = {}
endif
-" A Dictionary to track one-shot handlers for custom LSP requests
-let s:custom_handlers_map = get(s:, 'custom_handlers_map', {})
-
" Clear LSP linter data for the linting engine.
function! ale#lsp_linter#ClearLSPData() abort
let s:lsp_linter_map = {}
- let s:custom_handlers_map = {}
endfunction
" Only for internal use.
@@ -82,7 +78,12 @@ function! s:ShouldIgnoreDiagnostics(buffer, linter) abort
endfunction
function! s:HandleLSPDiagnostics(conn_id, response) abort
- let l:linter = s:lsp_linter_map[a:conn_id]
+ let l:linter = get(s:lsp_linter_map, a:conn_id)
+
+ if empty(l:linter)
+ return
+ endif
+
let l:filename = ale#util#ToResource(a:response.params.uri)
let l:escaped_name = escape(
\ fnameescape(l:filename),
@@ -540,9 +541,14 @@ endfunction
function! s:HandleLSPResponseToCustomRequests(conn_id, response) abort
if has_key(a:response, 'id')
- \&& has_key(s:custom_handlers_map, a:response.id)
- let l:Handler = remove(s:custom_handlers_map, a:response.id)
- call l:Handler(a:response)
+ " Get the custom handlers Dictionary from the linter map.
+ let l:linter = get(s:lsp_linter_map, a:conn_id, {})
+ let l:custom_handlers = get(l:linter, 'custom_handlers', {})
+
+ if has_key(l:custom_handlers, a:response.id)
+ let l:Handler = remove(l:custom_handlers, a:response.id)
+ call l:Handler(a:response)
+ endif
endif
endfunction
@@ -553,7 +559,17 @@ function! s:OnReadyForCustomRequests(args, linter, lsp_details) abort
if l:request_id > 0 && has_key(a:args, 'handler')
let l:Callback = function('s:HandleLSPResponseToCustomRequests')
call ale#lsp#RegisterCallback(l:id, l:Callback)
- let s:custom_handlers_map[l:request_id] = a:args.handler
+
+ " Remember the linter this connection is for.
+ let s:lsp_linter_map[l:id] = a:linter
+
+ " Add custom_handlers to the linter Dictionary.
+ if !has_key(a:linter, 'custom_handlers')
+ let a:linter.custom_handlers = {}
+ endif
+
+ " Put the handler function in the map to call later.
+ let a:linter.custom_handlers[l:request_id] = a:args.handler
endif
endfunction
diff --git a/doc/ale.txt b/doc/ale.txt
index 211ac1c3..9b25e6ec 100644
--- a/doc/ale.txt
+++ b/doc/ale.txt
@@ -455,6 +455,11 @@ If you want to use another plugin for LSP features and tsserver, you can use
the |g:ale_disable_lsp| setting to disable ALE's own LSP integrations, or
ignore particular linters with |g:ale_linters_ignore|.
+If for any reason you want to stop a language server ALE starts, such as when
+a project configuration has significantly changed, or new files have been
+added the language server isn't aware of, use either |ALEStopLSP| or
+|ALEStopAllLSPs| to stop the server until ALE automatically starts it again.
+
-------------------------------------------------------------------------------
5.1 Completion *ale-completion*
@@ -3931,6 +3936,17 @@ ALEStopAllLSPs *ALEStopAllLSPs*
This command can be used when LSP clients mess up and need to be restarted.
+ALEStopLSP `linter_name` *ALEStopLSP*
+
+ `ALEStopLSP` will stop a specific language server with a given linter name.
+ Completion is supported for currently running language servers. All language
+ servers with the given name will be stopped across all buffers for all
+ projects.
+
+ If the command is run with a bang (`:ALEStopLSP!`), all warnings will be
+ suppressed.
+
+
===============================================================================
9. API *ale-api*
diff --git a/plugin/ale.vim b/plugin/ale.vim
index a721bd89..47934fa0 100644
--- a/plugin/ale.vim
+++ b/plugin/ale.vim
@@ -246,6 +246,8 @@ 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 to stop a specific language server, or tsseserver.
+command! -bar -bang -nargs=1 -complete=customlist,ale#lsp#reset#Complete ALEStopLSP :call ale#lsp#reset#StopLSP(<f-args>, '<bang>')
" 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
index 310b3d62..c6be47e6 100644
--- a/test/lsp/test_reset_lsp.vader
+++ b/test/lsp/test_reset_lsp.vader
@@ -5,6 +5,7 @@ Before:
Save g:ale_set_loclist
Save g:ale_set_highlights
Save g:ale_echo_cursor
+ Save g:ale_buffer_info
let g:ale_enabled = 0
let g:ale_set_signs = 0
@@ -12,14 +13,20 @@ Before:
let g:ale_set_loclist = 0
let g:ale_set_highlights = 0
let g:ale_echo_cursor = 0
+ let g:expr_list = []
function EmptyString() abort
return ''
endfunction
+ runtime autoload/ale/util.vim
+
+ function! ale#util#Execute(expr) abort
+ call add(g:expr_list, a:expr)
+ endfunction
+
call ale#engine#InitBufferInfo(bufnr(''))
- " Call this function first, so we can be sure the module is loaded before we
- " check if it exists.
+ " Call this function first, to clear LSP data.
call ale#lsp_linter#ClearLSPData()
call ale#linter#Define('testft', {
@@ -30,7 +37,14 @@ Before:
\ 'project_root': function('EmptyString'),
\ 'language': function('EmptyString'),
\})
-
+ call ale#linter#Define('testft', {
+ \ 'name': 'lsplinter2',
+ \ 'lsp': 'tsserver',
+ \ 'executable': function('EmptyString'),
+ \ 'command': function('EmptyString'),
+ \ 'project_root': function('EmptyString'),
+ \ 'language': function('EmptyString'),
+ \})
call ale#linter#Define('testft', {
\ 'name': 'otherlinter',
\ 'callback': 'TestCallback',
@@ -42,34 +56,23 @@ Before:
After:
Restore
+ delfunction EmptyString
+ unlet! g:expr_list
unlet! b:ale_save_event_fired
- delfunction EmptyString
+ " Clear LSP data after tests.
+ call ale#lsp_linter#ClearLSPData()
+
+ runtime autoload/ale/util.vim
+
call ale#linter#Reset()
Given testft(Some file with an imaginary filetype):
Execute(ALEStopAllLSPs should clear the loclist):
+ " For these tests we only need to set the keys we need.
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',
- \ },
+ \ {'linter_name': 'lsplinter'},
+ \ {'linter_name': 'otherlinter'},
\]
let g:ale_buffer_info[bufnr('')].active_linter_list = [
\ {'name': 'lsplinter'},
@@ -79,20 +82,88 @@ Execute(ALEStopAllLSPs should clear the loclist):
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',
- \ },
- \]
+ AssertEqual
+ \ ['otherlinter'],
+ \ map(copy(g:ale_buffer_info[bufnr('')].loclist), 'v:val.linter_name')
" The LSP linter should be removed from the active linter list.
AssertEqual
\ ['otherlinter'],
\ map(copy(g:ale_buffer_info[bufnr('')].active_linter_list), 'v:val.name')
+
+Execute(ALEStopLSP should stop a named LSP):
+ let g:ale_buffer_info[bufnr('')].loclist = [
+ \ {'linter_name': 'lsplinter'},
+ \ {'linter_name': 'lsplinter2'},
+ \ {'linter_name': 'otherlinter'},
+ \]
+ let g:ale_buffer_info[bufnr('')].active_linter_list = [
+ \ {'name': 'lsplinter'},
+ \ {'name': 'lsplinter2'},
+ \ {'name': 'otherlinter'},
+ \]
+ call ale#lsp_linter#SetLSPLinterMap({
+ \ 'conn1': {'name': 'lsplinter'},
+ \ 'conn2': {'name': 'lsplinter2'},
+ \ 'conn3': {'name': 'lsplinter'},
+ \})
+
+ ALEStopLSP lsplinter
+
+ " We should remove only the items for this linter.
+ AssertEqual
+ \ ['lsplinter2', 'otherlinter'],
+ \ map(copy(g:ale_buffer_info[bufnr('')].loclist), 'v:val.linter_name')
+
+ " The linter should be removed from the active linter list.
+ AssertEqual
+ \ ['lsplinter2', 'otherlinter'],
+ \ map(copy(g:ale_buffer_info[bufnr('')].active_linter_list), 'v:val.name')
+
+ " The connections linters with this name should be removed.
+ AssertEqual
+ \ {'conn2': {'name': 'lsplinter2'}},
+ \ ale#lsp_linter#GetLSPLinterMap()
+
+Execute(ALEStopLSP should not clear results for linters not running):
+ let g:ale_buffer_info[bufnr('')].loclist = [
+ \ {'linter_name': 'lsplinter'},
+ \ {'linter_name': 'otherlinter'},
+ \]
+ let g:ale_buffer_info[bufnr('')].active_linter_list = [
+ \ {'name': 'lsplinter'},
+ \ {'name': 'otherlinter'},
+ \]
+
+ ALEStopLSP lsplinter
+
+ " We should emit a message saying the server isn't running.
+ AssertEqual
+ \ ['echom ''No running language server with name: lsplinter'''],
+ \ g:expr_list
+
+ " We should keep the linter items.
+ AssertEqual
+ \ ['lsplinter', 'otherlinter'],
+ \ map(copy(g:ale_buffer_info[bufnr('')].loclist), 'v:val.linter_name')
+ AssertEqual
+ \ ['lsplinter', 'otherlinter'],
+ \ map(copy(g:ale_buffer_info[bufnr('')].active_linter_list), 'v:val.name')
+
+Execute(ALEStopLSP with a bang should not emit warnings):
+ ALEStopLSP! lsplinter
+
+ AssertEqual [], g:expr_list
+
+Execute(ALEStopLSP's completion function should suggest running linter names):
+ call ale#lsp_linter#SetLSPLinterMap({
+ \ 'conn1': {'name': 'pyright'},
+ \ 'conn2': {'name': 'pylsp'},
+ \ 'conn3': {'name': 'imaginaryserver'},
+ \})
+
+ AssertEqual
+ \ ['imaginaryserver', 'pylsp', 'pyright'],
+ \ ale#lsp#reset#Complete('', '', 42)
+ AssertEqual ['imaginaryserver'], ale#lsp#reset#Complete('inary', '', 42)
+ AssertEqual ['pylsp'], ale#lsp#reset#Complete('LSP', '', 42)