add cache and rename some files
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
local guide = require 'parser.guide'
|
||||
local files = require 'files'
|
||||
local encoder = require 'encoder'
|
||||
|
||||
local offsetEncoding = 'utf16'
|
||||
|
||||
---@class converter
|
||||
local m = {}
|
||||
|
||||
---@alias position {line: integer, character: integer}
|
||||
|
||||
---@param row integer
|
||||
---@param col integer
|
||||
---@return position
|
||||
function m.position(row, col)
|
||||
return {
|
||||
line = row,
|
||||
character = col,
|
||||
}
|
||||
end
|
||||
|
||||
---@param state parser.state
|
||||
---@param pos integer
|
||||
---@return position
|
||||
local function rawPackPosition(state, pos)
|
||||
local row, col = guide.rowColOf(pos)
|
||||
if col > 0 then
|
||||
local text = state.lua
|
||||
if state and text then
|
||||
local lineOffset = state.lines[row]
|
||||
if lineOffset then
|
||||
local start = lineOffset
|
||||
local finish = lineOffset + col - 1
|
||||
if start <= #text and finish <= #text then
|
||||
col = encoder.len(offsetEncoding, text, lineOffset, lineOffset + col - 1)
|
||||
end
|
||||
else
|
||||
col = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
return {
|
||||
line = row,
|
||||
character = col,
|
||||
}
|
||||
end
|
||||
|
||||
---@param state parser.state
|
||||
---@param pos integer
|
||||
---@return position
|
||||
local function diffedPackPosition(state, pos)
|
||||
local offset = guide.positionToOffset(state, pos)
|
||||
local originOffset = files.diffedOffsetBack(state, offset)
|
||||
local originPos = guide.offsetToPositionByLines(state.originLines, originOffset)
|
||||
local row, col = guide.rowColOf(originPos)
|
||||
if col > 0 then
|
||||
local text = state.originText
|
||||
if text then
|
||||
local lineOffset = state.originLines[row]
|
||||
local finalOffset = math.min(lineOffset + col - 1, #text + 1)
|
||||
col = encoder.len(offsetEncoding, text, lineOffset, finalOffset)
|
||||
end
|
||||
end
|
||||
return {
|
||||
line = row,
|
||||
character = col,
|
||||
}
|
||||
end
|
||||
|
||||
---@param state parser.state
|
||||
---@param pos integer
|
||||
---@return position
|
||||
function m.packPosition(state, pos)
|
||||
if files.hasDiffed(state) then
|
||||
return diffedPackPosition(state, pos)
|
||||
else
|
||||
return rawPackPosition(state, pos)
|
||||
end
|
||||
end
|
||||
|
||||
---@param state parser.state
|
||||
---@param position position
|
||||
---@return integer
|
||||
local function rawUnpackPosition(state, position)
|
||||
local row, col = position.line, position.character
|
||||
if col > 0 then
|
||||
local text = state.lua
|
||||
if state and text then
|
||||
local lineOffset = state.lines[row]
|
||||
local textOffset = encoder.offset(offsetEncoding, text, col + 1, lineOffset)
|
||||
if textOffset and lineOffset then
|
||||
col = textOffset - lineOffset
|
||||
end
|
||||
end
|
||||
end
|
||||
local pos = guide.positionOf(row, col)
|
||||
return pos
|
||||
end
|
||||
|
||||
---@param state parser.state
|
||||
---@param position position
|
||||
---@return integer
|
||||
local function diffedUnpackPosition(state, position)
|
||||
local row, col = position.line, position.character
|
||||
if col > 0 then
|
||||
local lineOffset = state.originLines[row]
|
||||
if lineOffset then
|
||||
local textOffset = encoder.offset(offsetEncoding, state.originText, col + 1, lineOffset)
|
||||
if textOffset and lineOffset then
|
||||
col = textOffset - lineOffset
|
||||
end
|
||||
end
|
||||
end
|
||||
local originPos = guide.positionOf(row, col)
|
||||
local originOffset = guide.positionToOffsetByLines(state.originLines, originPos)
|
||||
local offset = files.diffedOffset(state, originOffset)
|
||||
local pos = guide.offsetToPosition(state, offset)
|
||||
return pos
|
||||
end
|
||||
|
||||
---@param state parser.state
|
||||
---@param position position
|
||||
---@return integer
|
||||
function m.unpackPosition(state, position)
|
||||
if files.hasDiffed(state) then
|
||||
return diffedUnpackPosition(state, position)
|
||||
else
|
||||
return rawUnpackPosition(state, position)
|
||||
end
|
||||
end
|
||||
|
||||
---@alias range {start: position, end: position}
|
||||
|
||||
---@param state parser.state
|
||||
---@param start integer
|
||||
---@param finish integer
|
||||
---@return range
|
||||
function m.packRange(state, start, finish)
|
||||
local range = {
|
||||
start = m.packPosition(state, start),
|
||||
['end'] = m.packPosition(state, finish),
|
||||
}
|
||||
return range
|
||||
end
|
||||
|
||||
---@param start position
|
||||
---@param finish position
|
||||
---@return range
|
||||
function m.range(start, finish)
|
||||
return {
|
||||
start = start,
|
||||
['end'] = finish,
|
||||
}
|
||||
end
|
||||
|
||||
---@param state parser.state
|
||||
---@param range range
|
||||
---@return integer start
|
||||
---@return integer finish
|
||||
function m.unpackRange(state, range)
|
||||
local start = m.unpackPosition(state, range.start)
|
||||
local finish = m.unpackPosition(state, range['end'])
|
||||
return start, finish
|
||||
end
|
||||
|
||||
---@alias location {uri: uri, range: range}
|
||||
|
||||
---@param uri string
|
||||
---@param range range
|
||||
---@return location
|
||||
function m.location(uri, range)
|
||||
return {
|
||||
uri = uri,
|
||||
range = range,
|
||||
}
|
||||
end
|
||||
|
||||
---@alias locationLink {targetUri:uri, targetRange: range, targetSelectionRange: range, originSelectionRange: range}
|
||||
|
||||
---@param uri string
|
||||
---@param range range
|
||||
---@param selection range
|
||||
---@param origin range
|
||||
---@return locationLink
|
||||
function m.locationLink(uri, range, selection, origin)
|
||||
return {
|
||||
targetUri = uri,
|
||||
targetRange = range,
|
||||
targetSelectionRange = selection,
|
||||
originSelectionRange = origin,
|
||||
}
|
||||
end
|
||||
|
||||
---@alias textEdit {range: range, newText: string}
|
||||
|
||||
---@param range range
|
||||
---@param newtext string
|
||||
---@return textEdit
|
||||
function m.textEdit(range, newtext)
|
||||
return {
|
||||
range = range,
|
||||
newText = newtext,
|
||||
}
|
||||
end
|
||||
|
||||
function m.setOffsetEncoding(encoding)
|
||||
offsetEncoding = encoding:lower():gsub('%-', '')
|
||||
end
|
||||
|
||||
---@param s string
|
||||
---@param i? integer
|
||||
---@param j? integer
|
||||
---@return integer
|
||||
function m.len(s, i, j)
|
||||
return encoder.len(offsetEncoding, s, i, j)
|
||||
end
|
||||
|
||||
---@class proto.command
|
||||
---@field title string
|
||||
---@field command string
|
||||
---@field arguments any[]
|
||||
|
||||
---@param title string
|
||||
---@param command string
|
||||
---@param arguments any[]
|
||||
---@return proto.command
|
||||
function m.command(title, command, arguments)
|
||||
return {
|
||||
title = title,
|
||||
command = command,
|
||||
arguments = arguments,
|
||||
}
|
||||
end
|
||||
|
||||
return m
|
||||
@@ -0,0 +1,197 @@
|
||||
local diag = require 'proto.diagnostic'
|
||||
|
||||
local m = {}
|
||||
|
||||
--- 诊断等级
|
||||
m.DiagnosticSeverity = {
|
||||
Error = 1,
|
||||
Warning = 2,
|
||||
Information = 3,
|
||||
Hint = 4,
|
||||
}
|
||||
|
||||
m.DiagnosticFileStatus = {
|
||||
Any = 1,
|
||||
Opened = 2,
|
||||
None = 3,
|
||||
}
|
||||
|
||||
--- 诊断类型与默认等级
|
||||
m.DiagnosticDefaultSeverity = diag.getDefaultSeverity()
|
||||
|
||||
--- 诊断类型与需要的文件状态(可以控制只分析打开的文件、还是所有文件)
|
||||
m.DiagnosticDefaultNeededFileStatus = diag.getDefaultStatus()
|
||||
|
||||
m.DiagnosticDefaultGroupSeverity = diag.getGroupSeverity()
|
||||
|
||||
m.DiagnosticDefaultGroupFileStatus = diag.getGroupStatus()
|
||||
|
||||
--- 诊断报告标签
|
||||
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.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.
|
||||
ContentModified = -32801,
|
||||
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,
|
||||
["definition"] = 1 << 1,
|
||||
["readonly"] = 1 << 2,
|
||||
["static"] = 1 << 3,
|
||||
["deprecated"] = 1 << 4,
|
||||
["abstract"] = 1 << 5,
|
||||
["async"] = 1 << 6,
|
||||
["modification"] = 1 << 7,
|
||||
["documentation"] = 1 << 8,
|
||||
["defaultLibrary"] = 1 << 9,
|
||||
["global"] = 1 << 10,
|
||||
}
|
||||
|
||||
m.TokenTypes = {
|
||||
["namespace"] = 00,
|
||||
["type"] = 01,
|
||||
["class"] = 02,
|
||||
["enum"] = 03,
|
||||
["interface"] = 04,
|
||||
["struct"] = 05,
|
||||
["typeParameter"] = 06,
|
||||
["parameter"] = 07,
|
||||
["variable"] = 08,
|
||||
["property"] = 09,
|
||||
["enumMember"] = 10,
|
||||
["event"] = 11,
|
||||
["function"] = 12,
|
||||
["method"] = 13,
|
||||
["macro"] = 14,
|
||||
["keyword"] = 15,
|
||||
["modifier"] = 16,
|
||||
["comment"] = 17,
|
||||
["string"] = 18,
|
||||
["number"] = 19,
|
||||
["regexp"] = 20,
|
||||
["operator"] = 21,
|
||||
["decorator"] = 22,
|
||||
}
|
||||
|
||||
m.BuiltIn = {
|
||||
['basic'] = 'default',
|
||||
['bit'] = 'default',
|
||||
['bit32'] = 'default',
|
||||
['builtin'] = 'default',
|
||||
['coroutine'] = 'default',
|
||||
['debug'] = 'default',
|
||||
['ffi'] = 'default',
|
||||
['io'] = 'default',
|
||||
['jit'] = 'default',
|
||||
['jit.profile'] = 'default',
|
||||
['jit.util'] = 'default',
|
||||
['math'] = 'default',
|
||||
['os'] = 'default',
|
||||
['package'] = 'default',
|
||||
['string'] = 'default',
|
||||
['table'] = 'default',
|
||||
['table.new'] = 'default',
|
||||
['table.clear'] = 'default',
|
||||
['utf8'] = 'default',
|
||||
['string.buffer'] = 'default',
|
||||
}
|
||||
|
||||
m.InlayHintKind = {
|
||||
Other = 0,
|
||||
Type = 1,
|
||||
Parameter = 2,
|
||||
}
|
||||
|
||||
return m
|
||||
@@ -0,0 +1,306 @@
|
||||
local util = require 'utility'
|
||||
|
||||
---@class proto.diagnostic
|
||||
local m = {}
|
||||
|
||||
---@alias DiagnosticSeverity
|
||||
---| 'Hint'
|
||||
---| 'Information'
|
||||
---| 'Warning'
|
||||
---| 'Error'
|
||||
|
||||
---@alias DiagnosticNeededFileStatus
|
||||
---| 'Any'
|
||||
---| 'Opened'
|
||||
---| 'None'
|
||||
|
||||
---@class proto.diagnostic.info
|
||||
---@field severity DiagnosticSeverity
|
||||
---@field status DiagnosticNeededFileStatus
|
||||
---@field group string
|
||||
|
||||
m.diagnosticDatas = {}
|
||||
m.diagnosticGroups = {}
|
||||
|
||||
function m.register(names)
|
||||
---@param info proto.diagnostic.info
|
||||
return function (info)
|
||||
for _, name in ipairs(names) do
|
||||
m.diagnosticDatas[name] = {
|
||||
severity = info.severity,
|
||||
status = info.status,
|
||||
}
|
||||
if not m.diagnosticGroups[info.group] then
|
||||
m.diagnosticGroups[info.group] = {}
|
||||
end
|
||||
m.diagnosticGroups[info.group][name] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
m.register {
|
||||
'unused-local',
|
||||
'unused-function',
|
||||
'unused-label',
|
||||
'unused-vararg',
|
||||
'trailing-space',
|
||||
'redundant-return',
|
||||
'empty-block',
|
||||
'code-after-break',
|
||||
'unreachable-code',
|
||||
} {
|
||||
group = 'unused',
|
||||
severity = 'Hint',
|
||||
status = 'Opened',
|
||||
}
|
||||
|
||||
m.register {
|
||||
'redundant-value',
|
||||
'unbalanced-assignments',
|
||||
'redundant-parameter',
|
||||
'missing-parameter',
|
||||
'missing-return-value',
|
||||
'redundant-return-value',
|
||||
'missing-return',
|
||||
'missing-fields',
|
||||
} {
|
||||
group = 'unbalanced',
|
||||
severity = 'Warning',
|
||||
status = 'Any',
|
||||
}
|
||||
|
||||
m.register {
|
||||
'need-check-nil',
|
||||
'undefined-field',
|
||||
'cast-local-type',
|
||||
'assign-type-mismatch',
|
||||
'param-type-mismatch',
|
||||
'cast-type-mismatch',
|
||||
'return-type-mismatch',
|
||||
'inject-field',
|
||||
--'unnecessary-assert',
|
||||
} {
|
||||
group = 'type-check',
|
||||
severity = 'Warning',
|
||||
status = 'Opened',
|
||||
}
|
||||
|
||||
m.register {
|
||||
'duplicate-doc-alias',
|
||||
'undefined-doc-class',
|
||||
'undefined-doc-name',
|
||||
'circle-doc-class',
|
||||
'undefined-doc-param',
|
||||
'duplicate-doc-param',
|
||||
'doc-field-no-class',
|
||||
'duplicate-doc-field',
|
||||
'unknown-diag-code',
|
||||
'unknown-cast-variable',
|
||||
'unknown-operator',
|
||||
} {
|
||||
group = 'luadoc',
|
||||
severity = 'Warning',
|
||||
status = 'Any',
|
||||
}
|
||||
|
||||
m.register {
|
||||
'incomplete-signature-doc',
|
||||
'missing-global-doc',
|
||||
'missing-local-export-doc',
|
||||
} {
|
||||
group = 'luadoc',
|
||||
severity = 'Warning',
|
||||
status = 'None',
|
||||
}
|
||||
|
||||
m.register {
|
||||
'codestyle-check'
|
||||
} {
|
||||
group = 'codestyle',
|
||||
severity = 'Warning',
|
||||
status = 'None',
|
||||
}
|
||||
|
||||
m.register {
|
||||
'spell-check'
|
||||
} {
|
||||
group = 'codestyle',
|
||||
severity = 'Information',
|
||||
status = 'None',
|
||||
}
|
||||
|
||||
m.register {
|
||||
'name-style-check'
|
||||
} {
|
||||
group = 'codestyle',
|
||||
severity = 'Warning',
|
||||
status = 'None',
|
||||
}
|
||||
|
||||
m.register {
|
||||
'newline-call',
|
||||
'newfield-call',
|
||||
'ambiguity-1',
|
||||
'count-down-loop',
|
||||
'different-requires',
|
||||
} {
|
||||
group = 'ambiguity',
|
||||
severity = 'Warning',
|
||||
status = 'Any',
|
||||
}
|
||||
|
||||
m.register {
|
||||
'await-in-sync',
|
||||
'not-yieldable',
|
||||
} {
|
||||
group = 'await',
|
||||
severity = 'Warning',
|
||||
status = 'None',
|
||||
}
|
||||
|
||||
m.register {
|
||||
'no-unknown',
|
||||
} {
|
||||
group = 'strong',
|
||||
severity = 'Warning',
|
||||
status = 'None',
|
||||
}
|
||||
|
||||
m.register {
|
||||
'redefined-local',
|
||||
} {
|
||||
group = 'redefined',
|
||||
severity = 'Hint',
|
||||
status = 'Opened',
|
||||
}
|
||||
|
||||
m.register {
|
||||
'undefined-global',
|
||||
'global-in-nil-env',
|
||||
} {
|
||||
group = 'global',
|
||||
severity = 'Warning',
|
||||
status = 'Any',
|
||||
}
|
||||
|
||||
m.register {
|
||||
'lowercase-global',
|
||||
'undefined-env-child',
|
||||
} {
|
||||
group = 'global',
|
||||
severity = 'Information',
|
||||
status = 'Any',
|
||||
}
|
||||
|
||||
m.register {
|
||||
'global-element',
|
||||
} {
|
||||
group = 'conventions',
|
||||
severity = 'Warning',
|
||||
status = 'None'
|
||||
}
|
||||
|
||||
m.register {
|
||||
'duplicate-index',
|
||||
} {
|
||||
group = 'duplicate',
|
||||
severity = 'Warning',
|
||||
status = 'Any',
|
||||
}
|
||||
|
||||
m.register {
|
||||
'duplicate-set-field',
|
||||
} {
|
||||
group = 'duplicate',
|
||||
severity = 'Warning',
|
||||
status = 'Opened',
|
||||
}
|
||||
|
||||
m.register {
|
||||
'close-non-object',
|
||||
'deprecated',
|
||||
'discard-returns',
|
||||
'invisible',
|
||||
} {
|
||||
group = 'strict',
|
||||
severity = 'Warning',
|
||||
status = 'Any',
|
||||
}
|
||||
|
||||
---@return table<string, DiagnosticSeverity>
|
||||
function m.getDefaultSeverity()
|
||||
local severity = {}
|
||||
for name, info in pairs(m.diagnosticDatas) do
|
||||
severity[name] = info.severity
|
||||
end
|
||||
return severity
|
||||
end
|
||||
|
||||
---@return table<string, DiagnosticNeededFileStatus>
|
||||
function m.getDefaultStatus()
|
||||
local status = {}
|
||||
for name, info in pairs(m.diagnosticDatas) do
|
||||
status[name] = info.status
|
||||
end
|
||||
return status
|
||||
end
|
||||
|
||||
function m.getGroupSeverity()
|
||||
local group = {}
|
||||
for name in pairs(m.diagnosticGroups) do
|
||||
group[name] = 'Fallback'
|
||||
end
|
||||
return group
|
||||
end
|
||||
|
||||
function m.getGroupStatus()
|
||||
local group = {}
|
||||
for name in pairs(m.diagnosticGroups) do
|
||||
group[name] = 'Fallback'
|
||||
end
|
||||
return group
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@return string[]
|
||||
m.getGroups = util.cacheReturn(function (name)
|
||||
local groups = {}
|
||||
for groupName, nameMap in pairs(m.diagnosticGroups) do
|
||||
if nameMap[name] then
|
||||
groups[#groups+1] = groupName
|
||||
end
|
||||
end
|
||||
table.sort(groups)
|
||||
return groups
|
||||
end)
|
||||
|
||||
---@return table<string, true>
|
||||
function m.getDiagAndErrNameMap()
|
||||
if not m._diagAndErrNames then
|
||||
local names = {}
|
||||
for name in pairs(m.getDefaultSeverity()) do
|
||||
names[name] = true
|
||||
end
|
||||
for _, fileName in ipairs {'parser.compile', 'parser.luadoc'} do
|
||||
local path = package.searchpath(fileName, package.path)
|
||||
if path then
|
||||
local f = io.open(path)
|
||||
if f then
|
||||
for line in f:lines() do
|
||||
local name = line:match([=[type%s*=%s*['"](%u[%u_]+%u)['"]]=])
|
||||
if name then
|
||||
local id = name:lower():gsub('_', '-')
|
||||
names[id] = true
|
||||
end
|
||||
end
|
||||
f:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort(names)
|
||||
m._diagAndErrNames = names
|
||||
end
|
||||
return m._diagAndErrNames
|
||||
end
|
||||
|
||||
return m
|
||||
@@ -0,0 +1,3 @@
|
||||
local proto = require 'proto.proto'
|
||||
|
||||
return proto
|
||||
@@ -0,0 +1,308 @@
|
||||
local util = require 'utility'
|
||||
local await = require 'await'
|
||||
local pub = require 'pub'
|
||||
local jsonrpc = require 'jsonrpc'
|
||||
local define = require 'proto.define'
|
||||
local json = require 'json'
|
||||
local inspect = require 'inspect'
|
||||
local platform = require 'bee.platform'
|
||||
local fs = require 'bee.filesystem'
|
||||
local net = require 'service.net'
|
||||
local timer = require 'timer'
|
||||
|
||||
local reqCounter = util.counter()
|
||||
|
||||
local function logSend(buf)
|
||||
if not RPCLOG then
|
||||
return
|
||||
end
|
||||
log.info('rpc send:', buf)
|
||||
end
|
||||
|
||||
local function logRecieve(proto)
|
||||
if not RPCLOG then
|
||||
return
|
||||
end
|
||||
log.info('rpc recieve:', json.encode(proto))
|
||||
end
|
||||
|
||||
---@class proto
|
||||
local m = {}
|
||||
|
||||
m.ability = {}
|
||||
m.waiting = {}
|
||||
m.holdon = {}
|
||||
m.mode = 'stdio'
|
||||
m.client = nil
|
||||
|
||||
function m.getMethodName(proto)
|
||||
if proto.method:sub(1, 2) == '$/' then
|
||||
return proto.method, true
|
||||
else
|
||||
return proto.method, false
|
||||
end
|
||||
end
|
||||
|
||||
---@param callback async fun()
|
||||
function m.on(method, callback)
|
||||
m.ability[method] = callback
|
||||
end
|
||||
|
||||
function m.send(data)
|
||||
local buf = jsonrpc.encode(data)
|
||||
logSend(buf)
|
||||
if m.mode == 'stdio' then
|
||||
io.write(buf)
|
||||
elseif m.mode == 'socket' then
|
||||
m.client:write(buf)
|
||||
end
|
||||
end
|
||||
|
||||
function m.response(id, res)
|
||||
if id == nil then
|
||||
log.error('Response id is nil!', inspect(res))
|
||||
return
|
||||
end
|
||||
if not m.holdon[id] then
|
||||
log.error('Unknown response id!', id)
|
||||
return
|
||||
end
|
||||
m.holdon[id] = nil
|
||||
local data = {}
|
||||
data.id = id
|
||||
data.result = res == nil and json.null or res
|
||||
m.send(data)
|
||||
end
|
||||
|
||||
function m.responseErr(id, code, message)
|
||||
if id == nil then
|
||||
log.error('Response id is nil!', inspect(message))
|
||||
return
|
||||
end
|
||||
if not m.holdon[id] then
|
||||
log.error('Unknown response id!', id)
|
||||
return
|
||||
end
|
||||
m.holdon[id] = nil
|
||||
m.send {
|
||||
id = id,
|
||||
error = {
|
||||
code = code,
|
||||
message = message,
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
function m.notify(name, params)
|
||||
m.send {
|
||||
method = name,
|
||||
params = params,
|
||||
}
|
||||
end
|
||||
|
||||
---@async
|
||||
function m.awaitRequest(name, params)
|
||||
local id = reqCounter()
|
||||
m.send {
|
||||
id = id,
|
||||
method = name,
|
||||
params = params,
|
||||
}
|
||||
local result, error = await.wait(function (resume)
|
||||
m.waiting[id] = {
|
||||
id = id,
|
||||
method = name,
|
||||
params = params,
|
||||
resume = resume,
|
||||
}
|
||||
end)
|
||||
if error then
|
||||
log.warn(('Response of [%s] error [%d]: %s'):format(name, error.code, error.message))
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function m.request(name, params, callback)
|
||||
local id = reqCounter()
|
||||
m.send {
|
||||
id = id,
|
||||
method = name,
|
||||
params = params,
|
||||
}
|
||||
m.waiting[id] = {
|
||||
id = id,
|
||||
method = name,
|
||||
params = params,
|
||||
resume = function (result, error)
|
||||
if error then
|
||||
log.warn(('Response of [%s] error [%d]: %s'):format(name, error.code, error.message))
|
||||
end
|
||||
if callback then
|
||||
callback(result)
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
local secretOption = {
|
||||
process = function (item, path)
|
||||
if path[1] == 'params'
|
||||
and path[2] == 'textDocument'
|
||||
and path[3] == 'text'
|
||||
and path[4] == nil then
|
||||
return '"***"'
|
||||
end
|
||||
return item
|
||||
end
|
||||
}
|
||||
|
||||
m.methodQueue = {}
|
||||
|
||||
function m.applyMethod(proto)
|
||||
logRecieve(proto)
|
||||
local method, optional = m.getMethodName(proto)
|
||||
local abil = m.ability[method]
|
||||
if proto.id then
|
||||
m.holdon[proto.id] = proto
|
||||
end
|
||||
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
|
||||
await.call(function () ---@async
|
||||
--log.debug('Start method:', method)
|
||||
if proto.id then
|
||||
await.setID('proto:' .. proto.id)
|
||||
end
|
||||
local clock = os.clock()
|
||||
local ok = false
|
||||
local res
|
||||
-- 任务可能在执行过程中被中断,通过close来捕获
|
||||
local response <close> = function ()
|
||||
local passed = os.clock() - clock
|
||||
if passed > 0.5 then
|
||||
log.warn(('Method [%s] takes [%.3f]sec. %s'):format(method, passed, inspect(proto, secretOption)))
|
||||
end
|
||||
--log.debug('Finish method:', method)
|
||||
if not proto.id then
|
||||
return
|
||||
end
|
||||
await.close('proto:' .. proto.id)
|
||||
if ok then
|
||||
m.response(proto.id, res)
|
||||
else
|
||||
m.responseErr(proto.id, proto._closeReason or define.ErrorCodes.InternalError, proto._closeMessage or res)
|
||||
end
|
||||
end
|
||||
ok, res = xpcall(abil, log.error, proto.params, proto.id)
|
||||
await.delay()
|
||||
end)
|
||||
end
|
||||
|
||||
function m.applyMethodQueue()
|
||||
local queue = m.methodQueue
|
||||
m.methodQueue = {}
|
||||
local canceled = {}
|
||||
for _, proto in ipairs(queue) do
|
||||
if proto.method == '$/cancelRequest' then
|
||||
canceled[proto.params.id] = true
|
||||
end
|
||||
end
|
||||
for _, proto in ipairs(queue) do
|
||||
if not canceled[proto.id] then
|
||||
m.applyMethod(proto)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function m.doMethod(proto)
|
||||
m.methodQueue[#m.methodQueue+1] = proto
|
||||
if #m.methodQueue > 1 then
|
||||
return
|
||||
end
|
||||
timer.wait(0, m.applyMethodQueue)
|
||||
end
|
||||
|
||||
function m.close(id, reason, message)
|
||||
local proto = m.holdon[id]
|
||||
if not proto then
|
||||
return
|
||||
end
|
||||
proto._closeReason = reason
|
||||
proto._closeMessage = message
|
||||
await.close('proto:' .. id)
|
||||
end
|
||||
|
||||
function m.doResponse(proto)
|
||||
logRecieve(proto)
|
||||
local id = proto.id
|
||||
local waiting = m.waiting[id]
|
||||
if not waiting then
|
||||
log.warn('Response id not found: ' .. inspect(proto))
|
||||
return
|
||||
end
|
||||
m.waiting[id] = nil
|
||||
if proto.error then
|
||||
waiting.resume(nil, proto.error)
|
||||
return
|
||||
end
|
||||
waiting.resume(proto.result)
|
||||
end
|
||||
|
||||
function m.listen(mode, socketPort)
|
||||
m.mode = mode
|
||||
if mode == 'stdio' then
|
||||
log.info('Listen Mode: stdio')
|
||||
if platform.os == 'windows' then
|
||||
local windows = require 'bee.windows'
|
||||
windows.filemode(io.stdin, 'b')
|
||||
windows.filemode(io.stdout, 'b')
|
||||
end
|
||||
io.stdin:setvbuf 'no'
|
||||
io.stdout:setvbuf 'no'
|
||||
pub.task('loadProtoByStdio')
|
||||
elseif mode == 'socket' then
|
||||
local unixFolder = LOGPATH .. '/unix'
|
||||
fs.create_directories(fs.path(unixFolder))
|
||||
local unixPath = unixFolder .. '/' .. tostring(socketPort)
|
||||
|
||||
local server = net.listen('unix', unixPath)
|
||||
|
||||
log.info('Listen Mode: socket')
|
||||
log.info('Listen Port:', socketPort)
|
||||
log.info('Listen Path:', unixPath)
|
||||
|
||||
assert(server)
|
||||
|
||||
local dummyClient = {
|
||||
buf = '',
|
||||
write = function (self, data)
|
||||
self.buf = self.buf.. data
|
||||
end,
|
||||
update = function () end,
|
||||
}
|
||||
m.client = dummyClient
|
||||
|
||||
function server:on_accepted(client)
|
||||
m.client = client
|
||||
client:write(dummyClient.buf)
|
||||
return true
|
||||
end
|
||||
|
||||
function server:on_error(...)
|
||||
log.error(...)
|
||||
end
|
||||
|
||||
pub.task('loadProtoBySocket', {
|
||||
port = socketPort,
|
||||
unixPath = unixPath,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return m
|
||||
Reference in New Issue
Block a user