summaryrefslogtreecommitdiff
path: root/server/src/service.lua
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/service.lua')
-rw-r--r--server/src/service.lua214
1 files changed, 200 insertions, 14 deletions
diff --git a/server/src/service.lua b/server/src/service.lua
index 40769a28..09662405 100644
--- a/server/src/service.lua
+++ b/server/src/service.lua
@@ -1,10 +1,197 @@
local subprocess = require 'bee.subprocess'
-local lsp = require 'lsp'
local Method = require 'method'
local fs = require 'bee.filesystem'
local thread = require 'thread'
+local json = require 'json'
+local parser = require 'parser'
+local matcher = require 'matcher'
-local function listen(self)
+
+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._method = nil
+mt._file = nil
+
+function mt:_callback(method, params)
+ local f = self._method
+ if f then
+ return f(method, params)
+ end
+ return nil, '没有注册method'
+end
+
+function mt:_send(data)
+ local f = self._output
+ if not f then
+ return
+ end
+ data.jsonrpc = '2.0'
+ local content = json.encode(data)
+ local buf = ('Content-Length: %d\r\n\r\n%s'):format(#content, content)
+ f(buf)
+end
+
+function mt:_doProto(proto)
+ local id = proto.id
+ local method = proto.method
+ local params = proto.params
+ local response, err = self:_callback(method, params)
+ if id then
+ if response then
+ self:_send {
+ id = id,
+ result = response,
+ }
+ else
+ self:_send {
+ id = id,
+ error = {
+ code = ErrorCodes.UnknownErrorCode,
+ message = err or ('没有回应:' .. method),
+ },
+ }
+ end
+ end
+ if response == nil then
+ log.error(err or ('没有回应:' .. method))
+ end
+end
+
+function mt:_buildTextCache()
+ if not next(self._need_compile) then
+ return
+ end
+ local list = {}
+ for uri in pairs(self._need_compile) do
+ list[#list+1] = uri
+ end
+
+ local size = 0
+ local clock = os.clock()
+ for _, uri in ipairs(list) do
+ local obj = self:compileText(uri)
+ size = size + #obj.text
+ end
+ local passed = os.clock() - clock
+ log.debug(('\n\z
+ 语法树缓存完成\n\z
+ 耗时:[%.3f]秒\n\z
+ 数量:[%d]\n\z
+ 总大小:[%.3f]kb\n\z
+ 速度:[%.3f]kb/s\n\z
+ 内存:[%.3f]kb'):format(
+ passed,
+ #list,
+ size / 1000,
+ size / passed / 1000,
+ collectgarbage 'count'
+ ))
+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:saveText(uri, version, text)
+ local obj = self._file[uri]
+ if obj then
+ if obj.version >= version then
+ return
+ end
+ obj.version = version
+ obj.text = text
+ self._need_compile[uri] = true
+ else
+ self._file[uri] = {
+ version = version,
+ text = text,
+ }
+ self._need_compile[uri] = true
+ end
+end
+
+function mt:loadText(uri)
+ local obj = self._file[uri]
+ if not obj then
+ return nil
+ end
+ self:compileText(uri)
+ return obj.results, obj.lines
+end
+
+function mt:compileText(uri)
+ local obj = self._file[uri]
+ if not obj then
+ return nil
+ end
+ if not self._need_compile[uri] then
+ return nil
+ end
+ self._need_compile[uri] = nil
+ local ast = parser:ast(obj.text)
+ obj.results = matcher.compile(ast)
+ obj.lines = parser:lines(obj.text)
+ return obj
+end
+
+function mt:removeText(uri)
+ self._file[uri] = nil
+ self._need_compile[uri] = nil
+end
+
+function mt:setMethod(method)
+ self._method = method
+end
+
+function mt:runStep()
+ local proto = self._input()
+ if proto then
+ -- 协议内容读取成功后重置
+ self._header = nil
+ self._idle_clock = os.clock()
+ self:_doProto(proto)
+ return
+ end
+ if os.clock() - self._idle_clock >= 1 then
+ self:_buildTextCache()
+ end
+end
+
+function mt:stop()
+ self._input = nil
+ self._output = nil
+end
+
+function mt:listen()
subprocess.filemode(io.stdin, 'b')
subprocess.filemode(io.stdout, 'b')
io.stdin:setvbuf 'no'
@@ -12,14 +199,13 @@ local function listen(self)
thread.require 'proto'
- local session = lsp()
- session:setInput(function ()
+ self:setInput(function ()
return thread.proto()
end)
- session:setOutput(function (buf)
+ self:setOutput(function (buf)
io.write(buf)
end)
- session:setMethod(function (method, params)
+ self:setMethod(function (method, params)
local optional
if method:sub(1, 2) == '$/' then
method = method:sub(3)
@@ -27,7 +213,7 @@ local function listen(self)
end
local f = Method[method]
if f then
- local suc, res, res2 = pcall(f, session, params)
+ local suc, res, res2 = pcall(f, self, params)
if suc then
return res, res2
else
@@ -43,17 +229,17 @@ local function listen(self)
while true do
thread.on_tick()
- session:runStep()
+ self:runStep()
thread.sleep(0.001)
end
end
-local mt = {
- listen = listen,
-}
-mt.__index = mt
-
return function ()
- local session = setmetatable({}, mt)
+ local session = setmetatable({
+ _file = {},
+ _need_compile = {},
+ _header = nil,
+ _idle_clock = os.clock(),
+ }, mt)
return session
end