summaryrefslogtreecommitdiff
path: root/script/proto
diff options
context:
space:
mode:
author最萌小汐 <sumneko@hotmail.com>2020-11-20 21:57:09 +0800
committer最萌小汐 <sumneko@hotmail.com>2020-11-20 21:57:09 +0800
commit4ca61ec457822dd14966afa0752340ae8ce180a1 (patch)
treeae8adb1ad82c717868e551e699fd3cf3bb290089 /script/proto
parentc63b2e404d8d2bb984afe3678a5ba2b2836380cc (diff)
downloadlua-language-server-4ca61ec457822dd14966afa0752340ae8ce180a1.zip
no longer beta
Diffstat (limited to 'script/proto')
-rw-r--r--script/proto/define.lua287
-rw-r--r--script/proto/init.lua3
-rw-r--r--script/proto/proto.lua147
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