refactor(lsp): merge rpc.domain_socket_connect into rpc.connect (#28398)

See discussion in https://github.com/neovim/neovim/pull/26850
This commit is contained in:
Mathias Fußenegger
2024-04-18 15:34:10 +02:00
committed by GitHub
parent 206475d791
commit 97323d821b
4 changed files with 41 additions and 90 deletions

View File

@@ -2229,32 +2229,20 @@ Lua module: vim.lsp.rpc *lsp-rpc*
• {terminate} (`fun()`) • {terminate} (`fun()`)
connect({host}, {port}) *vim.lsp.rpc.connect()* connect({host_or_path}, {port}) *vim.lsp.rpc.connect()*
Create a LSP RPC client factory that connects via TCP to the given host Create a LSP RPC client factory that connects to either:
and port. • a named pipe (windows)
• a domain socket (unix)
• a host and port via TCP
Return a function that can be passed to the `cmd` field for Return a function that can be passed to the `cmd` field for
|vim.lsp.start_client()| or |vim.lsp.start()|. |vim.lsp.start_client()| or |vim.lsp.start()|.
Parameters: ~ Parameters: ~
• {host} (`string`) host to connect to • {host_or_path} (`string`) host to connect to or path to a pipe/domain
• {port} (`integer`) port to connect to socket
• {port} (`integer?`) TCP port to connect to. If absent the
Return: ~ first argument must be a pipe
(`fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient`)
*vim.lsp.rpc.domain_socket_connect()*
domain_socket_connect({pipe_path})
Create a LSP RPC client factory that connects via named pipes (Windows) or
unix domain sockets (Unix) to the given pipe_path (file path on Unix and
name on Windows).
Return a function that can be passed to the `cmd` field for
|vim.lsp.start_client()| or |vim.lsp.start()|.
Parameters: ~
• {pipe_path} (`string`) file path of the domain socket (Unix) or name
of the named pipe (Windows) to connect to
Return: ~ Return: ~
(`fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient`) (`fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient`)

View File

@@ -225,7 +225,7 @@ The following new APIs and features were added.
• |vim.lsp.util.locations_to_items()| sets the `user_data` of each item to • |vim.lsp.util.locations_to_items()| sets the `user_data` of each item to
the original LSP `Location` or `LocationLink`. the original LSP `Location` or `LocationLink`.
• Added support for connecting to servers using named pipes (Windows) or • Added support for connecting to servers using named pipes (Windows) or
unix domain sockets (Unix) via |vim.lsp.rpc.domain_socket_connect()|. unix domain sockets (Unix) via |vim.lsp.rpc.connect()|.
• Added support for `completionList.itemDefaults`, reducing overhead when • Added support for `completionList.itemDefaults`, reducing overhead when
computing completion items where properties often share the same value computing completion items where properties often share the same value
(e.g. `commitCharacters`). Note that this might affect plugins and (e.g. `commitCharacters`). Note that this might affect plugins and

View File

@@ -621,22 +621,33 @@ local function merge_dispatchers(dispatchers)
return merged return merged
end end
--- Create a LSP RPC client factory that connects via TCP to the given host and port. --- Create a LSP RPC client factory that connects to either:
---
--- - a named pipe (windows)
--- - a domain socket (unix)
--- - a host and port via TCP
--- ---
--- Return a function that can be passed to the `cmd` field for --- Return a function that can be passed to the `cmd` field for
--- |vim.lsp.start_client()| or |vim.lsp.start()|. --- |vim.lsp.start_client()| or |vim.lsp.start()|.
--- ---
---@param host string host to connect to ---@param host_or_path string host to connect to or path to a pipe/domain socket
---@param port integer port to connect to ---@param port integer? TCP port to connect to. If absent the first argument must be a pipe
---@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient ---@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient
function M.connect(host, port) function M.connect(host_or_path, port)
return function(dispatchers) return function(dispatchers)
dispatchers = merge_dispatchers(dispatchers) dispatchers = merge_dispatchers(dispatchers)
local tcp = assert(uv.new_tcp()) local handle = (
port == nil
and assert(
uv.new_pipe(false),
string.format('Pipe with name %s could not be opened.', host_or_path)
)
or assert(uv.new_tcp(), 'Could not create new TCP socket')
)
local closing = false local closing = false
local transport = { local transport = {
write = function(msg) write = function(msg)
tcp:write(msg) handle:write(msg)
end, end,
is_closing = function() is_closing = function()
return closing return closing
@@ -644,18 +655,19 @@ function M.connect(host, port)
terminate = function() terminate = function()
if not closing then if not closing then
closing = true closing = true
tcp:shutdown() handle:shutdown()
tcp:close() handle:close()
dispatchers.on_exit(0, 0) dispatchers.on_exit(0, 0)
end end
end, end,
} }
local client = new_client(dispatchers, transport) local client = new_client(dispatchers, transport)
tcp:connect(host, port, function(err) local function on_connect(err)
if err then if err then
local address = port == nil and host_or_path or (host_or_path .. ':' .. port)
vim.schedule(function() vim.schedule(function()
vim.notify( vim.notify(
string.format('Could not connect to %s:%s, reason: %s', host, port, vim.inspect(err)), string.format('Could not connect to %s, reason: %s', address, vim.inspect(err)),
vim.log.levels.WARN vim.log.levels.WARN
) )
end) end)
@@ -664,64 +676,15 @@ function M.connect(host, port)
local handle_body = function(body) local handle_body = function(body)
client:handle_body(body) client:handle_body(body)
end end
tcp:read_start(M.create_read_loop(handle_body, transport.terminate, function(read_err) handle:read_start(M.create_read_loop(handle_body, transport.terminate, function(read_err)
client:on_error(M.client_errors.READ_ERROR, read_err) client:on_error(M.client_errors.READ_ERROR, read_err)
end)) end))
end)
return public_client(client)
end end
end if port == nil then
handle:connect(host_or_path, on_connect)
--- Create a LSP RPC client factory that connects via named pipes (Windows) else
--- or unix domain sockets (Unix) to the given pipe_path (file path on handle:connect(host_or_path, port, on_connect)
--- Unix and name on Windows).
---
--- Return a function that can be passed to the `cmd` field for
--- |vim.lsp.start_client()| or |vim.lsp.start()|.
---
---@param pipe_path string file path of the domain socket (Unix) or name of the named pipe (Windows) to connect to
---@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient
function M.domain_socket_connect(pipe_path)
return function(dispatchers)
dispatchers = merge_dispatchers(dispatchers)
local pipe =
assert(uv.new_pipe(false), string.format('pipe with name %s could not be opened.', pipe_path))
local closing = false
local transport = {
write = vim.schedule_wrap(function(msg)
pipe:write(msg)
end),
is_closing = function()
return closing
end,
terminate = function()
if not closing then
closing = true
pipe:shutdown()
pipe:close()
dispatchers.on_exit(0, 0)
end end
end,
}
local client = new_client(dispatchers, transport)
pipe:connect(pipe_path, function(err)
if err then
vim.schedule(function()
vim.notify(
string.format('Could not connect to :%s, reason: %s', pipe_path, vim.inspect(err)),
vim.log.levels.WARN
)
end)
return
end
local handle_body = function(body)
client:handle_body(body)
end
pipe:read_start(M.create_read_loop(handle_body, transport.terminate, function(read_err)
client:on_error(M.client_errors.READ_ERROR, read_err)
end))
end)
return public_client(client) return public_client(client)
end end

View File

@@ -4311,7 +4311,7 @@ describe('LSP', function()
]] ]]
eq('initialize', result.method) eq('initialize', result.method)
end) end)
it('can connect to lsp server via rpc.domain_socket_connect', function() it('can connect to lsp server via pipe or domain_socket', function()
local tmpfile --- @type string local tmpfile --- @type string
if is_os('win') then if is_os('win') then
tmpfile = '\\\\.\\\\pipe\\pipe.test' tmpfile = '\\\\.\\\\pipe\\pipe.test'
@@ -4336,7 +4336,7 @@ describe('LSP', function()
client:close() client:close()
end)) end))
end) end)
vim.lsp.start({ name = "dummy", cmd = vim.lsp.rpc.domain_socket_connect(SOCK) }) vim.lsp.start({ name = "dummy", cmd = vim.lsp.rpc.connect(SOCK) })
vim.wait(1000, function() return init ~= nil end) vim.wait(1000, function() return init ~= nil end)
assert(init, "server must receive `initialize` request") assert(init, "server must receive `initialize` request")
server:close() server:close()