diff options
-rw-r--r-- | src/lsp/init.lua | 122 | ||||
-rw-r--r-- | src/lsp/method.lua | 8 | ||||
-rw-r--r-- | src/service/init.lua | 29 | ||||
-rw-r--r-- | src/utility/init.lua | 2 | ||||
-rw-r--r-- | src/utility/io.lua (renamed from src/utility.lua) | 0 | ||||
-rw-r--r-- | src/utility/table.lua | 62 |
6 files changed, 220 insertions, 3 deletions
diff --git a/src/lsp/init.lua b/src/lsp/init.lua new file mode 100644 index 00000000..5af4d876 --- /dev/null +++ b/src/lsp/init.lua @@ -0,0 +1,122 @@ +local json = require 'json' +local Method = require 'lsp.method' + +local ErrorCodes = { + -- Defined by JSON RPC + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + serverErrorStart = -32099, + serverErrorEnd = -32000, + ServerNotInitialized = -32002, + UnknownErrorCode = -32001, + + -- Defined by the protocol. + RequestCancelled = -32800, +} + +local mt = {} +mt.__index = mt + +mt._input = nil +mt._output = nil +mt._inited = false +mt._method = nil + +function mt:_callback(method, params) + local f = self._method + if f then + return f(method, params) + end + return nil +end + +function mt:_send(data) + local f = self._output + if not f then + return + end + local content = json.encode(data) + local buf = ('Content-Length: %d\r\n\r\n%s'):format(#content + 2, content) + f(buf) +end + +function mt:_readAsContent(header) + local len = tonumber(header:match('%d+')) + if not len then + log.error('错误的协议头:', header) + return + end + local buf = self:read(len) + local suc, res = pcall(json.decode, buf) + if not suc then + log.error('错误的协议:', buf) + return + end + log.debug(table.dump(res)) + local id = res.id + local method = res.method + local params = res.params + local f = Method[method] + if not f then + return + end + local response, err = f(self, params) + if response then + self._send { + id = id, + result = response, + } + else + self._send { + id = id, + error = { + code = ErrorCodes.UnknownErrorCode, + message = err, + }, + } + end +end + +function mt:setInput(input) + self._input = input +end + +function mt:setOutput(output) + self._output = output +end + +function mt:read(mode) + if not self._input then + return nil + end + return self._input(mode) +end + +function mt:start(method) + self._method = method + while true do + local header = self:read 'L' + if not header then + return + end + if header:sub(1, #'Content-Length') == 'Content-Length' then + self:_readAsContent(header) + elseif header:sub(1, #'Content-Type') == 'Content-Type' then + else + log.error('错误的协议头:', header) + end + end + return true +end + +function mt:stop() + self._input = nil + self._output = nil +end + +return function () + return setmetatable({}, mt) +end diff --git a/src/lsp/method.lua b/src/lsp/method.lua new file mode 100644 index 00000000..64767f3b --- /dev/null +++ b/src/lsp/method.lua @@ -0,0 +1,8 @@ +local Method = {} + +function Method:initialize(params) + self._inited = true + return self:_callback('initialize', params) +end + +return Method diff --git a/src/service/init.lua b/src/service/init.lua index 53e6e4b3..5e54b5b3 100644 --- a/src/service/init.lua +++ b/src/service/init.lua @@ -1,5 +1,16 @@ local sleep = require 'ffi.sleep' local ext = require 'process.ext' +local lsp = require 'lsp' + +local Method = {} + +function Method.initialize() + return { + capabilities = { + definitionProvider = true, + } + } +end local function listen(self, input, output) if input then @@ -18,9 +29,21 @@ local function listen(self, input, output) io.stdout:setvbuf 'no' end - for line in io.input():lines 'L' do - log.debug('标准输入:\n', line) - end + local session = lsp() + session:setInput(function (mode) + return io.read(mode) + end) + session:setOutput(function (buf) + io.write(buf) + log.debug(buf) + end) + session:start(function (method, params) + local f = Method[method] + if f then + return f(params) + end + return nil + end) end local mt = { diff --git a/src/utility/init.lua b/src/utility/init.lua new file mode 100644 index 00000000..55bd6b0d --- /dev/null +++ b/src/utility/init.lua @@ -0,0 +1,2 @@ +require 'utility.io' +require 'utility.table' diff --git a/src/utility.lua b/src/utility/io.lua index 73c65237..73c65237 100644 --- a/src/utility.lua +++ b/src/utility/io.lua diff --git a/src/utility/table.lua b/src/utility/table.lua new file mode 100644 index 00000000..37a74632 --- /dev/null +++ b/src/utility/table.lua @@ -0,0 +1,62 @@ +local table_sort = table.sort +local string_rep = string.rep +local type = type +local pairs = pairs +local ipairs = ipairs +local math_type = math.type + +local TAB = setmetatable({}, { __index = function (self, n) + self[n] = string_rep('\t', n) + return self[n] +end}) + +local KEY = {} + +function table.dump(tbl) + if type(tbl) ~= 'table' then + error('必须是表') + end + local table_mark = {} + local lines = {} + lines[#lines+1] = '{' + local function unpack(tbl, tab) + if table_mark[tbl] then + error('不能循环引用') + end + table_mark[tbl] = true + local keys = {} + for key in pairs(tbl) do + if type(key) == 'string' then + if key:find('[^%w_]') then + KEY[key] = ('[%q]'):format(key) + else + KEY[key] = key + end + elseif math_type(key) == 'integer' then + KEY[key] = ('[%d]'):format(key) + else + error('必须使用字符串或整数作为键') + end + keys[#keys+1] = key + end + table_sort(keys, function (a, b) + return KEY[a] < KEY[b] + end) + for _, key in ipairs(keys) do + local value = tbl[key] + local tp = type(value) + if tp == 'table' then + lines[#lines+1] = ('%s%s = {'):format(TAB[tab+1], KEY[key]) + unpack(value, tab+1) + lines[#lines+1] = ('%s},'):format(TAB[tab+1]) + elseif tp == 'string' or tp == 'number' or tp == 'boolean' then + lines[#lines+1] = ('%s%s = %q,'):format(TAB[tab+1], KEY[key], value) + else + error(('不支持的值类型[%s]'):format(tp)) + end + end + end + unpack(tbl, 0) + lines[#lines+1] = '}' + return table.concat(lines, '\n') +end |