mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			183 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			183 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| local uv = vim.uv
 | |
| local log = require('vim.lsp.log')
 | |
| 
 | |
| local is_win = vim.fn.has('win32') == 1
 | |
| 
 | |
| --- Checks whether a given path exists and is a directory.
 | |
| ---@param filename string path to check
 | |
| ---@return boolean
 | |
| local function is_dir(filename)
 | |
|   local stat = uv.fs_stat(filename)
 | |
|   return stat and stat.type == 'directory' or false
 | |
| end
 | |
| 
 | |
| --- @class (private) vim.lsp.rpc.Transport
 | |
| --- @field write fun(self: vim.lsp.rpc.Transport, msg: string)
 | |
| --- @field is_closing fun(self: vim.lsp.rpc.Transport): boolean
 | |
| --- @field terminate fun(self: vim.lsp.rpc.Transport)
 | |
| 
 | |
| --- @class (private,exact) vim.lsp.rpc.Transport.Run : vim.lsp.rpc.Transport
 | |
| --- @field new fun(): vim.lsp.rpc.Transport.Run
 | |
| --- @field sysobj? vim.SystemObj
 | |
| local TransportRun = {}
 | |
| 
 | |
| --- @return vim.lsp.rpc.Transport.Run
 | |
| function TransportRun.new()
 | |
|   return setmetatable({}, { __index = TransportRun })
 | |
| end
 | |
| 
 | |
| --- @param cmd string[] Command to start the LSP server.
 | |
| --- @param extra_spawn_params? vim.lsp.rpc.ExtraSpawnParams
 | |
| --- @param on_read fun(err: any, data: string)
 | |
| --- @param on_exit fun(code: integer, signal: integer)
 | |
| function TransportRun:run(cmd, extra_spawn_params, on_read, on_exit)
 | |
|   local function on_stderr(_, chunk)
 | |
|     if chunk then
 | |
|       log.error('rpc', cmd[1], 'stderr', chunk)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   extra_spawn_params = extra_spawn_params or {}
 | |
| 
 | |
|   if extra_spawn_params.cwd then
 | |
|     assert(is_dir(extra_spawn_params.cwd), 'cwd must be a directory')
 | |
|   end
 | |
| 
 | |
|   local detached = not is_win
 | |
|   if extra_spawn_params.detached ~= nil then
 | |
|     detached = extra_spawn_params.detached
 | |
|   end
 | |
| 
 | |
|   local ok, sysobj_or_err = pcall(vim.system, cmd, {
 | |
|     stdin = true,
 | |
|     stdout = on_read,
 | |
|     stderr = on_stderr,
 | |
|     cwd = extra_spawn_params.cwd,
 | |
|     env = extra_spawn_params.env,
 | |
|     detach = detached,
 | |
|   }, function(obj)
 | |
|     on_exit(obj.code, obj.signal)
 | |
|   end)
 | |
| 
 | |
|   if not ok then
 | |
|     local err = sysobj_or_err --[[@as string]]
 | |
|     local sfx = err:match('ENOENT')
 | |
|         and '. The language server is either not installed, missing from PATH, or not executable.'
 | |
|       or string.format(' with error message: %s', err)
 | |
| 
 | |
|     error(('Spawning language server with cmd: `%s` failed%s'):format(vim.inspect(cmd), sfx))
 | |
|   end
 | |
| 
 | |
|   self.sysobj = sysobj_or_err --[[@as vim.SystemObj]]
 | |
| end
 | |
| 
 | |
| function TransportRun:write(msg)
 | |
|   assert(self.sysobj):write(msg)
 | |
| end
 | |
| 
 | |
| function TransportRun:is_closing()
 | |
|   return self.sysobj == nil or self.sysobj:is_closing()
 | |
| end
 | |
| 
 | |
| function TransportRun:terminate()
 | |
|   assert(self.sysobj):kill(15)
 | |
| end
 | |
| 
 | |
| --- @class (private,exact) vim.lsp.rpc.Transport.Connect : vim.lsp.rpc.Transport
 | |
| --- @field new fun(): vim.lsp.rpc.Transport.Connect
 | |
| --- @field handle? uv.uv_pipe_t|uv.uv_tcp_t
 | |
| --- Connect returns a PublicClient synchronously so the caller
 | |
| --- can immediately send messages before the connection is established
 | |
| --- -> Need to buffer them until that happens
 | |
| --- @field connected boolean
 | |
| --- @field closing boolean
 | |
| --- @field msgbuf vim.Ringbuf
 | |
| --- @field on_exit? fun(code: integer, signal: integer)
 | |
| local TransportConnect = {}
 | |
| 
 | |
| --- @return vim.lsp.rpc.Transport.Connect
 | |
| function TransportConnect.new()
 | |
|   return setmetatable({
 | |
|     connected = false,
 | |
|     -- size should be enough because the client can't really do anything until initialization is done
 | |
|     -- which required a response from the server - implying the connection got established
 | |
|     msgbuf = vim.ringbuf(10),
 | |
|     closing = false,
 | |
|   }, { __index = TransportConnect })
 | |
| end
 | |
| 
 | |
| --- @param host_or_path string
 | |
| --- @param port? integer
 | |
| --- @param on_read fun(err: any, data: string)
 | |
| --- @param on_exit? fun(code: integer, signal: integer)
 | |
| function TransportConnect:connect(host_or_path, port, on_read, on_exit)
 | |
|   self.on_exit = on_exit
 | |
|   self.handle = (
 | |
|     port and assert(uv.new_tcp(), 'Could not create new TCP socket')
 | |
|     or assert(uv.new_pipe(false), 'Pipe could not be opened.')
 | |
|   )
 | |
| 
 | |
|   local function on_connect(err)
 | |
|     if err then
 | |
|       local address = not port and host_or_path or (host_or_path .. ':' .. port)
 | |
|       vim.schedule(function()
 | |
|         vim.notify(
 | |
|           string.format('Could not connect to %s, reason: %s', address, vim.inspect(err)),
 | |
|           vim.log.levels.WARN
 | |
|         )
 | |
|       end)
 | |
|       return
 | |
|     end
 | |
|     self.handle:read_start(on_read)
 | |
|     self.connected = true
 | |
|     for msg in self.msgbuf do
 | |
|       self.handle:write(msg)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   if not port then
 | |
|     self.handle:connect(host_or_path, on_connect)
 | |
|     return
 | |
|   end
 | |
| 
 | |
|   --- @diagnostic disable-next-line:param-type-mismatch bad UV typing
 | |
|   local info = uv.getaddrinfo(host_or_path, nil)
 | |
|   local resolved_host = info and info[1] and info[1].addr or host_or_path
 | |
|   self.handle:connect(resolved_host, port, on_connect)
 | |
| end
 | |
| 
 | |
| function TransportConnect:write(msg)
 | |
|   if self.connected then
 | |
|     local _, err = self.handle:write(msg)
 | |
|     if err and not self.closing then
 | |
|       log.error('Error on handle:write: %q', err)
 | |
|     end
 | |
|     return
 | |
|   end
 | |
| 
 | |
|   self.msgbuf:push(msg)
 | |
| end
 | |
| 
 | |
| function TransportConnect:is_closing()
 | |
|   return self.closing
 | |
| end
 | |
| 
 | |
| function TransportConnect:terminate()
 | |
|   if self.closing then
 | |
|     return
 | |
|   end
 | |
|   self.closing = true
 | |
|   if self.handle then
 | |
|     self.handle:shutdown()
 | |
|     self.handle:close()
 | |
|   end
 | |
|   if self.on_exit then
 | |
|     self.on_exit(0, 0)
 | |
|   end
 | |
| end
 | |
| 
 | |
| return {
 | |
|   TransportRun = TransportRun,
 | |
|   TransportConnect = TransportConnect,
 | |
| }
 | 
