diff options
author | 最萌小汐 <sumneko@hotmail.com> | 2022-03-01 16:23:54 +0800 |
---|---|---|
committer | 最萌小汐 <sumneko@hotmail.com> | 2022-03-01 16:23:54 +0800 |
commit | 86e1e3b431687c62e29c6f5955992cd9bd77a387 (patch) | |
tree | 23a2f34647f7ef91eca31afb8426c435ba8ef9e8 /script | |
parent | c9841388c2db3875177eb48d656b33b86230d7de (diff) | |
download | lua-language-server-86e1e3b431687c62e29c6f5955992cd9bd77a387.zip |
cleanup
Diffstat (limited to 'script')
-rw-r--r-- | script/lclient.lua | 219 |
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 |