Files
vim-lsp-mirror/lua/lsp/client.lua
Prabir Shrestha 7ecda5fa61 lsp lua client
2021-03-08 02:32:15 +00:00

264 lines
7.0 KiB
Lua

local C = require 'lsp/callbag'
local inspect = require 'lsp/inspect'
local M = {}
local send_type_request = 1
local send_type_notification = 2
local send_type_response = 3
local vimeval
local vimcmd
if vim.api ~= nil then
vimcmd = vim.api.nvim_command
vimeval = vim.api.nvim_eval
uv = vim.loop
else
vimcmd = vim.command
vimeval = vim.eval
end
local function json_encode(obj)
return vim.fn.json_encode(obj)
end
local function json_decode(str)
return vim.fn.json_decode(str)
end
local function log(...)
local args = {...}
vim.fn.call('lsp#log', args)
end
local client_index = 0
local function new_client_id()
client_index = client_index + 1
return client_index
end
-- {
-- dispose = function() end,
-- buffer = ''
-- client_id,
-- request_sequence = 0,
-- pid = 123
-- stdin = C.makeSubject()
-- requests = {}
-- on_notifications = {}
-- }
local clients = {}
local function on_ready(x)
x.data.state.pid = x.data.pid
end
local function get_content_length(header_string)
local headers = {}
for k, v in header_string:gmatch("([%a%d%-]+): ([%g ]+)\r\n") do
if k == nil then error("Unparseable Header") end
headers[k:lower()] = v
end
return tonumber(headers['content-length'])
end
local function on_stdout(x)
local state = x.state
state.buffer = state.buffer .. x.data
while true do
if state.content_length < 0 then
local header_end_index = state.buffer:find('\r\n\r\n', 1, true)
if header_end_index < 0 then
return
end
local headers = state.buffer:sub(1, header_end_index - 1)
state.content_length = get_content_length(headers .. '\r\n')
if state.content_length < 0 then
-- invalid content length
M.stop(state.client_id)
return
end
state.buffer = state.buffer:sub(header_end_index + 4)
end
if #state.buffer < state.content_length then
-- incomplete message, wait for next buffer to arrive
return
end
-- we have full message
local response_str = state.buffer:sub(1, state.content_length)
state.content_length = -1
-- TODO: try..catch json_decode
local response = json_decode(response_str)
state.buffer = state.buffer:sub(#response_str + 1)
if response then
-- call appropriate callbacks
local on_notification_data = { response = response }
local request
if response.method and response.id then
-- it is a request from a server
request = response
if state.options.on_request then
state.options.on_request(state.client_id, request)
end
elseif response.id then
-- it is a request->response
if state.requests[response.id] then
on_notification_data['request'] = state.requests[response.id]
end
if state.options.on_notification then
-- call client's on_notification first
-- TODO: try..catch
log('call')
state.options.on_notification(state.client_id, on_notification_data, 'on_notification')
end
if state.on_notifications[response.id] then
log('call2')
-- TODO: try..catch
state.on_notifications[response.id](state.client_id, on_notification_data, 'on_notification')
state.on_notifications[response.id] = nil
end
log('call3')
if state.requests[response.id] then
state.requests[response.id] = nil
end
else
-- it is a notification
if state.options.on_notification then
state.options.on_notification(state.client_id, on_notification_data, 'on_notification')
end
end
end
if state.buffer == "" then
-- buffer is empty, wait for next message to arrive
return
end
end
end
if vim.api then on_stdout = vim.schedule_wrap(on_stdout) end
local function on_stderr(x)
-- log('stderr')
end
local function on_exit(x)
log('exit')
end
local spawn_on_next = {
ready = on_ready,
stdout = on_stdout,
stderr = on_stderr,
exit = on_exit,
}
function M.start(options)
for k,v in pairs(options) do
log(k)
end
local state = {
buffer = '',
client_id = new_client_id(),
content_length = -1,
request_sequence = 0,
requests = {},
on_notifications = {},
options = options,
stdin = C.makeSubject()
}
clients[state.client_id] = state
if options['tcp'] then
-- TODO: TCP not supported
return -1
end
local dispose = C.pipe(
C.spawn(options['cmd'], { ready = true, stdin = state['stdin'], stderr = true, stdout = true, state = state }),
C.subscribe({
next = function(x) spawn_on_next[x['event']](x) end,
error = function (e) print(inspect(e)) end,
complete = function () end
})
)
state['dispose'] = dispose
return state.client_id
end
function M.stop(id)
local state = clients[id]
if state then
if state.dispose then
state.dispose()
state.dispose = nil
end
clients[id] = nil
end
end
function M.send(id, options, typ)
local state = clients[id]
if not state then return -1 end
local request = { jsonrpc = '2.0' }
if typ == send_type_request then
state.request_sequence = state.request_sequence + 1
request.id = state.request_sequence
state.requests[request.id] = request
if options.on_notification then
state.on_notifications[option.request.id] = options['on_notification']
end
end
if options.id then request.id = options.id end
if options.method then request.method = options.method end
if options.params then request.params = options.params end
if options.result then request.result = options.result end
if options.error then request.error = options.error end
local json = json_encode(request)
local payload = 'Content-Length: ' .. #json .. '\r\n\r\n' .. json
state.stdin(1, payload)
if typ == send_type_request then
local id = request.id
local sync = options.sync or 0
if sync ~= 0 then
-- TODO: implement wait
end
return id
else
return 0
end
end
function M.send_notification(id, options)
end
function M.send_response(id, options)
end
function M.get_last_request_id(id)
return clients[id].request_sequence
end
function M.is_error(obj_or_response)
return false
end
function M.error_message(obj_or_response)
end
function M.is_server_instantiated_notification(notification)
end
return M