mirror of
https://github.com/prabirshrestha/vim-lsp.git
synced 2025-12-14 20:35:59 +01:00
264 lines
7.0 KiB
Lua
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
|