diff options
author | 最萌小汐 <sumneko@hotmail.com> | 2020-11-20 21:57:09 +0800 |
---|---|---|
committer | 最萌小汐 <sumneko@hotmail.com> | 2020-11-20 21:57:09 +0800 |
commit | 4ca61ec457822dd14966afa0752340ae8ce180a1 (patch) | |
tree | ae8adb1ad82c717868e551e699fd3cf3bb290089 /script/proto | |
parent | c63b2e404d8d2bb984afe3678a5ba2b2836380cc (diff) | |
download | lua-language-server-4ca61ec457822dd14966afa0752340ae8ce180a1.zip |
no longer beta
Diffstat (limited to 'script/proto')
-rw-r--r-- | script/proto/define.lua | 287 | ||||
-rw-r--r-- | script/proto/init.lua | 3 | ||||
-rw-r--r-- | script/proto/proto.lua | 147 |
3 files changed, 437 insertions, 0 deletions
diff --git a/script/proto/define.lua b/script/proto/define.lua new file mode 100644 index 00000000..966a5161 --- /dev/null +++ b/script/proto/define.lua @@ -0,0 +1,287 @@ +local guide = require 'parser.guide' +local util = require 'utility' + +local m = {} + +--- 获取 position 对应的光标位置 +---@param lines table +---@param text string +---@param position position +---@return integer +function m.offset(lines, text, position) + local row = position.line + 1 + local start = guide.lineRange(lines, row) + if start <= 0 or start > #text then + return #text + 1 + end + local offset = utf8.offset(text, position.character + 1, start) + return offset - 1 +end + +--- 获取 position 对应的光标位置(根据附近的单词) +---@param lines table +---@param text string +---@param position position +---@return integer +function m.offsetOfWord(lines, text, position) + local row = position.line + 1 + local start = guide.lineRange(lines, row) + if start <= 0 or start > #text then + return #text + 1 + end + local offset = utf8.offset(text, position.character + 1, start) + if offset > #text + or text:sub(offset-1, offset):match '[%w_][^%w_]' then + offset = offset - 1 + end + return offset +end + +--- 将光标位置转化为 position +---@alias position table +---@param lines table +---@param text string +---@param offset integer +---@return position +function m.position(lines, text, offset) + local row, col = guide.positionOf(lines, offset) + local start = guide.lineRange(lines, row) + if start < 1 then + start = 1 + end + local ucol = util.utf8Len(text, start, start + col - 1) + if row < 1 then + row = 1 + end + return { + line = row - 1, + character = ucol, + } +end + +--- 将起点与终点位置转化为 range +---@alias range table +---@param lines table +---@param text string +---@param offset1 integer +---@param offset2 integer +function m.range(lines, text, offset1, offset2) + local range = { + start = m.position(lines, text, offset1), + ['end'] = m.position(lines, text, offset2), + } + if range.start.character > 0 then + range.start.character = range.start.character - 1 + end + return range +end + +---@alias location table +---@param uri string +---@param range range +---@return location +function m.location(uri, range) + return { + uri = uri, + range = range, + } +end + +---@alias locationLink table +---@param uri string +---@param range range +---@param selection range +---@param origin range +function m.locationLink(uri, range, selection, origin) + return { + targetUri = uri, + targetRange = range, + targetSelectionRange = selection, + originSelectionRange = origin, + } +end + +function m.textEdit(range, newtext) + return { + range = range, + newText = newtext, + } +end + +--- 诊断等级 +m.DiagnosticSeverity = { + Error = 1, + Warning = 2, + Information = 3, + Hint = 4, +} + +--- 诊断类型与默认等级 +m.DiagnosticDefaultSeverity = { + ['unused-local'] = 'Hint', + ['unused-function'] = 'Hint', + ['undefined-global'] = 'Warning', + ['global-in-nil-env'] = 'Warning', + ['unused-label'] = 'Hint', + ['unused-vararg'] = 'Hint', + ['trailing-space'] = 'Hint', + ['redefined-local'] = 'Hint', + ['newline-call'] = 'Information', + ['newfield-call'] = 'Warning', + ['redundant-parameter'] = 'Hint', + ['ambiguity-1'] = 'Warning', + ['lowercase-global'] = 'Information', + ['undefined-env-child'] = 'Information', + ['duplicate-index'] = 'Warning', + ['empty-block'] = 'Hint', + ['redundant-value'] = 'Hint', + ['code-after-break'] = 'Hint', + + ['duplicate-doc-class'] = 'Warning', + ['undefined-doc-class'] = 'Warning', + ['undefined-doc-name'] = 'Warning', + ['circle-doc-class'] = 'Warning', + ['undefined-doc-param'] = 'Warning', + ['duplicate-doc-param'] = 'Warning', + ['doc-field-no-class'] = 'Warning', + ['duplicate-doc-field'] = 'Warning', +} + +--- 诊断报告标签 +m.DiagnosticTag = { + Unnecessary = 1, + Deprecated = 2, +} + +m.DocumentHighlightKind = { + Text = 1, + Read = 2, + Write = 3, +} + +m.MessageType = { + Error = 1, + Warning = 2, + Info = 3, + Log = 4, +} + +m.FileChangeType = { + Created = 1, + Changed = 2, + Deleted = 3, +} + +m.CompletionItemKind = { + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25, +} + +m.DiagnosticSeverity = { + Error = 1, + Warning = 2, + Information = 3, + Hint = 4, +} + +m.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, +} + +m.SymbolKind = { + File = 1, + Module = 2, + Namespace = 3, + Package = 4, + Class = 5, + Method = 6, + Property = 7, + Field = 8, + Constructor = 9, + Enum = 10, + Interface = 11, + Function = 12, + Variable = 13, + Constant = 14, + String = 15, + Number = 16, + Boolean = 17, + Array = 18, + Object = 19, + Key = 20, + Null = 21, + EnumMember = 22, + Struct = 23, + Event = 24, + Operator = 25, + TypeParameter = 26, +} + +m.TokenModifiers = { + ["declaration"] = 1 << 0, + ["documentation"] = 1 << 1, + ["static"] = 1 << 2, + ["abstract"] = 1 << 3, + ["deprecated"] = 1 << 4, + ["readonly"] = 1 << 5, +} + +m.TokenTypes = { + ["comment"] = 0, + ["keyword"] = 1, + ["number"] = 2, + ["regexp"] = 3, + ["operator"] = 4, + ["namespace"] = 5, + ["type"] = 6, + ["struct"] = 7, + ["class"] = 8, + ["interface"] = 9, + ["enum"] = 10, + ["typeParameter"] = 11, + ["function"] = 12, + ["member"] = 13, + ["macro"] = 14, + ["variable"] = 15, + ["parameter"] = 16, + ["property"] = 17, + ["label"] = 18, +} + + +return m diff --git a/script/proto/init.lua b/script/proto/init.lua new file mode 100644 index 00000000..33e637f6 --- /dev/null +++ b/script/proto/init.lua @@ -0,0 +1,3 @@ +local proto = require 'proto.proto' + +return proto diff --git a/script/proto/proto.lua b/script/proto/proto.lua new file mode 100644 index 00000000..d8538d8f --- /dev/null +++ b/script/proto/proto.lua @@ -0,0 +1,147 @@ +local subprocess = require 'bee.subprocess' +local util = require 'utility' +local await = require 'await' +local pub = require 'pub' +local jsonrpc = require 'jsonrpc' +local define = require 'proto.define' +local timer = require 'timer' +local json = require 'json' + +local reqCounter = util.counter() + +local m = {} + +m.ability = {} +m.waiting = {} +m.holdon = {} + +function m.getMethodName(proto) + if proto.method:sub(1, 2) == '$/' then + return proto.method:sub(3), true + else + return proto.method, false + end +end + +function m.on(method, callback) + m.ability[method] = callback +end + +function m.response(id, res) + if id == nil then + log.error('Response id is nil!', util.dump(res)) + return + end + assert(m.holdon[id]) + m.holdon[id] = nil + local data = {} + data.id = id + data.result = res == nil and json.null or res + local buf = jsonrpc.encode(data) + --log.debug('Response', id, #buf) + io.stdout:write(buf) +end + +function m.responseErr(id, code, message) + if id == nil then + log.error('Response id is nil!', util.dump(message)) + return + end + local buf = jsonrpc.encode { + id = id, + error = { + code = code, + message = message, + } + } + --log.debug('ResponseErr', id, #buf) + io.stdout:write(buf) +end + +function m.notify(name, params) + local buf = jsonrpc.encode { + method = name, + params = params, + } + --log.debug('Notify', name, #buf) + io.stdout:write(buf) +end + +function m.awaitRequest(name, params) + local id = reqCounter() + local buf = jsonrpc.encode { + id = id, + method = name, + params = params, + } + --log.debug('Request', name, #buf) + io.stdout:write(buf) + return await.wait(function (waker) + m.waiting[id] = waker + end) +end + +function m.doMethod(proto) + local method, optional = m.getMethodName(proto) + local abil = m.ability[method] + if not abil then + if not optional then + log.warn('Recieved unknown proto: ' .. method) + end + if proto.id then + m.responseErr(proto.id, define.ErrorCodes.MethodNotFound, method) + end + return + end + if proto.id then + m.holdon[proto.id] = method + end + await.call(function () + --log.debug('Start method:', method) + local clock = os.clock() + local ok = true + local res + -- 任务可能在执行过程中被中断,通过close来捕获 + local response <close> = util.defer(function () + local passed = os.clock() - clock + if passed > 0.2 then + log.debug(('Method [%s] takes [%.3f]sec.'):format(method, passed)) + end + --log.debug('Finish method:', method) + if not proto.id then + return + end + if ok then + m.response(proto.id, res) + else + m.responseErr(proto.id, define.ErrorCodes.InternalError, res) + end + end) + ok, res = xpcall(abil, log.error, proto.params) + end) +end + +function m.doResponse(proto) + local id = proto.id + local waker = m.waiting[id] + if not waker then + log.warn('Response id not found: ' .. util.dump(proto)) + return + end + m.waiting[id] = nil + if proto.error then + log.warn(('Response error [%d]: %s'):format(proto.error.code, proto.error.message)) + return + end + waker(proto.result) +end + +function m.listen() + subprocess.filemode(io.stdin, 'b') + subprocess.filemode(io.stdout, 'b') + io.stdin:setvbuf 'no' + io.stdout:setvbuf 'no' + pub.task('loadProto') +end + +return m |