diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index a8b5825e63..e820d54a9e 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -2311,14 +2311,14 @@ connect({host_or_path}, {port}) *vim.lsp.rpc.connect()* Create a LSP RPC client factory that connects to either: • a named pipe (windows) • a domain socket (unix) - • a host and port via TCP (host must be IP address) + • a host and port via TCP Return a function that can be passed to the `cmd` field for |vim.lsp.start_client()| or |vim.lsp.start()|. Parameters: ~ - • {host_or_path} (`string`) host (IP address) to connect to or path to - a pipe/domain socket + • {host_or_path} (`string`) host to connect to or path to a pipe/domain + socket • {port} (`integer?`) TCP port to connect to. If absent the first argument must be a pipe diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index a9c6723094..d81eae20d1 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -627,12 +627,12 @@ end --- --- - a named pipe (windows) --- - a domain socket (unix) ---- - a host and port via TCP (host must be IP address) +--- - a host and port via TCP --- --- Return a function that can be passed to the `cmd` field for --- |vim.lsp.start_client()| or |vim.lsp.start()|. --- ----@param host_or_path string host (IP address) to connect to or path to a pipe/domain socket +---@param host_or_path string host to connect to or path to a pipe/domain socket ---@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 function M.connect(host_or_path, port) @@ -703,7 +703,9 @@ function M.connect(host_or_path, port) if port == nil then handle:connect(host_or_path, on_connect) else - handle:connect(host_or_path, port, on_connect) + local info = uv.getaddrinfo(host_or_path, nil) + local resolved_host = info and info[1] and info[1].addr or host_or_path + handle:connect(resolved_host, port, on_connect) end return public_client(client) diff --git a/test/functional/plugin/lsp/testutil.lua b/test/functional/plugin/lsp/testutil.lua index 2595f6ad09..f60d111f87 100644 --- a/test/functional/plugin/lsp/testutil.lua +++ b/test/functional/plugin/lsp/testutil.lua @@ -21,6 +21,33 @@ function M.clear_notrace() } end +M.create_tcp_echo_server = function() + --- Create a TCP server that echos the first message it receives. + --- @param host string + ---@return uv.uv_tcp_t + ---@return integer + ---@return fun():string|nil + function _G._create_tcp_server(host) + local uv = vim.uv + local server = assert(uv.new_tcp()) + local init = nil + server:bind(host, 0) + server:listen(127, function(err) + assert(not err, err) + local socket = assert(uv.new_tcp()) + server:accept(socket) + socket:read_start(require('vim.lsp.rpc').create_read_loop(function(body) + init = body + socket:close() + end)) + end) + local port = server:getsockname().port + return server, port, function() + return init + end + end +end + M.create_server_definition = function() function _G._create_server(opts) opts = opts or {} diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index ff042424f9..1347ea9745 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -33,6 +33,7 @@ local create_server_definition = t_lsp.create_server_definition local fake_lsp_code = t_lsp.fake_lsp_code local fake_lsp_logfile = t_lsp.fake_lsp_logfile local test_rpc_server = t_lsp.test_rpc_server +local create_tcp_echo_server = t_lsp.create_tcp_echo_server local function get_buf_option(name, bufnr) bufnr = bufnr or 'BUFFER' @@ -4981,26 +4982,33 @@ describe('LSP', function() end) describe('cmd', function() - it('can connect to lsp server via rpc.connect', function() + it('connects to lsp server via rpc.connect using ip address', function() + exec_lua(create_tcp_echo_server) local result = exec_lua(function() - local uv = vim.uv - local server = assert(uv.new_tcp()) - local init = nil - server:bind('127.0.0.1', 0) - server:listen(127, function(err) - assert(not err, err) - local socket = assert(uv.new_tcp()) - server:accept(socket) - socket:read_start(require('vim.lsp.rpc').create_read_loop(function(body) - init = body - socket:close() - end)) - end) - local port = server:getsockname().port + local server, port, last_message = _G._create_tcp_server('127.0.0.1') vim.lsp.start({ name = 'dummy', cmd = vim.lsp.rpc.connect('127.0.0.1', port) }) vim.wait(1000, function() - return init ~= nil + return last_message() ~= nil end) + local init = last_message() + assert(init, 'server must receive `initialize` request') + server:close() + server:shutdown() + return vim.json.decode(init) + end) + eq('initialize', result.method) + end) + + it('connects to lsp server via rpc.connect using hostname', function() + skip(is_os('bsd'), 'issue with host resolution in ci') + exec_lua(create_tcp_echo_server) + local result = exec_lua(function() + local server, port, last_message = _G._create_tcp_server('::1') + vim.lsp.start({ name = 'dummy', cmd = vim.lsp.rpc.connect('localhost', port) }) + vim.wait(1000, function() + return last_message() ~= nil + end) + local init = last_message() assert(init, 'server must receive `initialize` request') server:close() server:shutdown()