Add Neovim TCP connections to language servers

Support TCP connections to language servers through Neovim's built in
client. In all but what is currently the nightly builds of Neovim
connections via a hostname will fail, but connections via an IP address
should function. We will still enable the built in Neovim client by
default anyway, as LSP clients very rarely connect over TCP.
This commit is contained in:
w0rp
2025-03-19 18:39:05 +00:00
parent 73b568b071
commit cdbd218a82
4 changed files with 71 additions and 2 deletions

View File

@@ -569,6 +569,21 @@ function! ale#lsp#StartProgram(conn_id, executable, command) abort
return l:job_id > 0
endfunction
" Split an address into [host, port].
" The port will either be a number or v:null.
function! ale#lsp#SplitAddress(address) abort
let l:port_match = matchlist(a:address, '\v:(\d+)$')
if !empty(l:port_match)
let l:host = a:address[:-len(l:port_match[1]) - 2]
let l:port = l:port_match[1] + 0
return [l:host, l:port ? l:port : v:null]
endif
return [a:address, v:null]
endfunction
" Connect to an LSP server via TCP.
"
" 1 will be returned if the connection is running, or 0 if the connection could
@@ -577,8 +592,23 @@ function! ale#lsp#ConnectToAddress(conn_id, address) abort
let l:conn = s:connections[a:conn_id]
let l:started = 0
" TODO: Start Neovim client here.
if !has_key(l:conn, 'channel_id') || !ale#socket#IsOpen(l:conn.channel_id)
if g:ale_use_neovim_lsp_api && !l:conn.is_tsserver
let [l:host, l:port] = ale#lsp#SplitAddress(a:address)
let l:client_id = luaeval('require("ale.lsp").start(_A)', {
\ 'name': a:conn_id,
\ 'host': l:host,
\ 'port': l:port,
\ 'root_dir': l:conn.root,
\ 'init_options': l:conn.init_options,
\})
if l:client_id > 0
let l:conn.client_id = l:client_id
endif
return l:client_id > 0
elseif !has_key(l:conn, 'channel_id') || !ale#socket#IsOpen(l:conn.channel_id)
let l:channel_id = ale#socket#Open(a:address, {
\ 'callback': {_, mess -> ale#lsp#HandleMessage(a:conn_id, mess)},
\})

View File

@@ -476,6 +476,11 @@ functionality for Neovim's LSP client should work as expected, and this
ensures ALE integrates well with other plugins that rely on Neovim's LSP
client.
NOTE: Neovim versions below `0.11.0` do not support socket connections to
language servers when the `address` defined in ALE uses a hostname instead of
an IP address. To work around this, configure language clients with an IP
address instead of a hostname, or revert back to ALE's custom LSP client.
See |lsp| for information on Neovim's built in LSP client.
For diagnostics, for computing problems to show via ALE, ALE overrides the

View File

@@ -6,6 +6,31 @@ module.start = function(config)
config.init_options[true] = nil
end
-- If configuring LSP via a socket connection, then generate the cmd
-- using vim.lsp.rpc.connect(), as defined in Neovim documentation.
if config.host then
local cmd_func = vim.lsp.rpc.connect(config.host, config.port)
config.host = nil
config.port = nil
-- Wrap the cmd function so we don't throw errors back to the user
-- if the connection to an address fails to start.
--
-- We will separately log in ALE that we failed to start a connection.
--
-- In older Neovim versions TCP connections do not function if supplied
-- a hostname instead of an address.
config.cmd = function(dispatch)
local success, result = pcall(cmd_func, dispatch)
if success then
return result
end
return nil
end
end
config.handlers = {
-- Override Neovim's handling of diagnostics to run through ALE's
-- functions so all of the functionality in ALE works.

View File

@@ -0,0 +1,9 @@
Execute(Address splitting should function as intended):
AssertEqual ['foo', v:null], ale#lsp#SplitAddress('foo')
AssertEqual ['foo:', v:null], ale#lsp#SplitAddress('foo:')
AssertEqual ['foo', v:null], ale#lsp#SplitAddress('foo:0')
AssertEqual ['foo', 123], ale#lsp#SplitAddress('foo:123')
AssertEqual ['protocol:/foo:', v:null], ale#lsp#SplitAddress('protocol:/foo:')
AssertEqual ['protocol:/foo', v:null], ale#lsp#SplitAddress('protocol:/foo:0')
AssertEqual ['protocol:/foo', 123], ale#lsp#SplitAddress('protocol:/foo:123')
AssertEqual ['protocol:foo', v:null], ale#lsp#SplitAddress('protocol:foo')