summaryrefslogtreecommitdiff
path: root/script
diff options
context:
space:
mode:
author最萌小汐 <sumneko@hotmail.com>2022-03-01 16:23:54 +0800
committer最萌小汐 <sumneko@hotmail.com>2022-03-01 16:23:54 +0800
commit86e1e3b431687c62e29c6f5955992cd9bd77a387 (patch)
tree23a2f34647f7ef91eca31afb8426c435ba8ef9e8 /script
parentc9841388c2db3875177eb48d656b33b86230d7de (diff)
downloadlua-language-server-86e1e3b431687c62e29c6f5955992cd9bd77a387.zip
cleanup
Diffstat (limited to 'script')
-rw-r--r--script/lclient.lua219
1 files changed, 219 insertions, 0 deletions
diff --git a/script/lclient.lua b/script/lclient.lua
new file mode 100644
index 00000000..8da8d138
--- /dev/null
+++ b/script/lclient.lua
@@ -0,0 +1,219 @@
+local gc = require 'gc'
+local util = require 'utility'
+local proto = require 'proto'
+local await = require 'await'
+local timer = require 'timer'
+local pub = require 'pub'
+local json = require 'json'
+
+require 'provider'
+
+local counter = util.counter()
+
+---@class languageClient
+---@field _outs table
+---@field _gc gc
+---@field _waiting table
+---@field _methods table
+local mt = {}
+mt.__index = mt
+
+function mt:__close()
+ self:remove()
+end
+
+function mt:_fakeProto()
+ proto.send = function (data)
+ self._outs[#self._outs+1] = data
+ end
+end
+
+function mt:_flushServer()
+ -- reset scopes
+ local ws = require 'workspace'
+ local scope = require 'workspace.scope'
+ local files = require 'files'
+ ws.reset()
+ scope.reset()
+ files.reset()
+end
+
+function mt:_localLoadFile()
+ local awaitTask = pub.awaitTask
+ ---@async
+ ---@param name string
+ ---@param params any
+ pub.awaitTask = function (name, params)
+ if name == 'loadFile' then
+ local path = params
+ return util.loadFile(path)
+ end
+ return awaitTask(name, params)
+ end
+ self:gc(function ()
+ pub.awaitTask = awaitTask
+ end)
+end
+
+---@async
+function mt:initialize(params)
+ self:awaitRequest('initialize', params or {})
+ self:notify('initialized')
+end
+
+function mt:reportHangs()
+ local hangs = {}
+ hangs[#hangs+1] = ('====== C -> S ======')
+ for _, waiting in util.sortPairs(self._waiting) do
+ hangs[#hangs+1] = ('%03d %s'):format(waiting.id, waiting.method)
+ end
+ hangs[#hangs+1] = ('====== S -> C ======')
+ for _, waiting in util.sortPairs(proto.waiting) do
+ hangs[#hangs+1] = ('%03d %s'):format(waiting.id, waiting.method)
+ end
+ hangs[#hangs+1] = ('====================')
+ return table.concat(hangs, '\n')
+end
+
+---@param callback async fun(client: languageClient)
+function mt:start(callback)
+ self:_fakeProto()
+ self:_flushServer()
+ self:_localLoadFile()
+
+ local finished = false
+
+ await.setErrorHandle(function (...)
+ local msg = log.error(...)
+ error(msg)
+ end)
+
+ ---@async
+ await.call(function ()
+ callback(self)
+ finished = true
+ end)
+
+ local jumpedTime = 0
+
+ while true do
+ if finished then
+ break
+ end
+ if await.step() then
+ goto CONTINUE
+ end
+ timer.update()
+ if await.step() then
+ goto CONTINUE
+ end
+ if self:update() then
+ goto CONTINUE
+ end
+ timer.timeJump(1.0)
+ jumpedTime = jumpedTime + 1.0
+ if jumpedTime > 2 * 60 * 60 then
+ error('two hours later ...\n' .. self:reportHangs())
+ end
+ ::CONTINUE::
+ end
+
+ self:remove()
+end
+
+function mt:gc(obj)
+ return self._gc:add(obj)
+end
+
+function mt:remove()
+ self._gc:remove()
+end
+
+function mt:notify(method, params)
+ proto.doMethod {
+ method = method,
+ params = params,
+ }
+end
+
+function mt:request(method, params, callback)
+ local id = counter()
+ self._waiting[id] = {
+ id = id,
+ params = params,
+ callback = callback,
+ }
+ proto.doMethod {
+ id = id,
+ method = method,
+ params = params,
+ }
+end
+
+---@async
+function mt:awaitRequest(method, params)
+ return await.wait(function (waker)
+ self:request(method, params, function (result)
+ if result == json.null then
+ result = nil
+ end
+ waker(result)
+ end)
+ end)
+end
+
+function mt:update()
+ local outs = self._outs
+ if #outs == 0 then
+ return false
+ end
+ self._outs = {}
+ for _, out in ipairs(outs) do
+ if out.method then
+ local callback = self._methods[out.method]
+ if callback then
+ proto.doResponse {
+ id = out.id,
+ result = callback(out.params),
+ }
+ elseif out.method:sub(1, 2) ~= '$/' then
+ error('Unknown method: ' .. out.method)
+ end
+ else
+ local callback = self._waiting[out.id].callback
+ self._waiting[out.id] = nil
+ callback(out.result, out.error)
+ end
+ end
+ return true
+end
+
+function mt:register(method, callback)
+ self._methods[method] = callback
+end
+
+function mt:registerFakers()
+ for _, method in ipairs {
+ 'textDocument/publishDiagnostics',
+ 'workspace/configuration',
+ 'workspace/semanticTokens/refresh',
+ 'window/workDoneProgress/create',
+ 'window/showMessage',
+ 'window/logMessage',
+ } do
+ self:register(method, function ()
+ return nil
+ end)
+ end
+end
+
+---@return languageClient
+return function ()
+ local self = setmetatable({
+ _gc = gc(),
+ _outs = {},
+ _waiting = {},
+ _methods = {},
+ }, mt)
+ return self
+end