diff options
Diffstat (limited to 'script')
61 files changed, 1609 insertions, 948 deletions
diff --git a/script/await.lua b/script/await.lua index e92af272..ff840956 100644 --- a/script/await.lua +++ b/script/await.lua @@ -25,7 +25,7 @@ local function setID(id, co, callback) end --- 设置错误处理器 ----@param errHandle function {comment = '当有错误发生时,会以错误堆栈为参数调用该函数'} +---@param errHandle function # 当有错误发生时,会以错误堆栈为参数调用该函数 function m.setErrorHandle(errHandle) m.errorHandle = errHandle end @@ -39,6 +39,7 @@ function m.checkResult(co, ...) end --- 创建一个任务 +---@param callback async fun() function m.call(callback, ...) local co = coroutine.create(callback) local closers = {} @@ -66,6 +67,7 @@ function m.call(callback, ...) end --- 创建一个任务,并挂起当前线程,当任务完成后再延续当前线程/若任务被关闭,则返回nil +---@async function m.await(callback, ...) if not coroutine.isyieldable() then return callback(...) @@ -109,6 +111,7 @@ end --- 休眠一段时间 ---@param time number +---@async function m.sleep(time) if not coroutine.isyieldable() then if m.errorHandle then @@ -128,6 +131,7 @@ end --- 等待直到唤醒 ---@param callback function +---@async function m.wait(callback, ...) if not coroutine.isyieldable() then return @@ -148,6 +152,7 @@ function m.wait(callback, ...) end --- 延迟 +---@async function m.delay() if not m._enable then return @@ -174,6 +179,7 @@ function m.delay() end --- stop then close +---@async function m.stop() if not coroutine.isyieldable() then return @@ -239,6 +245,7 @@ function m.disable() end --- 注册事件 +---@param callback async fun(ev: string, ...) function m.watch(callback) m.watchList[#m.watchList+1] = callback end diff --git a/script/client.lua b/script/client.lua index 93ac38b3..4d39cd0d 100644 --- a/script/client.lua +++ b/script/client.lua @@ -59,6 +59,23 @@ function m.getAbility(name) return current end +function m.getOffsetEncoding() + if m._offsetEncoding then + return m._offsetEncoding + end + local clientEncodings = m.getAbility 'offsetEncoding' + if type(clientEncodings) == 'table' then + for _, encoding in ipairs(clientEncodings) do + if encoding == 'utf-8' then + m._offsetEncoding = 'utf-8' + return m._offsetEncoding + end + end + end + m._offsetEncoding = 'utf-16' + return m._offsetEncoding +end + local function packMessage(...) local strs = table.pack(...) for i = 1, strs.n do @@ -88,6 +105,7 @@ end ---@param titles string[] ---@return string action ---@return integer index +---@async function m.awaitRequestMessage(type, message, titles) proto.notify('window/logMessage', { type = define.MessageType[type] or 3, @@ -254,6 +272,7 @@ function m.init(t) m.client(t.clientInfo.name) nonil.disable() lang(LOCALE or t.locale) + converter.setOffsetEncoding(m.getOffsetEncoding()) hookPrint() end diff --git a/script/config/config.lua b/script/config/config.lua index a361d68f..0e18428f 100644 --- a/script/config/config.lua +++ b/script/config/config.lua @@ -151,6 +151,7 @@ local Template = { "?.lua", "?/init.lua", }, + ['Lua.runtime.pathStrict'] = Type.Boolean >> false, ['Lua.runtime.special'] = Type.Hash(Type.String, Type.String), ['Lua.runtime.meta'] = Type.String >> '${version} ${language} ${encoding}', ['Lua.runtime.unicodeName'] = Type.Boolean, @@ -198,6 +199,7 @@ local Template = { ['Lua.hint.paramType'] = Type.Boolean >> true, ['Lua.hint.setType'] = Type.Boolean >> false, ['Lua.hint.paramName'] = Type.String >> 'All', + ['Lua.hint.await'] = Type.Boolean >> true, ['Lua.window.statusBar'] = Type.Boolean >> true, ['Lua.window.progressBar'] = Type.Boolean >> true, ['Lua.IntelliSense.traceLocalSet'] = Type.Boolean >> false, diff --git a/script/config/loader.lua b/script/config/loader.lua index 03fe9456..e754be49 100644 --- a/script/config/loader.lua +++ b/script/config/loader.lua @@ -19,37 +19,43 @@ local m = {} function m.loadRCConfig(filename) local path = workspace.getAbsolutePath(filename) if not path then - return + m.lastRCConfig = nil + return nil end local buf = util.loadFile(path) if not buf then - return + m.lastRCConfig = nil + return nil end local suc, res = pcall(json.decode, buf) if not suc then errorMessage(lang.script('CONFIG_LOAD_ERROR', res)) - return + return m.lastRCConfig end + m.lastRCConfig = res return res end function m.loadLocalConfig(filename) local path = workspace.getAbsolutePath(filename) if not path then - return + m.lastLocalConfig = nil + return nil end local buf = util.loadFile(path) if not buf then errorMessage(lang.script('CONFIG_LOAD_FAILED', path)) - return + m.lastLocalConfig = nil + return nil end local firstChar = buf:match '%S' if firstChar == '{' then local suc, res = pcall(json.decode, buf) if not suc then errorMessage(lang.script('CONFIG_LOAD_ERROR', res)) - return + return m.lastLocalConfig end + m.lastLocalConfig = res return res else local suc, res = pcall(function () @@ -57,12 +63,14 @@ function m.loadLocalConfig(filename) end) if not suc then errorMessage(lang.script('CONFIG_LOAD_ERROR', res)) - return + return m.lastLocalConfig end + m.lastLocalConfig = res return res end end +---@async function m.loadClientConfig() local configs = proto.awaitRequest('workspace/configuration', { items = { diff --git a/script/core/code-action.lua b/script/core/code-action.lua index 8256107e..ad048c48 100644 --- a/script/core/code-action.lua +++ b/script/core/code-action.lua @@ -308,6 +308,44 @@ local function solveTrailingSpace(uri, diag, results) } end +local function solveAwaitInSync(uri, diag, results) + local state = files.getState(uri) + if not state then + return + end + local start, finish = converter.unpackRange(uri, diag.range) + local parentFunction + guide.eachSourceType(state.ast, 'function', function (source) + if source.start > finish + or source.finish < start then + return + end + if not parentFunction or parentFunction.start < source.start then + parentFunction = source + end + end) + if not parentFunction then + return + end + local row = guide.rowColOf(parentFunction.start) + local pos = guide.positionOf(row, 0) + results[#results+1] = { + title = lang.script.ACTION_MARK_ASYNC, + kind = 'quickfix', + edit = { + changes = { + [uri] = { + { + start = pos, + finish = pos, + newText = '---@async\n', + } + } + } + }, + } +end + local function solveDiagnostic(uri, diag, start, results) if diag.source == lang.script.DIAG_SYNTAX_CHECK then solveSyntax(uri, diag, results) @@ -326,6 +364,8 @@ local function solveDiagnostic(uri, diag, start, results) solveAmbiguity1(uri, diag, results) elseif diag.code == 'trailing-space' then solveTrailingSpace(uri, diag, results) + elseif diag.code == 'await-in-sync' then + solveAwaitInSync(uri, diag, results) end disableDiagnostic(uri, diag.code, start, results) end diff --git a/script/core/command/autoRequire.lua b/script/core/command/autoRequire.lua index aa641967..30bd13a1 100644 --- a/script/core/command/autoRequire.lua +++ b/script/core/command/autoRequire.lua @@ -63,6 +63,7 @@ local function findInsertRow(uri) return row or 0, fmt end +---@async local function askAutoRequire(visiblePaths) local selects = {} local nameMap = {} @@ -125,6 +126,7 @@ local function applyAutoRequire(uri, row, name, result, fmt) }) end +---@async return function (data) local uri = data.uri local target = data.target @@ -135,7 +137,7 @@ return function (data) end local path = furi.decode(target) - local visiblePaths = rpath.getVisiblePath(path, config.get 'Lua.runtime.path') + local visiblePaths = rpath.getVisiblePath(path) if not visiblePaths or #visiblePaths == 0 then return end diff --git a/script/core/command/jsonToLua.lua b/script/core/command/jsonToLua.lua index 6aecee2c..d29ad608 100644 --- a/script/core/command/jsonToLua.lua +++ b/script/core/command/jsonToLua.lua @@ -7,6 +7,7 @@ local lang = require 'language' local converter = require 'proto.converter' local guide = require 'parser.guide' +---@async return function (data) local state = files.getState(data.uri) local text = files.getText(data.uri) diff --git a/script/core/command/removeSpace.lua b/script/core/command/removeSpace.lua index 3021d4a4..aa565f7f 100644 --- a/script/core/command/removeSpace.lua +++ b/script/core/command/removeSpace.lua @@ -12,6 +12,7 @@ local function isInString(ast, offset) end) or false end +---@async return function (data) local uri = data.uri local text = files.getText(uri) diff --git a/script/core/command/solve.lua b/script/core/command/solve.lua index f7831f7f..8065aa9d 100644 --- a/script/core/command/solve.lua +++ b/script/core/command/solve.lua @@ -27,6 +27,7 @@ local literalMap = { ['table'] = true, } +---@async return function (data) local uri = data.uri local text = files.getText(uri) diff --git a/script/core/completion.lua b/script/core/completion.lua index cdd01f98..4dd55070 100644 --- a/script/core/completion.lua +++ b/script/core/completion.lua @@ -20,6 +20,7 @@ local lookBackward = require 'core.look-backward' local guide = require 'parser.guide' local infer = require 'core.infer' local noder = require 'core.noder' +local await = require 'await' local DiagnosticModes = { 'disable-next-line', @@ -30,6 +31,8 @@ local DiagnosticModes = { local stackID = 0 local stacks = {} + +---@param callback async fun() local function stack(callback) stackID = stackID + 1 stacks[stackID] = callback @@ -40,6 +43,7 @@ local function clearStack() stacks = {} end +---@async local function resolveStack(id) local callback = stacks[id] if not callback then @@ -87,7 +91,8 @@ local function findNearestTableField(state, position) return source end -local function findParent(state, text, position) +local function findParent(state, position) + local text = state.lua local offset = guide.positionToOffset(state, position) for i = offset, 1, -1 do local char = text:sub(i, i) @@ -124,7 +129,7 @@ local function findParent(state, text, position) return nil, nil end -local function findParentInStringIndex(state, text, position) +local function findParentInStringIndex(state, position) local near, nearStart guide.eachSourceContain(state.ast, position, function (source) local start = guide.getStartFinish(source) @@ -202,6 +207,7 @@ local function getSnip(source) end end +---@async local function buildDesc(source) if source.type == 'dummy' then return @@ -228,7 +234,7 @@ local function buildFunction(results, source, value, oop, data) title = 'trigger signature', command = 'editor.action.triggerParameterHints', } - snipData.id = stack(function () + snipData.id = stack(function () ---@async return { detail = buildDetail(source), description = buildDesc(source), @@ -291,7 +297,7 @@ local function checkLocal(state, word, position, results) match = name, insertText = name, kind = define.CompletionItemKind.Function, - id = stack(function () + id = stack(function () ---@async return { detail = buildDetail(source), description = buildDesc(source), @@ -304,7 +310,7 @@ local function checkLocal(state, word, position, results) results[#results+1] = { label = name, kind = define.CompletionItemKind.Variable, - id = stack(function () + id = stack(function () ---@async return { detail = buildDetail(source), description = buildDesc(source), @@ -369,7 +375,7 @@ local function checkModule(state, word, position, results) }, }, }, - id = stack(function () + id = stack(function () ---@async local md = markdown() md:add('md', lang.script('COMPLETION_IMPORT_FROM', ('[%s](%s)'):format( workspace.getRelativePath(uri), @@ -388,19 +394,16 @@ local function checkModule(state, word, position, results) end end -local function checkFieldFromFieldToIndex(name, src, parent, word, startPos, position) +local function checkFieldFromFieldToIndex(state, name, src, parent, word, startPos, position) if name:match '^[%a_][%w_]*$' then return nil end local textEdit, additionalTextEdits - local uri = guide.getUri(parent) - local text = files.getText(uri) - local state = files.getState(uri) local startOffset = guide.positionToOffset(state, startPos) local offset = guide.positionToOffset(state, position) local wordStartOffset if word == '' then - wordStartOffset = text:match('()%S', startOffset + 1) + wordStartOffset = state.lua:match('()%S', startOffset + 1) if wordStartOffset then wordStartOffset = wordStartOffset - 1 else @@ -444,8 +447,8 @@ local function checkFieldFromFieldToIndex(name, src, parent, word, startPos, pos } end else - if config.get 'Lua.runtime.version' == 'Lua 5.1' - or config.get 'Lua.runtime.version' == 'LuaJIT' then + if config.get 'Lua.runtime.version' == 'lua 5.1' + or config.get 'Lua.runtime.version' == 'luaJIT' then textEdit.newText = '_G' .. textEdit.newText else textEdit.newText = '_ENV' .. textEdit.newText @@ -454,7 +457,7 @@ local function checkFieldFromFieldToIndex(name, src, parent, word, startPos, pos return textEdit, additionalTextEdits end -local function checkFieldThen(name, src, word, startPos, position, parent, oop, results) +local function checkFieldThen(state, name, src, word, startPos, position, parent, oop, results) local value = searcher.getObjectValue(src) or src local kind = define.CompletionItemKind.Field if value.type == 'function' @@ -470,7 +473,7 @@ local function checkFieldThen(name, src, word, startPos, position, parent, oop, match = name:match '^[^(]+', insertText = name:match '^[^(]+', deprecated = vm.isDeprecated(src) or nil, - id = stack(function () + id = stack(function () ---@async return { detail = buildDetail(src), description = buildDesc(src), @@ -495,7 +498,7 @@ local function checkFieldThen(name, src, word, startPos, position, parent, oop, newText = name, } else - textEdit, additionalTextEdits = checkFieldFromFieldToIndex(name, src, parent, word, startPos, position) + textEdit, additionalTextEdits = checkFieldFromFieldToIndex(state, name, src, parent, word, startPos, position) end results[#results+1] = { label = name, @@ -503,7 +506,7 @@ local function checkFieldThen(name, src, word, startPos, position, parent, oop, deprecated = vm.isDeprecated(src) or nil, textEdit = textEdit, additionalTextEdits = additionalTextEdits, - id = stack(function () + id = stack(function () ---@async return { detail = buildDetail(src), description = buildDesc(src), @@ -512,6 +515,7 @@ local function checkFieldThen(name, src, word, startPos, position, parent, oop, } end +---@async local function checkFieldOfRefs(refs, state, word, startPos, position, parent, oop, results, locals, isGlobal) local fields = {} local funcs = {} @@ -570,17 +574,20 @@ local function checkFieldOfRefs(refs, state, word, startPos, position, parent, o end for name, src in util.sortPairs(fields) do if src then - checkFieldThen(name, src, word, startPos, position, parent, oop, results) + checkFieldThen(state, name, src, word, startPos, position, parent, oop, results) + await.delay() end end end +---@async local function checkGlobal(state, word, startPos, position, parent, oop, results) local locals = guide.getVisibleLocals(state.ast, position) local globals = vm.getGlobalSets '*' checkFieldOfRefs(globals, state, word, startPos, position, parent, oop, results, locals, 'global') end +---@async local function checkField(state, word, start, position, parent, oop, results) if parent.tag == '_ENV' or parent.special == '_G' then local globals = vm.getGlobalSets '*' @@ -620,7 +627,8 @@ local function checkTableField(state, word, start, results) end) end -local function checkCommon(myUri, word, text, position, results) +local function checkCommon(state, word, position, results) + local myUri = state.uri local showWord = config.get 'Lua.completion.showWord' if showWord == 'Disable' then return @@ -688,8 +696,7 @@ local function checkCommon(myUri, word, text, position, results) end end end - local state = files.getState(myUri) - for str, offset in text:gmatch '([%a_][%w_]+)()' do + for str, offset in state.lua:gmatch '([%a_][%w_]+)()' do if #results >= 100 then break end @@ -719,7 +726,8 @@ local function isInString(state, position) end) end -local function checkKeyWord(state, text, start, position, word, hasSpace, afterLocal, results) +local function checkKeyWord(state, start, position, word, hasSpace, afterLocal, results) + local text = state.lua local snipType = config.get 'Lua.completion.keywordSnippet' local symbol = lookBackward.findSymbol(text, guide.positionToOffset(state, start)) local isExp = symbol == '(' or symbol == ',' or symbol == '=' @@ -871,7 +879,8 @@ local function checkFunctionArgByDocParam(state, word, startPos, results) end end -local function isAfterLocal(state, text, startPos) +local function isAfterLocal(state, startPos) + local text = state.lua local offset = guide.positionToOffset(state, startPos) local pos = lookBackward.skipSpace(text, offset) local word = lookBackward.findWord(text, pos) @@ -886,7 +895,7 @@ local function collectRequireNames(mode, myUri, literal, source, smark, position goto CONTINUE end local path = workspace.getRelativePath(uri) - local infos = rpath.getVisiblePath(path, config.get 'Lua.runtime.path') + local infos = rpath.getVisiblePath(path) for _, info in ipairs(infos) do if matchKey(literal, info.expect) then if not collect[info.expect] then @@ -977,7 +986,7 @@ local function collectRequireNames(mode, myUri, literal, source, smark, position end end -local function checkUri(state, text, position, results) +local function checkUri(state, position, results) local myUri = guide.getUri(state.ast) guide.eachSourceContain(state.ast, position, function (source) if source.type ~= 'string' then @@ -1005,7 +1014,8 @@ local function checkUri(state, text, position, results) end) end -local function checkLenPlusOne(state, text, position, results) +local function checkLenPlusOne(state, position, results) + local text = state.lua guide.eachSourceContain(state.ast, position, function (source) if source.type == 'getindex' or source.type == 'setindex' then @@ -1097,7 +1107,7 @@ local function mergeEnums(a, b, source) end end -local function checkTypingEnum(state, text, position, defs, str, results) +local function checkTypingEnum(state, position, defs, str, results) local enums = {} for _, def in ipairs(defs) do if def.type == 'doc.type.enum' @@ -1119,7 +1129,7 @@ local function checkTypingEnum(state, text, position, defs, str, results) end end -local function checkEqualEnumLeft(state, text, position, source, results) +local function checkEqualEnumLeft(state, position, source, results) if not source then return end @@ -1129,10 +1139,11 @@ local function checkEqualEnumLeft(state, text, position, source, results) end end) local defs = vm.getDefs(source) - checkTypingEnum(state, text, position, defs, str, results) + checkTypingEnum(state, position, defs, str, results) end -local function checkEqualEnum(state, text, position, results) +local function checkEqualEnum(state, position, results) + local text = state.lua local start = lookBackward.findTargetSymbol(text, guide.positionToOffset(state, position), '=') if not start then return @@ -1154,10 +1165,10 @@ local function checkEqualEnum(state, text, position, results) if source.type == 'call' and not eqOrNeq then return end - checkEqualEnumLeft(state, text, position, source, results) + checkEqualEnumLeft(state, position, source, results) end -local function checkEqualEnumInString(state, text, position, results) +local function checkEqualEnumInString(state, position, results) local source = findNearestSource(state, position) local parent = source.parent if parent.type == 'binary' then @@ -1170,16 +1181,16 @@ local function checkEqualEnumInString(state, text, position, results) if parent.op.type ~= '==' and parent.op.type ~= '~=' then return end - checkEqualEnumLeft(state, text, position, parent[1], results) + checkEqualEnumLeft(state, position, parent[1], results) end if parent.type == 'local' then - checkEqualEnumLeft(state, text, position, parent, results) + checkEqualEnumLeft(state, position, parent, results) end if parent.type == 'setlocal' or parent.type == 'setglobal' or parent.type == 'setfield' or parent.type == 'setindex' then - checkEqualEnumLeft(state, text, position, parent.node, results) + checkEqualEnumLeft(state, position, parent.node, results) end end @@ -1191,20 +1202,21 @@ local function isFuncArg(state, position) end) end -local function trySpecial(state, text, position, results) +local function trySpecial(state, position, results) if isInString(state, position) then - checkUri(state, text, position, results) - checkEqualEnumInString(state, text, position, results) + checkUri(state, position, results) + checkEqualEnumInString(state, position, results) return end -- x[#x+1] - checkLenPlusOne(state, text, position, results) + checkLenPlusOne(state, position, results) -- type(o) == - checkEqualEnum(state, text, position, results) + checkEqualEnum(state, position, results) end -local function tryIndex(state, text, position, results) - local parent, oop = findParentInStringIndex(state, text, position) +---@async +local function tryIndex(state, position, results) + local parent, oop = findParentInStringIndex(state, position) if not parent then return end @@ -1212,7 +1224,9 @@ local function tryIndex(state, text, position, results) checkField(state, word, position, position, parent, oop, results) end -local function tryWord(state, text, position, triggerCharacter, results) +---@async +local function tryWord(state, position, triggerCharacter, results) + local text = state.lua local offset = guide.positionToOffset(state, position) local finish = lookBackward.skipSpace(text, offset) local word, start = lookBackward.findWord(text, offset) @@ -1226,11 +1240,11 @@ local function tryWord(state, text, position, triggerCharacter, results) if isInString(state, position) then if not hasSpace then if #results == 0 then - checkCommon(state.uri, word, text, position, results) + checkCommon(state, word, position, results) end end else - local parent, oop = findParent(state, text, startPos) + local parent, oop = findParent(state, startPos) if parent then if not hasSpace then checkField(state, word, startPos, position, parent, oop, results) @@ -1239,8 +1253,8 @@ local function tryWord(state, text, position, triggerCharacter, results) checkProvideLocal(state, word, startPos, results) checkFunctionArgByDocParam(state, word, startPos, results) else - local afterLocal = isAfterLocal(state, text, startPos) - local stop = checkKeyWord(state, text, startPos, position, word, hasSpace, afterLocal, results) + local afterLocal = isAfterLocal(state, startPos) + local stop = checkKeyWord(state, startPos, position, word, hasSpace, afterLocal, results) if stop then return end @@ -1257,12 +1271,14 @@ local function tryWord(state, text, position, triggerCharacter, results) end end if not hasSpace then - checkCommon(state.uri, word, text, position, results) + checkCommon(state, word, position, results) end end end -local function trySymbol(state, text, position, results) +---@async +local function trySymbol(state, position, results) + local text = state.lua local symbol, start = lookBackward.findSymbol(text, guide.positionToOffset(state, position)) if not symbol then return nil @@ -1273,7 +1289,7 @@ local function trySymbol(state, text, position, results) local startPos = guide.offsetToPosition(state, start) if symbol == '.' or symbol == ':' then - local parent, oop = findParent(state, text, startPos) + local parent, oop = findParent(state, startPos) if parent then tracy.ZoneBeginN 'completion.trySymbol' checkField(state, '', startPos, position, parent, oop, results) @@ -1411,7 +1427,7 @@ local function getCallEnumsAndFuncs(source, index, oop, call) end end -local function findCall(state, text, position) +local function findCall(state, position) local call guide.eachSourceContain(state.ast, position, function (src) if src.type == 'call' then @@ -1423,7 +1439,7 @@ local function findCall(state, text, position) return call end -local function getCallArgInfo(call, text, position) +local function getCallArgInfo(call, position) if not call.args then return 1, nil, nil end @@ -1448,7 +1464,8 @@ local function getFuncParamByCallIndex(func, index) return func.args[index] end -local function checkTableLiteralField(state, text, position, tbl, fields, results) +local function checkTableLiteralField(state, position, tbl, fields, results) + local text = state.lua local mark = {} for _, field in ipairs(tbl) do if field.type == 'tablefield' @@ -1480,7 +1497,7 @@ local function checkTableLiteralField(state, text, position, tbl, fields, result label = guide.getKeyName(field), kind = define.CompletionItemKind.Property, insertText = ('%s = $0'):format(guide.getKeyName(field)), - id = stack(function () + id = stack(function () ---@async return { detail = buildDetail(field), description = buildDesc(field), @@ -1492,7 +1509,7 @@ local function checkTableLiteralField(state, text, position, tbl, fields, result end end -local function checkTableLiteralFieldByCall(state, text, position, call, defs, index, results) +local function checkTableLiteralFieldByCall(state, position, call, defs, index, results) local source = findNearestTableField(state, position) if not source then return @@ -1526,16 +1543,16 @@ local function checkTableLiteralFieldByCall(state, text, position, call, defs, i end ::CONTINUE:: end - checkTableLiteralField(state, text, position, tbl, fields, results) + checkTableLiteralField(state, position, tbl, fields, results) end -local function tryCallArg(state, text, position, results) - local call = findCall(state, text, position) +local function tryCallArg(state, position, results) + local call = findCall(state, position) if not call then return end local myResults = {} - local argIndex, arg, oop = getCallArgInfo(call, text, position) + local argIndex, arg, oop = getCallArgInfo(call, position) if arg and arg.type == 'function' then return end @@ -1550,10 +1567,10 @@ local function tryCallArg(state, text, position, results) for _, enum in ipairs(myResults) do results[#results+1] = enum end - checkTableLiteralFieldByCall(state, text, position, call, defs, argIndex, results) + checkTableLiteralFieldByCall(state, position, call, defs, argIndex, results) end -local function tryTable(state, text, position, results) +local function tryTable(state, position, results) local source = findNearestTableField(state, position) if not source then return @@ -1577,7 +1594,7 @@ local function tryTable(state, text, position, results) fields[#fields+1] = field end end - checkTableLiteralField(state, text, position, tbl, fields, results) + checkTableLiteralField(state, position, tbl, fields, results) end local function getComment(state, position) @@ -1589,7 +1606,7 @@ local function getComment(state, position) return nil end -local function getLuaDoc(state, position) +local function getluaDoc(state, position) for _, doc in ipairs(state.ast.docs) do if position >= doc.start and position <= doc.range then return doc @@ -1598,7 +1615,7 @@ local function getLuaDoc(state, position) return nil end -local function tryLuaDocCate(word, results) +local function tryluaDocCate(word, results) for _, docType in ipairs { 'class', 'type', @@ -1615,6 +1632,8 @@ local function tryLuaDocCate(word, results) 'see', 'diagnostic', 'module', + 'async', + 'nodiscard', } do if matchKey(word, docType) then results[#results+1] = { @@ -1625,7 +1644,7 @@ local function tryLuaDocCate(word, results) end end -local function getLuaDocByContain(state, position) +local function getluaDocByContain(state, position) local result local range = math.huge guide.eachSourceContain(state.ast.docs, position, function (src) @@ -1641,12 +1660,12 @@ local function getLuaDocByContain(state, position) return result end -local function getLuaDocByErr(state, text, start, position) +local function getluaDocByErr(state, start, position) local targetError for _, err in ipairs(state.errs) do if err.finish <= position and err.start >= start then - if not text:sub(err.finish + 1, position):find '%S' then + if not state.lua:sub(err.finish + 1, position):find '%S' then targetError = err break end @@ -1666,13 +1685,16 @@ local function getLuaDocByErr(state, text, start, position) return targetError, targetDoc end -local function tryLuaDocBySource(state, position, source, results) +local function tryluaDocBySource(state, position, source, results) if source.type == 'doc.extends.name' then if source.parent.type == 'doc.class' then + local used = {} for _, doc in ipairs(vm.getDocDefines '*') do if doc.type == 'doc.class.name' and doc.parent ~= source.parent + and not used[doc[1]] and matchKey(source[1], doc[1]) then + used[doc[1]] = true results[#results+1] = { label = doc[1], kind = define.CompletionItemKind.Class, @@ -1687,10 +1709,13 @@ local function tryLuaDocBySource(state, position, source, results) end return true elseif source.type == 'doc.type.name' then + local used = {} for _, doc in ipairs(vm.getDocDefines '*') do if (doc.type == 'doc.class.name' or doc.type == 'doc.alias.name') and doc.parent ~= source.parent + and not used[doc[1]] and matchKey(source[1], doc[1]) then + used[doc[1]] = true results[#results+1] = { label = doc[1], kind = define.CompletionItemKind.Class, @@ -1761,7 +1786,7 @@ local function tryLuaDocBySource(state, position, source, results) return false end -local function tryLuaDocByErr(state, position, err, docState, results) +local function tryluaDocByErr(state, position, err, docState, results) if err.type == 'LUADOC_MISS_CLASS_EXTENDS_NAME' then for _, doc in ipairs(vm.getDocDefines '*') do if doc.type == 'doc.class.name' @@ -1840,7 +1865,7 @@ local function tryLuaDocByErr(state, position, err, docState, results) end end -local function buildLuaDocOfFunction(func) +local function buildluaDocOfFunction(func) local index = 1 local buf = {} buf[#buf+1] = '${1:comment}' @@ -1882,7 +1907,7 @@ local function buildLuaDocOfFunction(func) return insertText end -local function tryLuaDocOfFunction(doc, results) +local function tryluaDocOfFunction(doc, results) if not doc.bindSources then return end @@ -1902,7 +1927,7 @@ local function tryLuaDocOfFunction(doc, results) return end end - local insertText = buildLuaDocOfFunction(func) + local insertText = buildluaDocOfFunction(func) results[#results+1] = { label = '@param;@return', kind = define.CompletionItemKind.Snippet, @@ -1912,8 +1937,8 @@ local function tryLuaDocOfFunction(doc, results) } end -local function tryLuaDoc(state, text, position, results) - local doc = getLuaDoc(state, position) +local function tryluaDoc(state, position, results) + local doc = getluaDoc(state, position) if not doc then return end @@ -1921,38 +1946,38 @@ local function tryLuaDoc(state, text, position, results) local line = doc.originalComment.text -- 尝试 ---$ if line == '-' then - tryLuaDocOfFunction(doc, results) + tryluaDocOfFunction(doc, results) return end -- 尝试 ---@$ local cate = line:match('^-+%s*@(%a*)$') if cate then - tryLuaDocCate(cate, results) + tryluaDocCate(cate, results) return end end -- 根据输入中的source来补全 - local source = getLuaDocByContain(state, position) + local source = getluaDocByContain(state, position) if source then - local suc = tryLuaDocBySource(state, position, source, results) + local suc = tryluaDocBySource(state, position, source, results) if suc then return end end -- 根据附近的错误消息来补全 - local err, expectDoc = getLuaDocByErr(state, text, doc.start, position) + local err, expectDoc = getluaDocByErr(state, doc.start, position) if err then - tryLuaDocByErr(state, position, err, expectDoc, results) + tryluaDocByErr(state, position, err, expectDoc, results) return end end -local function tryComment(state, text, position, results) +local function tryComment(state, position, results) if #results > 0 then return end - local word = lookBackward.findWord(text, guide.positionToOffset(state, position)) - local doc = getLuaDoc(state, position) + local word = lookBackward.findWord(state.lua, guide.positionToOffset(state, position)) + local doc = getluaDoc(state, position) if not word then local comment = getComment(state, position) if comment.type == 'comment.short' @@ -1973,7 +1998,7 @@ local function tryComment(state, text, position, results) if doc and doc.type ~= 'doc.comment' then return end - checkCommon(state.uri, word, text, position, results) + checkCommon(state, word, position, results) end local function makeCache(uri, position, results) @@ -2043,7 +2068,7 @@ local function getCache(uri, position) end if results.enableCommon then - checkCommon(uri, word, text, position, results) + checkCommon(state, word, position, results) end return cache.results @@ -2054,6 +2079,7 @@ local function clearCache() cache.results = nil end +---@async local function completion(uri, position, triggerCharacter) tracy.ZoneBeginN 'completion cache' local results = getCache(uri, position) @@ -2061,29 +2087,30 @@ local function completion(uri, position, triggerCharacter) if results then return results end + await.delay() tracy.ZoneBeginN 'completion #1' local state = files.getState(uri) local text = files.getText(uri) - results = {} + local results = {} clearStack() tracy.ZoneEnd() tracy.ZoneBeginN 'completion #2' if state then if getComment(state, position) then - tryLuaDoc(state, text, position, results) - tryComment(state, text, position, results) + tryluaDoc(state, position, results) + tryComment(state, position, results) else - trySpecial(state, text, position, results) - tryCallArg(state, text, position, results) - tryTable(state, text, position, results) - tryWord(state, text, position, triggerCharacter, results) - tryIndex(state, text, position, results) - trySymbol(state, text, position, results) + trySpecial(state, position, results) + tryCallArg(state, position, results) + tryTable(state, position, results) + tryWord(state, position, triggerCharacter, results) + tryIndex(state, position, results) + trySymbol(state, position, results) end else local word = lookBackward.findWord(text, guide.positionToOffset(state, position)) if word then - checkCommon(nil, word, text, position, results) + checkCommon(nil, word, position, results) end end tracy.ZoneEnd() @@ -2099,6 +2126,7 @@ local function completion(uri, position, triggerCharacter) return results end +---@async local function resolve(id) local item = resolveStack(id) local cache = workspace.getCache 'completion' diff --git a/script/core/diagnostics/await-in-sync.lua b/script/core/diagnostics/await-in-sync.lua new file mode 100644 index 00000000..558a5ff0 --- /dev/null +++ b/script/core/diagnostics/await-in-sync.lua @@ -0,0 +1,30 @@ +local files = require 'files' +local guide = require 'parser.guide' +local vm = require 'vm' +local lang = require 'language' +local await = require 'await' + +---@async +return function (uri, callback) + local state = files.getState(uri) + if not state then + return + end + + ---@async + guide.eachSourceType(state.ast, 'call', function (source) + local currentFunc = guide.getParentFunction(source) + if currentFunc and vm.isAsync(currentFunc, false) then + return + end + await.delay() + if vm.isAsyncCall(source) then + callback { + start = source.node.start, + finish = source.node.finish, + message = lang.script('DIAG_AWAIT_IN_SYNC'), + } + return + end + end) +end diff --git a/script/core/diagnostics/deprecated.lua b/script/core/diagnostics/deprecated.lua index 0aeac9e9..e9a1fef7 100644 --- a/script/core/diagnostics/deprecated.lua +++ b/script/core/diagnostics/deprecated.lua @@ -8,6 +8,7 @@ local await = require 'await' local noder = require 'core.noder' local types = {'getglobal', 'getfield', 'getindex', 'getmethod'} +---@async return function (uri, callback) local ast = files.getState(uri) if not ast then @@ -16,7 +17,7 @@ return function (uri, callback) local cache = {} - guide.eachSourceTypes(ast.ast, types, function (src) + guide.eachSourceTypes(ast.ast, types, function (src) ---@async if src.type == 'getglobal' then local key = src[1] if not key then diff --git a/script/core/diagnostics/discard-returns.lua b/script/core/diagnostics/discard-returns.lua new file mode 100644 index 00000000..cef7ece5 --- /dev/null +++ b/script/core/diagnostics/discard-returns.lua @@ -0,0 +1,29 @@ +local files = require 'files' +local guide = require 'parser.guide' +local vm = require 'vm' +local await = require 'await' +local lang = require 'language' + +---@async +return function (uri, callback) + local state = files.getState(uri) + if not state then + return + end + ---@async + guide.eachSourceType(state.ast, 'call', function (source) + local parent = source.parent + if parent.type ~= 'function' + and parent.type ~= 'main' then + return + end + await.delay() + if vm.isNoDiscard(source.node, true) then + callback { + start = source.start, + finish = source.finish, + message = lang.script('DIAG_DISCARD_RETURNS'), + } + end + end) +end diff --git a/script/core/diagnostics/duplicate-doc-class.lua b/script/core/diagnostics/duplicate-doc-class.lua index 780d15b9..97e2089a 100644 --- a/script/core/diagnostics/duplicate-doc-class.lua +++ b/script/core/diagnostics/duplicate-doc-class.lua @@ -16,8 +16,7 @@ return function (uri, callback) local cache = {} for _, doc in ipairs(state.ast.docs) do - if doc.type == 'doc.class' - or doc.type == 'doc.alias' then + if doc.type == 'doc.alias' then local name = guide.getKeyName(doc) if not cache[name] then local docs = vm.getDocDefines(name) diff --git a/script/core/diagnostics/init.lua b/script/core/diagnostics/init.lua index 63a1bcf0..4950900b 100644 --- a/script/core/diagnostics/init.lua +++ b/script/core/diagnostics/init.lua @@ -64,6 +64,7 @@ local function check(uri, name, results) end end +---@async return function (uri, response) local ast = files.getState(uri) if not ast then diff --git a/script/core/diagnostics/not-yieldable.lua b/script/core/diagnostics/not-yieldable.lua new file mode 100644 index 00000000..612b5b98 --- /dev/null +++ b/script/core/diagnostics/not-yieldable.lua @@ -0,0 +1,63 @@ +local files = require 'files' +local await = require 'await' +local guide = require 'parser.guide' +local vm = require 'vm' +local lang = require 'language' +local infer = require 'core.infer' + +local function isYieldAble(defs, i) + local hasFuncDef + for _, def in ipairs(defs) do + if def.type == 'function' then + hasFuncDef = true + local arg = def.args and def.args[i] + if arg then + if infer.hasType(arg, 'any') + or vm.isAsync(arg, true) then + return true + end + end + end + if def.type == 'doc.type.function' then + hasFuncDef = true + local arg = def.args and def.args[i] + if arg then + if infer.hasType(arg.extends, 'any') + or vm.isAsync(arg.extends, true) then + return true + end + end + end + end + return not hasFuncDef +end + +---@async +return function (uri, callback) + local state = files.getState(uri) + if not state then + return + end + + guide.eachSourceType(state.ast, 'call', function (source) ---@async + if not source.args then + return + end + await.delay() + local defs = vm.getDefs(source.node) + if #defs == 0 then + return + end + for i, arg in ipairs(source.args) do + if vm.isAsync(arg, true) + and not vm.isLinkedCall(source.node, i) + and not isYieldAble(defs, i) then + callback { + start = arg.start, + finish = arg.finish, + message = lang.script('DIAG_NOT_YIELDABLE', i), + } + end + end + end) +end diff --git a/script/core/diagnostics/redundant-value.lua b/script/core/diagnostics/redundant-value.lua index 4c913330..6f60303b 100644 --- a/script/core/diagnostics/redundant-value.lua +++ b/script/core/diagnostics/redundant-value.lua @@ -4,13 +4,14 @@ local lang = require 'language' local guide = require 'parser.guide' local await = require 'await' +---@async return function (uri, callback) local state = files.getState(uri) if not state then return end - guide.eachSource(state.ast, function (src) + guide.eachSource(state.ast, function (src) ---@async await.delay() if src.redundant then callback { diff --git a/script/core/diagnostics/type-check.lua b/script/core/diagnostics/type-check.lua index 11678b38..8728b169 100644 --- a/script/core/diagnostics/type-check.lua +++ b/script/core/diagnostics/type-check.lua @@ -23,6 +23,9 @@ local typeNameMap = { } local function isTable(name) + if type(name) ~= 'string' then + return + end if tableMap[name] ---table<K: number, V: string> table or tableMap[name:sub(1, 5)] @@ -84,7 +87,8 @@ local function compatibleType(param, args) if param[1] and param[1]:sub(1,1) == '"' then return true end - elseif isTable(v.type or v[1]) and isTable(param[1] or param.type) then + elseif (isTable(v.type) or isTable(v[1])) + and (isTable(param[1]) or isTable(param.type)) then return true end end @@ -114,14 +118,16 @@ end local function addFatherClass(infers) for k in pairs(infers) do - local docDefs = vm.getDocDefines(k) - for _, doc in ipairs(docDefs) do - if doc.parent - and doc.parent.type == 'doc.class' - and doc.parent.extends then - for _, tp in ipairs(doc.parent.extends) do - if tp.type == 'doc.extends.name' then - infers[tp[1]] = true + if type(k) == 'string' then + local docDefs = vm.getDocDefines(k) + for _, doc in ipairs(docDefs) do + if doc.parent + and doc.parent.type == 'doc.class' + and doc.parent.extends then + for _, tp in ipairs(doc.parent.extends) do + if tp.type == 'doc.extends.name' then + infers[tp[1]] = true + end end end end @@ -238,9 +244,6 @@ local function getInfoFromDefs(defs) and isClassOralias(v.type) then plusAlias[#plusAlias+1] = v end - if not v[1] or not v.type then - log.warn('type-check: if not v[1] or not v.type') - end end plusAlias[#plusAlias+1] = types[i] end @@ -409,12 +412,13 @@ local function matchParams(paramsTypes, i, arg) return false, messages end +---@async return function (uri, callback) local ast = files.getState(uri) if not ast then return end - guide.eachSourceType(ast.ast, 'call', function (source) + guide.eachSourceType(ast.ast, 'call', function (source) ---@async if not source.args then return end diff --git a/script/core/diagnostics/undefined-doc-param.lua b/script/core/diagnostics/undefined-doc-param.lua index 6140b4f0..86bf3871 100644 --- a/script/core/diagnostics/undefined-doc-param.lua +++ b/script/core/diagnostics/undefined-doc-param.lua @@ -12,6 +12,9 @@ local function hasParamName(func, name) if arg[1] == name then return true end + if arg.type == '...' and name == '...' then + return true + end end return false end diff --git a/script/core/diagnostics/undefined-field.lua b/script/core/diagnostics/undefined-field.lua index ff02728f..2e64f0cc 100644 --- a/script/core/diagnostics/undefined-field.lua +++ b/script/core/diagnostics/undefined-field.lua @@ -18,6 +18,7 @@ local SkipCheckClass = { ['lightuserdata'] = true, } +---@async return function (uri, callback) local ast = files.getState(uri) if not ast then @@ -26,6 +27,7 @@ return function (uri, callback) local cache = {} + ---@async local function checkUndefinedField(src) local id = noder.getID(src) if not id then diff --git a/script/core/diagnostics/undefined-global.lua b/script/core/diagnostics/undefined-global.lua index 14754c16..48c8a226 100644 --- a/script/core/diagnostics/undefined-global.lua +++ b/script/core/diagnostics/undefined-global.lua @@ -14,6 +14,7 @@ local requireLike = { ['load'] = true, } +---@async return function (uri, callback) local ast = files.getState(uri) if not ast then @@ -21,7 +22,7 @@ return function (uri, callback) end -- 遍历全局变量,检查所有没有 set 模式的全局变量 - guide.eachSourceType(ast.ast, 'getglobal', function (src) + guide.eachSourceType(ast.ast, 'getglobal', function (src) ---@async local key = src[1] if not key then return diff --git a/script/core/diagnostics/unused-function.lua b/script/core/diagnostics/unused-function.lua index 8f6ccaac..0e0c0003 100644 --- a/script/core/diagnostics/unused-function.lua +++ b/script/core/diagnostics/unused-function.lua @@ -18,6 +18,7 @@ local function isToBeClosed(source) return false end +---@async return function (uri, callback) local ast = files.getState(uri) if not ast then @@ -25,6 +26,7 @@ return function (uri, callback) end local cache = {} + ---@async local function checkFunction(source) if cache[source] ~= nil then return cache[source] @@ -81,7 +83,7 @@ return function (uri, callback) end -- 只检查局部函数 - guide.eachSourceType(ast.ast, 'function', function (source) + guide.eachSourceType(ast.ast, 'function', function (source) ---@async checkFunction(source) end) end diff --git a/script/core/document-symbol.lua b/script/core/document-symbol.lua index cfabedab..07794e60 100644 --- a/script/core/document-symbol.lua +++ b/script/core/document-symbol.lua @@ -219,6 +219,7 @@ local function buildAnonymousFunction(source, text, used, symbols) } end +---@async local function buildSource(source, text, used, symbols) if source.type == 'local' or source.type == 'setlocal' @@ -234,6 +235,7 @@ local function buildSource(source, text, used, symbols) end end +---@async local function makeSymbol(uri) local ast = files.getState(uri) local text = files.getText(uri) @@ -243,7 +245,7 @@ local function makeSymbol(uri) local symbols = {} local used = {} - guide.eachSource(ast.ast, function (source) + guide.eachSource(ast.ast, function (source) ---@async buildSource(source, text, used, symbols) end) @@ -279,6 +281,7 @@ local function packChild(symbols) return root end +---@async local function packSymbols(symbols) await.delay() table.sort(symbols, function (a, b) @@ -291,6 +294,7 @@ local function packSymbols(symbols) return packChild(symbols) end +---@async return function (uri) local symbols = makeSymbol(uri) if not symbols then diff --git a/script/core/folding.lua b/script/core/folding.lua index bd3ba5f3..2cf06d46 100644 --- a/script/core/folding.lua +++ b/script/core/folding.lua @@ -145,6 +145,7 @@ local Care = { end, } +---@async return function (uri) local state = files.getState(uri) local text = files.getText(uri) @@ -154,7 +155,7 @@ return function (uri) local regions = {} local status = {} - guide.eachSource(state.ast, function (source) + guide.eachSource(state.ast, function (source) ---@async local tp = source.type if Care[tp] then await.delay() diff --git a/script/core/generic.lua b/script/core/generic.lua index 92e97362..b383ee5d 100644 --- a/script/core/generic.lua +++ b/script/core/generic.lua @@ -7,7 +7,7 @@ local noder = require "core.noder" ---@field proto parser.guide.object ---@field parent parser.guide.object ----@class generic.closure +---@class generic.closure: parser.guide.object ---@field type string ---@field proto parser.guide.object ---@field upvalues table<string, generic.value[]> diff --git a/script/core/hint.lua b/script/core/hint.lua index 62d2f7bf..42390443 100644 --- a/script/core/hint.lua +++ b/script/core/hint.lua @@ -6,13 +6,14 @@ local guide = require 'parser.guide' local await = require 'await' local define = require 'proto.define' +---@async local function typeHint(uri, results, start, finish) - local ast = files.getState(uri) - if not ast then + local state = files.getState(uri) + if not state then return end local mark = {} - guide.eachSourceBetween(ast.ast, start, finish, function (source) + guide.eachSourceBetween(state.ast, start, finish, function (source) ---@async if source.type ~= 'local' and source.type ~= 'setglobal' and source.type ~= 'tablefield' @@ -96,17 +97,18 @@ local function hasLiteralArgInCall(call) return false end +---@async local function paramName(uri, results, start, finish) local paramConfig = config.get 'Lua.hint.paramName' if not paramConfig or paramConfig == 'None' then return end - local ast = files.getState(uri) - if not ast then + local state = files.getState(uri) + if not state then return end local mark = {} - guide.eachSourceBetween(ast.ast, start, finish, function (source) + guide.eachSourceBetween(state.ast, start, finish, function (source) ---@async if source.type ~= 'call' then return end @@ -158,9 +160,39 @@ local function paramName(uri, results, start, finish) end) end +---@async +local function awaitHint(uri, results, start, finish) + local awaitConfig = config.get 'Lua.hint.await' + if not awaitConfig then + return + end + local state = files.getState(uri) + if not state then + return + end + guide.eachSourceBetween(state.ast, start, finish, function (source) ---@async + if source.type ~= 'call' then + return + end + await.delay() + local node = source.node + if not vm.isAsyncCall(source) then + return + end + results[#results+1] = { + text = 'await ', + offset = node.start, + kind = define.InlayHintKind.Other, + where = 'left', + } + end) +end + +---@async return function (uri, start, finish) local results = {} typeHint(uri, results, start, finish) + awaitHint(uri, results, start, finish) paramName(uri, results, start, finish) return results end diff --git a/script/core/hover/arg.lua b/script/core/hover/arg.lua index 822be2b6..4e6a1ace 100644 --- a/script/core/hover/arg.lua +++ b/script/core/hover/arg.lua @@ -38,7 +38,10 @@ local function asFunction(source, oop) infer.searchAndViewInfers(arg) ) elseif arg.type == '...' then - args[#args+1] = '...' + args[#args+1] = ('%s: %s'):format( + '...', + infer.searchAndViewInfers(arg) + ) else args[#args+1] = ('%s'):format(infer.searchAndViewInfers(arg)) end @@ -60,18 +63,11 @@ local function asDocFunction(source) for i = 1, #source.args do local arg = source.args[i] local name = arg.name[1] - if arg.extends then - args[i] = ('%s%s: %s'):format( - name, - arg.optional and '?' or '', - infer.searchAndViewInfers(arg.extends) - ) - else - args[i] = ('%s%s'):format( - name, - arg.optional and '?' or '' - ) - end + args[i] = ('%s%s: %s'):format( + name, + arg.optional and '?' or '', + arg.extends and infer.searchAndViewInfers(arg.extends) or 'any' + ) end return table.concat(args, ', ') end diff --git a/script/core/hover/description.lua b/script/core/hover/description.lua index 576ed42a..725ea7a5 100644 --- a/script/core/hover/description.lua +++ b/script/core/hover/description.lua @@ -31,8 +31,6 @@ local function collectRequire(mode, literal) if vm.isMetaFile(uri) then shows[i] = ('* [[meta]](%s)'):format(uri) elseif searcher then - searcher = searcher:sub(#rootPath + 1) - searcher = ws.normalize(searcher) searcher = searcher:gsub('^[/\\]+', '') shows[i] = ('* [%s](%s) %s'):format(path, uri, lang.script('HOVER_USE_LUA_PATH', searcher)) else diff --git a/script/core/hover/init.lua b/script/core/hover/init.lua index 784ef75d..7d99a006 100644 --- a/script/core/hover/init.lua +++ b/script/core/hover/init.lua @@ -7,12 +7,14 @@ local findSource = require 'core.find-source' local markdown = require 'provider.markdown' local infer = require 'core.infer' +---@async local function getHover(source) local md = markdown() local defMark = {} local labelMark = {} local descMark = {} + ---@async local function addHover(def, checkLable) if defMark[def] then return @@ -37,12 +39,17 @@ local function getHover(source) end if infer.searchAndViewInfers(source) == 'function' then + local hasFunc for _, def in ipairs(vm.getDefs(source)) do if def.type == 'function' or def.type == 'doc.type.function' then + hasFunc = true addHover(def, true) end end + if not hasFunc then + addHover(source, true) + end else addHover(source, true) for _, def in ipairs(vm.getDefs(source)) do @@ -74,6 +81,7 @@ local accept = { ['doc.module'] = true, } +---@async local function getHoverByUri(uri, position) local ast = files.getState(uri) if not ast then diff --git a/script/core/hover/label.lua b/script/core/hover/label.lua index 8906d54d..0bb4fe89 100644 --- a/script/core/hover/label.lua +++ b/script/core/hover/label.lua @@ -16,7 +16,11 @@ local function asFunction(source, oop) local arg = buildArg(source, oop) local rtn = buildReturn(source) local lines = {} - lines[1] = ('%s %s(%s)'):format(oop and 'method' or 'function', name or '', arg) + lines[1] = ('%s%s %s(%s)'):format( + vm.isAsync(source) and 'async ' or '', + oop and 'method' or 'function', + name or '', arg + ) lines[2] = rtn return table.concat(lines, '\n') end @@ -44,6 +48,7 @@ local function asDocTypeName(source) end end +---@async local function asValue(source, title) local name = buildName(source, false) or '' local type = infer.searchAndViewInfers(source) @@ -59,6 +64,9 @@ local function asValue(source, title) local pack = {} pack[#pack+1] = title pack[#pack+1] = name .. ':' + if vm.isAsync(source, true) then + pack[#pack+1] = 'async' + end if cont and ( type == 'table' or type == 'any' @@ -76,10 +84,12 @@ local function asValue(source, title) return table.concat(pack, ' ') end +---@async local function asLocal(source) return asValue(source, 'local') end +---@async local function asGlobal(source) return asValue(source, 'global') end @@ -111,6 +121,7 @@ local function isGlobalField(source) end end +---@async local function asField(source) if isGlobalField(source) then return asGlobal(source) @@ -175,6 +186,7 @@ local function asNumber(source) return formatNumber(num) end +---@async return function (source, oop) if source.type == 'function' then return asFunction(source, oop) diff --git a/script/core/hover/table.lua b/script/core/hover/table.lua index 68a04b40..285d5c02 100644 --- a/script/core/hover/table.lua +++ b/script/core/hover/table.lua @@ -134,6 +134,7 @@ local function getOptionalMap(fields) return optionals end +---@async return function (source) local maxFields = config.get 'Lua.hover.previewFields' if maxFields <= 0 then diff --git a/script/core/infer.lua b/script/core/infer.lua index 2825578e..8da35289 100644 --- a/script/core/infer.lua +++ b/script/core/infer.lua @@ -385,7 +385,7 @@ function m.viewDocFunction(doc) end local args = {} for i, arg in ipairs(doc.args) do - args[i] = ('%s: %s'):format(arg.name[1], m.viewDocName(arg.extends)) + args[i] = ('%s: %s'):format(arg.name[1], arg.extends and m.viewDocName(arg.extends) or 'any') end local label = ('fun(%s)'):format(table.concat(args, ', ')) if #doc.returns > 0 then diff --git a/script/core/noder.lua b/script/core/noder.lua index 59c103e8..374d2c9b 100644 --- a/script/core/noder.lua +++ b/script/core/noder.lua @@ -1188,46 +1188,6 @@ compileNodeMap = util.switch() : call(function (noders, id, source) local uri = guide.getUri(source) collector.subscribe(uri, id, noders) - - local parent = source.parent - if parent.type ~= 'doc.type' then - goto BREAK - end - local bindSources = parent.bindSources - if not bindSources then - goto BREAK - end - for i = 1, #bindSources do - local src = bindSources[i] - if src.type ~= 'local' then - goto CONTINUE1 - end - local refs = src.ref - if not refs then - goto CONTINUE1 - end - for j = 1, #refs do - local ref = refs[j] - if ref.type ~= 'getlocal' then - goto CONTINUE2 - end - local nxt = ref.next - if not nxt then - goto CONTINUE2 - end - local nxtType = nxt.type - if nxtType == 'setfield' - or nxtType == 'setmethod' - or nxtType == 'setindex' then - local defID = 'def:' .. id - collector.subscribe(uri, defID, noders) - goto BREAK - end - ::CONTINUE2:: - end - ::CONTINUE1:: - end - ::BREAK:: end) : case 'doc.class.name' : case 'doc.alias.name' diff --git a/script/core/semantic-tokens.lua b/script/core/semantic-tokens.lua index dc0649d1..8389cbb4 100644 --- a/script/core/semantic-tokens.lua +++ b/script/core/semantic-tokens.lua @@ -373,6 +373,7 @@ config.watch(function (key, value) end end) +---@async return function (uri, start, finish) local state = files.getState(uri) local text = files.getText(uri) @@ -381,7 +382,7 @@ return function (uri, start, finish) end local results = {} - guide.eachSourceBetween(state.ast, start, finish, function (source) + guide.eachSourceBetween(state.ast, start, finish, function (source) ---@async local method = Care[source.type] if not method then return diff --git a/script/core/signature.lua b/script/core/signature.lua index 007a3787..d55866f5 100644 --- a/script/core/signature.lua +++ b/script/core/signature.lua @@ -39,6 +39,7 @@ local function findNearCall(uri, ast, pos) return nearCall end +---@async local function makeOneSignature(source, oop, index) local label = hoverLabel(source, oop) -- 去掉返回值 @@ -65,7 +66,7 @@ local function makeOneSignature(source, oop, index) if index > i and i > 0 then local lastLabel = params[i].label local text = label:sub(lastLabel[1] + 1, lastLabel[2]) - if text == '...' then + if text:sub(1, 3) == '...' then index = i end end @@ -77,6 +78,7 @@ local function makeOneSignature(source, oop, index) } end +---@async local function makeSignatures(text, call, pos) local node = call.node local oop = node.type == 'method' @@ -136,6 +138,7 @@ local function makeSignatures(text, call, pos) return signs end +---@async return function (uri, pos) local state = files.getState(uri) if not state then diff --git a/script/core/workspace-symbol.lua b/script/core/workspace-symbol.lua index 265a8d92..5fb4a741 100644 --- a/script/core/workspace-symbol.lua +++ b/script/core/workspace-symbol.lua @@ -57,6 +57,7 @@ local function searchFile(uri, key, results) end) end +---@async return function (key) local results = {} diff --git a/script/files.lua b/script/files.lua index 4d649b29..a779f0f6 100644 --- a/script/files.lua +++ b/script/files.lua @@ -120,16 +120,22 @@ end --- 设置文件文本 ---@param uri uri ---@param text string -function m.setText(uri, text, isTrust, instance) +---@param isTrust boolean +---@param version integer +function m.setText(uri, text, isTrust, version) if not text then return end + if #text > 1024 * 1024 * 10 then + local client = require 'client' + client.showMessage('Warning', lang.script('WORKSPACE_SKIP_HUGE_FILE', uri)) + return + end --log.debug('setText', uri) local create if not m.fileMap[uri] then m.fileMap[uri] = { uri = uri, - version = 0, } m.fileCount = m.fileCount + 1 create = true @@ -154,7 +160,7 @@ function m.setText(uri, text, isTrust, instance) m.astMap[uri] = nil file.cache = {} file.cacheActiveTime = math.huge - file.version = file.version + 1 + file.version = version m.globalVersion = m.globalVersion + 1 await.close('files.version') m.onWatch('version') @@ -457,6 +463,7 @@ function m.compileState(uri, text) --await.delay() if state then state.uri = uri + state.lua = text state.ast.uri = uri local clock = os.clock() parser.luadoc(state) @@ -488,14 +495,14 @@ function m.getState(uri) if not file then return nil end - local ast = m.astMap[uri] - if not ast then - ast = m.compileState(uri, file.text) - m.astMap[uri] = ast + local state = m.astMap[uri] + if not state then + state = m.compileState(uri, file.text) + m.astMap[uri] = state --await.delay() end file.cacheActiveTime = timer.clock() - return ast + return state end ---设置文件的当前可见范围 @@ -703,13 +710,16 @@ function m.getDllWords(uri) end --- 注册事件 +---@param callback async fun(ev: string, uri: uri) function m.watch(callback) m.watchList[#m.watchList+1] = callback end -function m.onWatch(ev, ...) +function m.onWatch(ev, uri) for _, callback in ipairs(m.watchList) do - xpcall(callback, log.error, ev, ...) + await.call(function () + callback(ev, uri) + end) end end diff --git a/script/filewatch.lua b/script/filewatch.lua index e2050dd9..dd46d1a3 100644 --- a/script/filewatch.lua +++ b/script/filewatch.lua @@ -30,6 +30,7 @@ function m.watch(path) end end +---@param callback async fun() function m.event(callback) m._eventList[#m._eventList+1] = callback end diff --git a/script/glob/gitignore.lua b/script/glob/gitignore.lua index 73e20cbf..09be1415 100644 --- a/script/glob/gitignore.lua +++ b/script/glob/gitignore.lua @@ -163,6 +163,8 @@ function mt:getRelativePath(path) return path end +---@param callback async fun() +---@async function mt:scan(path, callback) local files = {} if type(callback) ~= 'function' then @@ -170,6 +172,7 @@ function mt:scan(path, callback) end local list = {} + ---@async local function check(current) local fileType = self:callInterface('type', current) if fileType == 'file' then diff --git a/script/global.d.lua b/script/global.d.lua index cc0aafbd..0435293e 100644 --- a/script/global.d.lua +++ b/script/global.d.lua @@ -11,7 +11,7 @@ DBGPORT = 0 DBGWAIT = false ---displayed language, use command line: --locale="en-us" ----@type '"en-us"'|'"zh-cn"' +---@type '"en-us"'|'"zh-cn"'|'"pt-br"' LOCALE = 'en-us' ---path of local config file, use command line: --configpath="config.lua" @@ -27,4 +27,9 @@ SHOWSOURCE = false TRACE = false ---trace searching with `too deep!` into log, use command line: --footprint=true +---@type boolean FOOTPRINT = false + +---trace rpc, use command line: --rpclog=true +---@type boolean +RPCLOG = false diff --git a/script/library.lua b/script/library.lua index 943e6d2d..81242a91 100644 --- a/script/library.lua +++ b/script/library.lua @@ -341,6 +341,7 @@ local function apply3rd(cfg, onlyMemory) end local hasAsked +---@async local function askFor3rd(cfg) hasAsked = true local yes1 = lang.script.WINDOW_APPLY_WHIT_SETTING @@ -395,7 +396,7 @@ local function check3rdByWords(text, configs) if hasAsked then return end - await.call(function () + await.call(function () ---@async for _, cfg in ipairs(configs) do if cfg.words then for _, word in ipairs(cfg.words) do @@ -419,7 +420,7 @@ local function check3rdByFileName(uri, configs) if not path then return end - await.call(function () + await.call(function () ---@async for _, cfg in ipairs(configs) do if cfg.files then for _, filename in ipairs(cfg.files) do diff --git a/script/parser/luadoc.lua b/script/parser/luadoc.lua index e21e7c19..d206e6d7 100644 --- a/script/parser/luadoc.lua +++ b/script/parser/luadoc.lua @@ -10,9 +10,8 @@ local Parser = re.compile([[ Main <- (Token / Sp)* Sp <- %s+ X16 <- [a-fA-F0-9] -Word <- [a-zA-Z0-9_] Token <- Integer / Name / String / Symbol -Name <- ({} {[a-zA-Z_0-9] [a-zA-Z0-9_.*-]*} {}) +Name <- ({} {%name} {}) -> Name Integer <- ({} {[0-9]+} !'.' {}) -> Integer @@ -45,7 +44,7 @@ EChar <- 'a' -> ea / ('z' (%nl / %s)*) -> '' / ('x' {X16 X16}) -> Char16 / ([0-9] [0-9]? [0-9]?) -> Char10 - / ('u{' {Word*} '}') -> CharUtf8 + / ('u{' {X16*} '}') -> CharUtf8 Symbol <- ({} { [:|,<>()?+#`{}] / '[]' @@ -63,6 +62,7 @@ Symbol <- ({} { er = '\r', et = '\t', ev = '\v', + name = (m.R('az', 'AZ', '09', '\x80\xff') + m.S('_')) * (m.R('az', 'AZ', '__', '09', '\x80\xff') + m.S('_.*-'))^0, Char10 = function (char) char = tonumber(char) if not char or char < 0 or char > 255 then @@ -342,6 +342,21 @@ local function parseTypeUnitTable(parent, node) return result end +local function parseDots(tp, parent) + if not checkToken('symbol', '...', 1) then + return + end + nextToken() + local dots = { + type = tp, + start = getStart(), + finish = getFinish(), + parent = parent, + [1] = '...', + } + return dots +end + local function parseTypeUnitFunction() local typeUnit = { type = 'doc.type.function', @@ -361,47 +376,29 @@ local function parseTypeUnitFunction() type = 'doc.type.arg', parent = typeUnit, } - if checkToken('symbol', '...', 1) then - nextToken() - local vararg = { - type = 'doc.type.name', - start = getStart(), + arg.name = parseName('doc.type.name', arg) + or parseDots('doc.type.name', arg) + if not arg.name then + pushError { + type = 'LUADOC_MISS_ARG_NAME', + start = getFinish(), finish = getFinish(), - parent = arg, - [1] = '...', } - arg.name = vararg - if not arg.start then - arg.start = arg.name.start - end - arg.finish = getFinish() - else - arg.name = parseName('doc.type.name', arg) - if not arg.name then - pushError { - type = 'LUADOC_MISS_ARG_NAME', - start = getFinish(), - finish = getFinish(), - } - break - end - if not arg.start then - arg.start = arg.name.start - end - if checkToken('symbol', '?', 1) then - nextToken() - arg.optional = true - end - arg.finish = getFinish() - if not nextSymbolOrError(':') then - break - end + break + end + if not arg.start then + arg.start = arg.name.start + end + if checkToken('symbol', '?', 1) then + nextToken() + arg.optional = true + end + arg.finish = getFinish() + if checkToken('symbol', ':', 1) then + nextToken() arg.extends = parseType(arg) - if not arg.extends then - break - end - arg.finish = getFinish() end + arg.finish = getFinish() typeUnit.args[#typeUnit.args+1] = arg if checkToken('symbol', ',', 1) then nextToken() @@ -492,6 +489,19 @@ local function parseTypeUnitLiteralTable() end local function parseTypeUnit(parent, content) + if content == 'async' then + local tp, cont = peekToken() + if tp == 'name' then + if cont == 'fun' then + nextToken() + local func = parseTypeUnit(parent, cont) + if func then + func.async = true + return func + end + end + end + end local result if content == 'fun' then result = parseTypeUnitFunction() @@ -749,6 +759,7 @@ local function parseParam() type = 'doc.param', } result.param = parseName('doc.param.name', result) + or parseDots('doc.param.name', result) if not result.param then pushError { type = 'LUADOC_MISS_PARAM_NAME', @@ -1098,6 +1109,22 @@ local function parseModule() return result end +local function parseAsync() + return { + type = 'doc.async', + start = getFinish(), + finish = getFinish(), + } +end + +local function parseNoDiscard() + return { + type = 'doc.nodiscard', + start = getFinish(), + finish = getFinish(), + } +end + local function convertTokens() local tp, text = nextToken() if not tp then @@ -1141,6 +1168,10 @@ local function convertTokens() return parseDiagnostic() elseif text == 'module' then return parseModule() + elseif text == 'async' then + return parseAsync() + elseif text == 'nodiscard' then + return parseNoDiscard() end end diff --git a/script/parser/newparser.lua b/script/parser/newparser.lua index 0b721a7a..8eb97b17 100644 --- a/script/parser/newparser.lua +++ b/script/parser/newparser.lua @@ -487,7 +487,13 @@ end local function skipComment(isAction) local token = Tokens[Index + 1] if token == '--' - or (token == '//' and isAction) then + or ( + token == '//' + and ( + isAction + or State.options.nonstandardSymbol['//'] + ) + ) then local start = Tokens[Index] local left = getPosition(start, 'left') local chead = false @@ -1527,7 +1533,7 @@ local function parseTable() local index = 0 local wantSep = false while true do - skipSpace() + skipSpace(true) local token = Tokens[Index + 1] if token == '}' then Index = Index + 2 @@ -2094,6 +2100,7 @@ local function parseParams(params) start = getPosition(Tokens[Index], 'left'), finish = getPosition(Tokens[Index] + 2, 'right'), parent = params, + [1] = '...', } local chunk = Chunk[#Chunk] chunk.vararg = vararg @@ -2161,7 +2168,7 @@ local function parseFunction(isLocal, isAction) Index = Index + 2 local LastLocalCount = LocalCount LocalCount = 0 - skipSpace() + skipSpace(true) local hasLeftParen = Tokens[Index + 1] == '(' if not hasLeftParen then local name = parseName() @@ -2191,7 +2198,7 @@ local function parseFunction(isLocal, isAction) finish = simple.finish, } end - skipSpace() + skipSpace(true) hasLeftParen = Tokens[Index + 1] == '(' end end @@ -2222,7 +2229,7 @@ local function parseFunction(isLocal, isAction) params.parent = func func.args = params end - skipSpace() + skipSpace(true) if Tokens[Index + 1] == ')' then local parenRight = getPosition(Tokens[Index], 'right') func.finish = parenRight @@ -2230,7 +2237,7 @@ local function parseFunction(isLocal, isAction) params.finish = parenRight end Index = Index + 2 - skipSpace() + skipSpace(true) else func.finish = lastRightPosition() if params then @@ -2339,6 +2346,9 @@ local function parseBinaryOP(asAction, level) if not symbol then return nil end + if symbol == '//' and State.options.nonstandardSymbol['//'] then + return nil + end local myLevel = BinarySymbol[symbol] if level and myLevel < level then return nil @@ -3551,6 +3561,16 @@ function parseAction() name.vstart = exp.start name.range = exp.finish exp.parent = name + if name.type == 'setlocal' then + local loc = name.node + if loc.attrs then + pushError { + type = 'SET_CONST', + start = name.start, + finish = name.finish, + } + end + end pushActionIntoCurrentChunk(name) return name else diff --git a/script/plugin.lua b/script/plugin.lua index 26f39226..f56dc9f9 100644 --- a/script/plugin.lua +++ b/script/plugin.lua @@ -50,6 +50,7 @@ local function resetFiles() end end +---@async local function checkTrustLoad() local filePath = LOGPATH .. '/trusted' local trusted = util.loadFile(filePath) @@ -79,7 +80,7 @@ function m.init() return end m.hasInited = true - await.call(function () + await.call(function () ---@async local ws = require 'workspace' m.interface = {} @@ -100,7 +101,7 @@ function m.init() m.showError(err) return end - if not checkTrustLoad() then + if not client.isVSCode() and not checkTrustLoad() then return end local suc, err = xpcall(f, log.error, f) diff --git a/script/proto/converter.lua b/script/proto/converter.lua index cf6331f1..9c75f056 100644 --- a/script/proto/converter.lua +++ b/script/proto/converter.lua @@ -2,7 +2,6 @@ local guide = require 'parser.guide' local files = require 'files' local encoder = require 'encoder' --- TODO local offsetEncoding = 'utf16' local m = {} @@ -178,4 +177,8 @@ function m.textEdit(range, newtext) } end +function m.setOffsetEncoding(encoding) + offsetEncoding = encoding:lower():gsub('%-', '') +end + return m diff --git a/script/proto/define.lua b/script/proto/define.lua index 713857af..dbb6ba85 100644 --- a/script/proto/define.lua +++ b/script/proto/define.lua @@ -44,6 +44,9 @@ m.DiagnosticDefaultSeverity = { ['no-implicit-any'] = 'Information', ['deprecated'] = 'Warning', ['different-requires'] = 'Warning', + ['await-in-sync'] = 'Warning', + ['not-yieldable'] = 'Warning', + ['discard-returns'] = 'Warning', ['type-check'] = 'Warning', ['duplicate-doc-class'] = 'Warning', @@ -98,6 +101,9 @@ m.DiagnosticDefaultNeededFileStatus = { ['no-implicit-any'] = 'None', ['deprecated'] = 'Opened', ['different-requires'] = 'Any', + ['await-in-sync'] = 'None', + ['not-yieldable'] = 'None', + ['discard-returns'] = 'Opened', ['type-check'] = 'None', ['duplicate-doc-class'] = 'Any', diff --git a/script/proto/proto.lua b/script/proto/proto.lua index e380f54f..7cdc461c 100644 --- a/script/proto/proto.lua +++ b/script/proto/proto.lua @@ -8,6 +8,20 @@ local json = require 'json' local reqCounter = util.counter() +local function logSend(buf) + if not RPCLOG then + return + end + log.debug('rpc send:', buf) +end + +local function logRecieve(proto) + if not RPCLOG then + return + end + log.debug('rpc recieve:', json.encode(proto)) +end + local m = {} m.ability = {} @@ -22,6 +36,7 @@ function m.getMethodName(proto) end end +---@param callback async fun() function m.on(method, callback) m.ability[method] = callback end @@ -38,6 +53,7 @@ function m.response(id, res) data.result = res == nil and json.null or res local buf = jsonrpc.encode(data) --log.debug('Response', id, #buf) + logSend(buf) io.write(buf) end @@ -56,6 +72,7 @@ function m.responseErr(id, code, message) } } --log.debug('ResponseErr', id, #buf) + logSend(buf) io.write(buf) end @@ -65,9 +82,11 @@ function m.notify(name, params) params = params, } --log.debug('Notify', name, #buf) + logSend(buf) io.write(buf) end +---@async function m.awaitRequest(name, params) local id = reqCounter() local buf = jsonrpc.encode { @@ -76,6 +95,7 @@ function m.awaitRequest(name, params) params = params, } --log.debug('Request', name, #buf) + logSend(buf) io.write(buf) local result, error = await.wait(function (resume) m.waiting[id] = resume @@ -94,6 +114,7 @@ function m.request(name, params, callback) params = params, } --log.debug('Request', name, #buf) + logSend(buf) io.write(buf) m.waiting[id] = function (result, error) if error then @@ -106,6 +127,7 @@ function m.request(name, params, callback) end function m.doMethod(proto) + logRecieve(proto) local method, optional = m.getMethodName(proto) local abil = m.ability[method] if not abil then @@ -120,7 +142,7 @@ function m.doMethod(proto) if proto.id then m.holdon[proto.id] = proto end - await.call(function () + await.call(function () ---@async --log.debug('Start method:', method) if proto.id then await.setID('proto:' .. proto.id) @@ -146,6 +168,7 @@ function m.doMethod(proto) end end ok, res = xpcall(abil, log.error, proto.params) + await.delay() end) end @@ -159,6 +182,7 @@ function m.close(id, reason) end function m.doResponse(proto) + logRecieve(proto) local id = proto.id local resume = m.waiting[id] if not resume then diff --git a/script/provider/capability.lua b/script/provider/capability.lua index 76cadc0d..b712defc 100644 --- a/script/provider/capability.lua +++ b/script/provider/capability.lua @@ -48,6 +48,7 @@ end function m.getIniter() local initer = { + offsetEncoding = client.getOffsetEncoding(), -- 文本同步方式 textDocumentSync = { -- 打开关闭文本时通知 diff --git a/script/provider/diagnostic.lua b/script/provider/diagnostic.lua index 492b3048..ca4bcdb8 100644 --- a/script/provider/diagnostic.lua +++ b/script/provider/diagnostic.lua @@ -11,6 +11,7 @@ local progress = require "progress" local client = require 'client' local converter = require 'proto.converter' +---@class diagnosticProvider local m = {} m._start = false m.cache = {} @@ -139,6 +140,10 @@ function m.clear(uri) log.debug('clearDiagnostics', uri) end +function m.clearCache(uri) + m.cache[uri] = false +end + function m.clearAll() for luri in pairs(m.cache) do m.clear(luri) @@ -152,11 +157,13 @@ function m.syntaxErrors(uri, ast) local results = {} - for _, err in ipairs(ast.errs) do - if not config.get 'Lua.diagnostics.disable'[err.type:lower():gsub('_', '-')] then - results[#results+1] = buildSyntaxError(uri, err) + pcall(function () + for _, err in ipairs(ast.errs) do + if not config.get 'Lua.diagnostics.disable'[err.type:lower():gsub('_', '-')] then + results[#results+1] = buildSyntaxError(uri, err) + end end - end + end) return results end @@ -180,6 +187,7 @@ function m.diagnostics(uri, diags) end) end +---@async function m.doDiagnostic(uri) if not config.get 'Lua.diagnostics.enable' then return @@ -213,6 +221,8 @@ function m.doDiagnostic(uri) return end + local version = files.getVersion(uri) + await.setID('diag:' .. uri) local prog <close> = progress.create(lang.script.WINDOW_DIAGNOSING, 0.5) @@ -236,6 +246,7 @@ function m.doDiagnostic(uri) proto.notify('textDocument/publishDiagnostics', { uri = uri, + version = version, diagnostics = full, }) if #full > 0 then @@ -265,15 +276,17 @@ function m.refresh(uri) return end await.close('diag:' .. uri) - await.call(function () + await.call(function () ---@async await.delay() if uri then - m.doDiagnostic(uri) + m.clearCache(uri) + xpcall(m.doDiagnostic, log.error, uri) end m.diagnosticsAll() end, 'files.version') end +---@async local function askForDisable() if m.dontAskedForDisable then return @@ -332,7 +345,7 @@ function m.diagnosticsAll(force) return end await.close 'diagnosticsAll' - await.call(function () + await.call(function () ---@async await.sleep(delay) m.diagnosticsAllClock = os.clock() local clock = os.clock() @@ -347,7 +360,7 @@ function m.diagnosticsAll(force) for i, uri in ipairs(uris) do bar:setMessage(('%d/%d'):format(i, #uris)) bar:setPercentage(i / #uris * 100) - m.doDiagnostic(uri) + xpcall(m.doDiagnostic, log.error, uri) await.delay() if cancelled then log.debug('Break workspace diagnostics') @@ -375,6 +388,7 @@ function m.checkStepResult() end end +---@async function m.checkWorkspaceDiag() if not await.hasID 'diagnosticsAll' then return @@ -400,7 +414,7 @@ function m.checkWorkspaceDiag() return false end -files.watch(function (ev, uri) +files.watch(function (ev, uri) ---@async if ev == 'remove' then m.clear(uri) m.refresh(uri) @@ -410,7 +424,7 @@ files.watch(function (ev, uri) end elseif ev == 'open' then if ws.isReady() then - m.doDiagnostic(uri) + xpcall(m.doDiagnostic, log.error, uri) end elseif ev == 'close' then if files.isLibrary(uri) @@ -420,7 +434,7 @@ files.watch(function (ev, uri) end end) -await.watch(function (ev, co) +await.watch(function (ev, co) ---@async if ev == 'delay' then if m.checkStepResult then m.checkStepResult() diff --git a/script/provider/provider.lua b/script/provider/provider.lua index 8932c373..d732f3c2 100644 --- a/script/provider/provider.lua +++ b/script/provider/provider.lua @@ -16,6 +16,7 @@ local cfgLoader = require 'config.loader' local converter = require 'proto.converter' local filewatch = require 'filewatch' +---@async local function updateConfig() local new if CONFIGPATH then @@ -43,7 +44,19 @@ local function updateConfig() log.debug('loaded config dump:', util.dump(new)) end -filewatch.event(function (changes) +---@class provider +local m = {} + +m.attributes = {} + +function m.register(method) + return function (attrs) + m.attributes[method] = attrs + proto.on(method, attrs[1]) + end +end + +filewatch.event(function (changes) ---@async local configPath = workspace.getAbsolutePath(CONFIGPATH or '.luarc.json') if not configPath then return @@ -56,777 +69,886 @@ filewatch.event(function (changes) end end) -proto.on('initialize', function (params) - client.init(params) - config.init() - workspace.initPath(params.rootUri) - return { - capabilities = cap.getIniter(), - serverInfo = { - name = 'sumneko.lua', - }, - } -end) - -proto.on('initialized', function (params) - files.init() - local _ <close> = progress.create(lang.script.WINDOW_INITIALIZING, 0.5) - updateConfig() - local registrations = {} - - if client.getAbility 'workspace.didChangeConfiguration.dynamicRegistration' then - -- 监视配置变化 - registrations[#registrations+1] = { - id = 'workspace/didChangeConfiguration', - method = 'workspace/didChangeConfiguration', +m.register 'initialize' { + function (params) + client.init(params) + config.init() + workspace.initPath(params.rootUri) + return { + capabilities = cap.getIniter(), + serverInfo = { + name = 'sumneko.lua', + }, } end +} - if #registrations ~= 0 then - proto.awaitRequest('client/registerCapability', { - registrations = registrations - }) - end - library.init() - workspace.init() - return true -end) +m.register 'initialized'{ + ---@async + function (params) + files.init() + local _ <close> = progress.create(lang.script.WINDOW_INITIALIZING, 0.5) + updateConfig() + local registrations = {} -proto.on('exit', function () - log.info('Server exited.') - os.exit(true) -end) + if client.getAbility 'workspace.didChangeConfiguration.dynamicRegistration' then + -- 监视配置变化 + registrations[#registrations+1] = { + id = 'workspace/didChangeConfiguration', + method = 'workspace/didChangeConfiguration', + } + end -proto.on('shutdown', function () - log.info('Server shutdown.') - return true -end) + if #registrations ~= 0 then + proto.awaitRequest('client/registerCapability', { + registrations = registrations + }) + end + library.init() + workspace.init() + return true + end +} -proto.on('workspace/didChangeConfiguration', function () - if CONFIGPATH then - return +m.register 'exit' { + function () + log.info('Server exited.') + os.exit(true) end - updateConfig() -end) +} -proto.on('workspace/didCreateFiles', function (params) - log.debug('workspace/didCreateFiles', util.dump(params)) - for _, file in ipairs(params.files) do - if workspace.isValidLuaUri(file.uri) then - files.setText(file.uri, pub.awaitTask('loadFile', file.uri), false) - end +m.register 'shutdown' { + function () + log.info('Server shutdown.') + return true end -end) +} -proto.on('workspace/didDeleteFiles', function (params) - log.debug('workspace/didDeleteFiles', util.dump(params)) - for _, file in ipairs(params.files) do - files.remove(file.uri) - local childs = files.getChildFiles(file.uri) - for _, uri in ipairs(childs) do - log.debug('workspace/didDeleteFiles#child', uri) - files.remove(uri) +m.register 'workspace/didChangeConfiguration' { + function () ---@async + if CONFIGPATH then + return end + updateConfig() end -end) +} -proto.on('workspace/didRenameFiles', function (params) - log.debug('workspace/didRenameFiles', util.dump(params)) - for _, file in ipairs(params.files) do - local text = files.getOriginText(file.oldUri) - if text then - files.remove(file.oldUri) - if workspace.isValidLuaUri(file.newUri) then - files.setText(file.newUri, text, false) +m.register 'workspace/didCreateFiles' { + ---@async + function (params) + log.debug('workspace/didCreateFiles', util.dump(params)) + for _, file in ipairs(params.files) do + if workspace.isValidLuaUri(file.uri) then + files.setText(file.uri, pub.awaitTask('loadFile', file.uri), false) end end - local childs = files.getChildFiles(file.oldUri) - for _, uri in ipairs(childs) do - local ctext = files.getOriginText(uri) - if ctext then - local ouri = uri - local tail = ouri:sub(#file.oldUri) - local nuri = file.newUri .. tail - log.debug('workspace/didRenameFiles#child', ouri, nuri) + end +} + +m.register 'workspace/didDeleteFiles' { + function (params) + log.debug('workspace/didDeleteFiles', util.dump(params)) + for _, file in ipairs(params.files) do + files.remove(file.uri) + local childs = files.getChildFiles(file.uri) + for _, uri in ipairs(childs) do + log.debug('workspace/didDeleteFiles#child', uri) files.remove(uri) - if workspace.isValidLuaUri(nuri) then - files.setText(nuri, text, false) + end + end + end +} + +m.register 'workspace/didRenameFiles' { + ---@async + function (params) + log.debug('workspace/didRenameFiles', util.dump(params)) + for _, file in ipairs(params.files) do + local text = files.getOriginText(file.oldUri) + if text then + files.remove(file.oldUri) + if workspace.isValidLuaUri(file.newUri) then + files.setText(file.newUri, text, false) + end + end + local childs = files.getChildFiles(file.oldUri) + for _, uri in ipairs(childs) do + local ctext = files.getOriginText(uri) + if ctext then + local ouri = uri + local tail = ouri:sub(#file.oldUri) + local nuri = file.newUri .. tail + log.debug('workspace/didRenameFiles#child', ouri, nuri) + files.remove(uri) + if workspace.isValidLuaUri(nuri) then + files.setText(nuri, text, false) + end end end end end -end) +} -proto.on('textDocument/didOpen', function (params) - workspace.awaitReady() - local doc = params.textDocument - local uri = doc.uri - local text = doc.text - log.debug('didOpen', uri) - files.setText(uri, text, true) - files.open(uri) -end) +m.register 'textDocument/didOpen' { + ---@async + function (params) + workspace.awaitReady() + local doc = params.textDocument + local uri = doc.uri + local text = doc.text + log.debug('didOpen', uri) + files.setText(uri, text, true) + files.open(uri) + end +} -proto.on('textDocument/didClose', function (params) - local doc = params.textDocument - local uri = doc.uri - log.debug('didClose', uri) - files.close(uri) - if not files.isLua(uri) then - files.remove(uri) +m.register 'textDocument/didClose' { + function (params) + local doc = params.textDocument + local uri = doc.uri + log.debug('didClose', uri) + files.close(uri) + if not files.isLua(uri) then + files.remove(uri) + end end -end) +} -proto.on('textDocument/didChange', function (params) - workspace.awaitReady() - local doc = params.textDocument - local changes = params.contentChanges - local uri = doc.uri - --log.debug('changes', util.dump(changes)) - local text = tm(uri, changes) - files.setText(uri, text, true) -end) +m.register 'textDocument/didChange' { + ---@async + function (params) + workspace.awaitReady() + local doc = params.textDocument + local changes = params.contentChanges + local uri = doc.uri + --log.debug('changes', util.dump(changes)) + local text = tm(uri, changes) + files.setText(uri, text, true, doc.version) + end +} -proto.on('textDocument/hover', function (params) - local doc = params.textDocument - local uri = doc.uri - if not workspace.isReady() then - local count, max = workspace.getLoadProcess() +m.register 'textDocument/hover' { + abortByFileUpdate = true, + ---@async + function (params) + local doc = params.textDocument + local uri = doc.uri + if not workspace.isReady() then + local count, max = workspace.getLoadProcess() + return { + contents = { + value = lang.script('HOVER_WS_LOADING', count, max), + kind = 'markdown', + } + } + end + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_HOVER, 0.5) + local core = require 'core.hover' + if not files.exists(uri) then + return nil + end + local pos = converter.unpackPosition(uri, params.position) + local hover, source = core.byUri(uri, pos) + if not hover then + return nil + end return { contents = { - value = lang.script('HOVER_WS_LOADING', count, max), + value = tostring(hover), kind = 'markdown', - } + }, + range = converter.packRange(uri, source.start, source.finish), } end - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_HOVER, 0.5) - local core = require 'core.hover' - if not files.exists(uri) then - return nil - end - local pos = converter.unpackPosition(uri, params.position) - local hover, source = core.byUri(uri, pos) - if not hover then - return nil - end - return { - contents = { - value = tostring(hover), - kind = 'markdown', - }, - range = converter.packRange(uri, source.start, source.finish), - } -end) +} -proto.on('textDocument/definition', function (params) - workspace.awaitReady() - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_DEFINITION, 0.5) - local core = require 'core.definition' - local uri = params.textDocument.uri - if not files.exists(uri) then - return nil - end - local pos = converter.unpackPosition(uri, params.position) - local result = core(uri, pos) - if not result then - return nil - end - local response = {} - for i, info in ipairs(result) do - local targetUri = info.uri - if targetUri then - if files.exists(targetUri) then - if client.getAbility 'textDocument.definition.linkSupport' then - response[i] = converter.locationLink(targetUri - , converter.packRange(targetUri, info.target.start, info.target.finish) - , converter.packRange(targetUri, info.target.start, info.target.finish) - , converter.packRange(uri, info.source.start, info.source.finish) - ) - else - response[i] = converter.location(targetUri - , converter.packRange(targetUri, info.target.start, info.target.finish) - ) +m.register 'textDocument/definition' { + abortByFileUpdate = true, + ---@async + function (params) + workspace.awaitReady() + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_DEFINITION, 0.5) + local core = require 'core.definition' + local uri = params.textDocument.uri + if not files.exists(uri) then + return nil + end + local pos = converter.unpackPosition(uri, params.position) + local result = core(uri, pos) + if not result then + return nil + end + local response = {} + for i, info in ipairs(result) do + local targetUri = info.uri + if targetUri then + if files.exists(targetUri) then + if client.getAbility 'textDocument.definition.linkSupport' then + response[i] = converter.locationLink(targetUri + , converter.packRange(targetUri, info.target.start, info.target.finish) + , converter.packRange(targetUri, info.target.start, info.target.finish) + , converter.packRange(uri, info.source.start, info.source.finish) + ) + else + response[i] = converter.location(targetUri + , converter.packRange(targetUri, info.target.start, info.target.finish) + ) + end end end end + return response end - return response -end) +} -proto.on('textDocument/typeDefinition', function (params) - workspace.awaitReady() - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_TYPE_DEFINITION, 0.5) - local core = require 'core.type-definition' - local uri = params.textDocument.uri - if not files.exists(uri) then - return nil - end - local pos = converter.unpackPosition(uri, params.position) - local result = core(uri, pos) - if not result then - return nil - end - local response = {} - for i, info in ipairs(result) do - local targetUri = info.uri - if targetUri then - if files.exists(targetUri) then - if client.getAbility 'textDocument.typeDefinition.linkSupport' then - response[i] = converter.locationLink(targetUri - , converter.packRange(targetUri, info.target.start, info.target.finish) - , converter.packRange(targetUri, info.target.start, info.target.finish) - , converter.packRange(uri, info.source.start, info.source.finish) - ) - else - response[i] = converter.location(targetUri - , converter.packRange(targetUri, info.target.start, info.target.finish) - ) +m.register 'textDocument/typeDefinition' { + abortByFileUpdate = true, + ---@async + function (params) + workspace.awaitReady() + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_TYPE_DEFINITION, 0.5) + local core = require 'core.type-definition' + local uri = params.textDocument.uri + if not files.exists(uri) then + return nil + end + local pos = converter.unpackPosition(uri, params.position) + local result = core(uri, pos) + if not result then + return nil + end + local response = {} + for i, info in ipairs(result) do + local targetUri = info.uri + if targetUri then + if files.exists(targetUri) then + if client.getAbility 'textDocument.typeDefinition.linkSupport' then + response[i] = converter.locationLink(targetUri + , converter.packRange(targetUri, info.target.start, info.target.finish) + , converter.packRange(targetUri, info.target.start, info.target.finish) + , converter.packRange(uri, info.source.start, info.source.finish) + ) + else + response[i] = converter.location(targetUri + , converter.packRange(targetUri, info.target.start, info.target.finish) + ) + end end end end + return response end - return response -end) +} -proto.on('textDocument/references', function (params) - workspace.awaitReady() - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_REFERENCE, 0.5) - local core = require 'core.reference' - local uri = params.textDocument.uri - if not files.exists(uri) then - return nil - end - local pos = converter.unpackPosition(uri, params.position) - local result = core(uri, pos) - if not result then - return nil - end - local response = {} - for i, info in ipairs(result) do - local targetUri = info.uri - response[i] = converter.location(targetUri - , converter.packRange(targetUri, info.target.start, info.target.finish) - ) - end - return response -end) - -proto.on('textDocument/documentHighlight', function (params) - local core = require 'core.highlight' - local uri = params.textDocument.uri - if not files.exists(uri) then - return nil - end - local pos = converter.unpackPosition(uri, params.position) - local result = core(uri, pos) - if not result then - return nil - end - local response = {} - for _, info in ipairs(result) do - response[#response+1] = { - range = converter.packRange(uri, info.start, info.finish), - kind = info.kind, - } +m.register 'textDocument/references' { + abortByFileUpdate = true, + ---@async + function (params) + workspace.awaitReady() + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_REFERENCE, 0.5) + local core = require 'core.reference' + local uri = params.textDocument.uri + if not files.exists(uri) then + return nil + end + local pos = converter.unpackPosition(uri, params.position) + local result = core(uri, pos) + if not result then + return nil + end + local response = {} + for i, info in ipairs(result) do + local targetUri = info.uri + response[i] = converter.location(targetUri + , converter.packRange(targetUri, info.target.start, info.target.finish) + ) + end + return response end - return response -end) +} -proto.on('textDocument/rename', function (params) - workspace.awaitReady() - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_RENAME, 0.5) - local core = require 'core.rename' - local uri = params.textDocument.uri - if not files.exists(uri) then - return nil - end - local pos = converter.unpackPosition(uri, params.position) - local result = core.rename(uri, pos, params.newName) - if not result then - return nil - end - local workspaceEdit = { - changes = {}, - } - for _, info in ipairs(result) do - local ruri = info.uri - if not workspaceEdit.changes[ruri] then - workspaceEdit.changes[ruri] = {} - end - local textEdit = converter.textEdit(converter.packRange(ruri, info.start, info.finish), info.text) - workspaceEdit.changes[ruri][#workspaceEdit.changes[ruri]+1] = textEdit - end - return workspaceEdit -end) +m.register 'textDocument/documentHighlight' { + abortByFileUpdate = true, + function (params) + local core = require 'core.highlight' + local uri = params.textDocument.uri + if not files.exists(uri) then + return nil + end + local pos = converter.unpackPosition(uri, params.position) + local result = core(uri, pos) + if not result then + return nil + end + local response = {} + for _, info in ipairs(result) do + response[#response+1] = { + range = converter.packRange(uri, info.start, info.finish), + kind = info.kind, + } + end + return response + end +} -proto.on('textDocument/prepareRename', function (params) - local core = require 'core.rename' - local uri = params.textDocument.uri - if not files.exists(uri) then - return nil - end - local pos = converter.unpackPosition(uri, params.position) - local result = core.prepareRename(uri, pos) - if not result then - return nil - end - return { - range = converter.packRange(uri, result.start, result.finish), - placeholder = result.text, - } -end) +m.register 'textDocument/rename' { + abortByFileUpdate = true, + ---@async + function (params) + workspace.awaitReady() + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_RENAME, 0.5) + local core = require 'core.rename' + local uri = params.textDocument.uri + if not files.exists(uri) then + return nil + end + local pos = converter.unpackPosition(uri, params.position) + local result = core.rename(uri, pos, params.newName) + if not result then + return nil + end + local workspaceEdit = { + changes = {}, + } + for _, info in ipairs(result) do + local ruri = info.uri + if not workspaceEdit.changes[ruri] then + workspaceEdit.changes[ruri] = {} + end + local textEdit = converter.textEdit(converter.packRange(ruri, info.start, info.finish), info.text) + workspaceEdit.changes[ruri][#workspaceEdit.changes[ruri]+1] = textEdit + end + return workspaceEdit + end +} -proto.on('textDocument/completion', function (params) - local uri = params.textDocument.uri - if not workspace.isReady() then - local count, max = workspace.getLoadProcess() +m.register 'textDocument/prepareRename' { + abortByFileUpdate = true, + function (params) + local core = require 'core.rename' + local uri = params.textDocument.uri + if not files.exists(uri) then + return nil + end + local pos = converter.unpackPosition(uri, params.position) + local result = core.prepareRename(uri, pos) + if not result then + return nil + end return { - { - label = lang.script('HOVER_WS_LOADING', count, max),textEdit = { - range = { - start = params.position, - ['end'] = params.position, - }, - newText = '', - }, - } + range = converter.packRange(uri, result.start, result.finish), + placeholder = result.text, } end - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_COMPLETION, 0.5) - --log.info(util.dump(params)) - local core = require 'core.completion' - --log.debug('textDocument/completion') - --log.debug('completion:', params.context and params.context.triggerKind, params.context and params.context.triggerCharacter) - if not files.exists(uri) then - return nil - end - local triggerCharacter = params.context and params.context.triggerCharacter - if config.get 'editor.acceptSuggestionOnEnter' ~= 'off' then - if triggerCharacter == '\n' - or triggerCharacter == '{' - or triggerCharacter == ',' then - return +} + +m.register 'textDocument/completion' { + ---@async + function (params) + local uri = params.textDocument.uri + if not workspace.isReady() then + local count, max = workspace.getLoadProcess() + return { + { + label = lang.script('HOVER_WS_LOADING', count, max),textEdit = { + range = { + start = params.position, + ['end'] = params.position, + }, + newText = '', + }, + } + } end - end - await.setPriority(1000) - local clock = os.clock() - local pos = converter.unpackPosition(uri, params.position) - local result = core.completion(uri, pos, triggerCharacter) - local passed = os.clock() - clock - if passed > 0.1 then - log.warn(('Completion takes %.3f sec.'):format(passed)) - end - if not result then - return nil - end - tracy.ZoneBeginN 'completion make' - local _ <close> = tracy.ZoneEnd - local easy = false - local items = {} - for i, res in ipairs(result) do - local item = { - label = res.label, - kind = res.kind, - detail = res.detail, - deprecated = res.deprecated, - sortText = ('%04d'):format(i), - filterText = res.filterText, - insertText = res.insertText, - insertTextFormat = 2, - commitCharacters = res.commitCharacters, - command = res.command, - textEdit = res.textEdit and { - range = converter.packRange( - uri, - res.textEdit.start, - res.textEdit.finish - ), - newText = res.textEdit.newText, - }, - additionalTextEdits = res.additionalTextEdits and (function () - local t = {} - for j, edit in ipairs(res.additionalTextEdits) do - t[j] = { - range = converter.packRange( - uri, - edit.start, - edit.finish - ), - newText = edit.newText, - } - end - return t - end)(), - documentation = res.description and { - value = tostring(res.description), - kind = 'markdown', - }, - } - if res.id then - if easy and os.clock() - clock < 0.05 then - local resolved = core.resolve(res.id) - if resolved then - item.detail = resolved.detail - item.documentation = resolved.description and { - value = tostring(resolved.description), - kind = 'markdown', + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_COMPLETION, 0.5) + --log.info(util.dump(params)) + local core = require 'core.completion' + --log.debug('textDocument/completion') + --log.debug('completion:', params.context and params.context.triggerKind, params.context and params.context.triggerCharacter) + if not files.exists(uri) then + return nil + end + local triggerCharacter = params.context and params.context.triggerCharacter + if config.get 'editor.acceptSuggestionOnEnter' ~= 'off' then + if triggerCharacter == '\n' + or triggerCharacter == '{' + or triggerCharacter == ',' then + return + end + end + --await.setPriority(1000) + local clock = os.clock() + local pos = converter.unpackPosition(uri, params.position) + local result = core.completion(uri, pos, triggerCharacter) + local passed = os.clock() - clock + if passed > 0.1 then + log.warn(('Completion takes %.3f sec.'):format(passed)) + end + if not result then + return nil + end + tracy.ZoneBeginN 'completion make' + local _ <close> = tracy.ZoneEnd + local easy = false + local items = {} + for i, res in ipairs(result) do + local item = { + label = res.label, + kind = res.kind, + detail = res.detail, + deprecated = res.deprecated, + sortText = ('%04d'):format(i), + filterText = res.filterText, + insertText = res.insertText, + insertTextFormat = 2, + commitCharacters = res.commitCharacters, + command = res.command, + textEdit = res.textEdit and { + range = converter.packRange( + uri, + res.textEdit.start, + res.textEdit.finish + ), + newText = res.textEdit.newText, + }, + additionalTextEdits = res.additionalTextEdits and (function () + local t = {} + for j, edit in ipairs(res.additionalTextEdits) do + t[j] = { + range = converter.packRange( + uri, + edit.start, + edit.finish + ), + newText = edit.newText, + } + end + return t + end)(), + documentation = res.description and { + value = tostring(res.description), + kind = 'markdown', + }, + } + if res.id then + if easy and os.clock() - clock < 0.05 then + local resolved = core.resolve(res.id) + if resolved then + item.detail = resolved.detail + item.documentation = resolved.description and { + value = tostring(resolved.description), + kind = 'markdown', + } + end + else + easy = false + item.data = { + uri = uri, + id = res.id, } end - else - easy = false - item.data = { - uri = uri, - id = res.id, - } end + items[i] = item end - items[i] = item + return { + isIncomplete = not result.complete, + items = items, + } end - return { - isIncomplete = not result.complete, - items = items, - } -end) +} -proto.on('completionItem/resolve', function (item) - local core = require 'core.completion' - if not item.data then +m.register 'completionItem/resolve' { + ---@async + function (item) + local core = require 'core.completion' + if not item.data then + return item + end + local id = item.data.id + local uri = item.data.uri + --await.setPriority(1000) + local resolved = core.resolve(id) + if not resolved then + return nil + end + item.detail = resolved.detail or item.detail + item.documentation = resolved.description and { + value = tostring(resolved.description), + kind = 'markdown', + } or item.documentation + item.additionalTextEdits = resolved.additionalTextEdits and (function () + local t = {} + for j, edit in ipairs(resolved.additionalTextEdits) do + t[j] = { + range = converter.packRange( + uri, + edit.start, + edit.finish + ), + newText = edit.newText, + } + end + return t + end)() or item.additionalTextEdits return item end - local id = item.data.id - local uri = item.data.uri - --await.setPriority(1000) - local resolved = core.resolve(id) - if not resolved then - return nil - end - item.detail = resolved.detail or item.detail - item.documentation = resolved.description and { - value = tostring(resolved.description), - kind = 'markdown', - } or item.documentation - item.additionalTextEdits = resolved.additionalTextEdits and (function () - local t = {} - for j, edit in ipairs(resolved.additionalTextEdits) do - t[j] = { - range = converter.packRange( - uri, - edit.start, - edit.finish - ), - newText = edit.newText, - } - end - return t - end)() or item.additionalTextEdits - return item -end) +} -proto.on('textDocument/signatureHelp', function (params) - if not config.get 'Lua.signatureHelp.enable' then - return nil - end - workspace.awaitReady() - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_SIGNATURE, 0.5) - local uri = params.textDocument.uri - if not files.exists(uri) then - return nil - end - local pos = converter.unpackPosition(uri, params.position) - local core = require 'core.signature' - local results = core(uri, pos) - if not results then - return nil - end - local infos = {} - for i, result in ipairs(results) do - local parameters = {} - for j, param in ipairs(result.params) do - parameters[j] = { - label = { - param.label[1], - param.label[2], +m.register 'textDocument/signatureHelp' { + abortByFileUpdate = true, + ---@async + function (params) + if not config.get 'Lua.signatureHelp.enable' then + return nil + end + workspace.awaitReady() + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_SIGNATURE, 0.5) + local uri = params.textDocument.uri + if not files.exists(uri) then + return nil + end + local pos = converter.unpackPosition(uri, params.position) + local core = require 'core.signature' + local results = core(uri, pos) + if not results then + return nil + end + local infos = {} + for i, result in ipairs(results) do + local parameters = {} + for j, param in ipairs(result.params) do + parameters[j] = { + label = { + param.label[1], + param.label[2], + } } + end + infos[i] = { + label = result.label, + parameters = parameters, + activeParameter = result.index - 1, + documentation = result.description and { + value = tostring(result.description), + kind = 'markdown', + }, } end - infos[i] = { - label = result.label, - parameters = parameters, - activeParameter = result.index - 1, - documentation = result.description and { - value = tostring(result.description), - kind = 'markdown', - }, + return { + signatures = infos, } end - return { - signatures = infos, - } -end) +} -proto.on('textDocument/documentSymbol', function (params) - workspace.awaitReady() - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_SYMBOL, 0.5) - local core = require 'core.document-symbol' - local uri = params.textDocument.uri +m.register 'textDocument/documentSymbol' { + abortByFileUpdate = true, + ---@async + function (params) + workspace.awaitReady() + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_SYMBOL, 0.5) + local core = require 'core.document-symbol' + local uri = params.textDocument.uri - local symbols = core(uri) - if not symbols then - return nil - end + local symbols = core(uri) + if not symbols then + return nil + end - local function convert(symbol) - await.delay() - symbol.range = converter.packRange( - uri, - symbol.range[1], - symbol.range[2] - ) - symbol.selectionRange = converter.packRange( - uri, - symbol.selectionRange[1], - symbol.selectionRange[2] - ) - if symbol.name == '' then - symbol.name = lang.script.SYMBOL_ANONYMOUS - end - symbol.valueRange = nil - if symbol.children then - for _, child in ipairs(symbol.children) do - convert(child) + ---@async + local function convert(symbol) + await.delay() + symbol.range = converter.packRange( + uri, + symbol.range[1], + symbol.range[2] + ) + symbol.selectionRange = converter.packRange( + uri, + symbol.selectionRange[1], + symbol.selectionRange[2] + ) + if symbol.name == '' then + symbol.name = lang.script.SYMBOL_ANONYMOUS + end + symbol.valueRange = nil + if symbol.children then + for _, child in ipairs(symbol.children) do + convert(child) + end end end - end - - for _, symbol in ipairs(symbols) do - convert(symbol) - end - return symbols -end) + for _, symbol in ipairs(symbols) do + convert(symbol) + end -proto.on('textDocument/codeAction', function (params) - local core = require 'core.code-action' - local uri = params.textDocument.uri - local range = params.range - local diagnostics = params.context.diagnostics - if not files.exists(uri) then - return nil + return symbols end +} - local start, finish = converter.unpackRange(uri, range) - local results = core(uri, start, finish, diagnostics) +m.register 'textDocument/codeAction' { + abortByFileUpdate = true, + function (params) + local core = require 'core.code-action' + local uri = params.textDocument.uri + local range = params.range + local diagnostics = params.context.diagnostics + if not files.exists(uri) then + return nil + end - if not results or #results == 0 then - return nil - end + local start, finish = converter.unpackRange(uri, range) + local results = core(uri, start, finish, diagnostics) + + if not results or #results == 0 then + return nil + end - for _, res in ipairs(results) do - if res.edit then - for turi, changes in pairs(res.edit.changes) do - for _, change in ipairs(changes) do - change.range = converter.packRange(turi, change.start, change.finish) - change.start = nil - change.finish = nil + for _, res in ipairs(results) do + if res.edit then + for turi, changes in pairs(res.edit.changes) do + for _, change in ipairs(changes) do + change.range = converter.packRange(turi, change.start, change.finish) + change.start = nil + change.finish = nil + end end end end - end - return results -end) + return results + end +} -proto.on('workspace/executeCommand', function (params) - local command = params.command:gsub(':.+', '') - if command == 'lua.removeSpace' then - local core = require 'core.command.removeSpace' - return core(params.arguments[1]) - elseif command == 'lua.solve' then - local core = require 'core.command.solve' - return core(params.arguments[1]) - elseif command == 'lua.jsonToLua' then - local core = require 'core.command.jsonToLua' - return core(params.arguments[1]) - elseif command == 'lua.setConfig' then - local core = require 'core.command.setConfig' - return core(params.arguments[1]) - elseif command == 'lua.autoRequire' then - local core = require 'core.command.autoRequire' - return core(params.arguments[1]) +m.register 'workspace/executeCommand' { + ---@async + function (params) + local command = params.command:gsub(':.+', '') + if command == 'lua.removeSpace' then + local core = require 'core.command.removeSpace' + return core(params.arguments[1]) + elseif command == 'lua.solve' then + local core = require 'core.command.solve' + return core(params.arguments[1]) + elseif command == 'lua.jsonToLua' then + local core = require 'core.command.jsonToLua' + return core(params.arguments[1]) + elseif command == 'lua.setConfig' then + local core = require 'core.command.setConfig' + return core(params.arguments[1]) + elseif command == 'lua.autoRequire' then + local core = require 'core.command.autoRequire' + return core(params.arguments[1]) + end end -end) +} -proto.on('workspace/symbol', function (params) - workspace.awaitReady() - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_WS_SYMBOL, 0.5) - local core = require 'core.workspace-symbol' +m.register 'workspace/symbol' { + abortByFileUpdate = true, + ---@async + function (params) + workspace.awaitReady() + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_WS_SYMBOL, 0.5) + local core = require 'core.workspace-symbol' - local symbols = core(params.query) - if not symbols or #symbols == 0 then - return nil - end + local symbols = core(params.query) + if not symbols or #symbols == 0 then + return nil + end - local function convert(symbol) - symbol.location = converter.location( - symbol.uri, - converter.packRange( + local function convert(symbol) + symbol.location = converter.location( symbol.uri, - symbol.range[1], - symbol.range[2] + converter.packRange( + symbol.uri, + symbol.range[1], + symbol.range[2] + ) ) - ) - symbol.uri = nil - end - - for _, symbol in ipairs(symbols) do - convert(symbol) - end + symbol.uri = nil + end - return symbols -end) + for _, symbol in ipairs(symbols) do + convert(symbol) + end -proto.on('textDocument/semanticTokens/full', function (params) - local uri = params.textDocument.uri - workspace.awaitReady() - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_SEMANTIC_FULL, 0.5) - local core = require 'core.semantic-tokens' - local results = core(uri, 0, math.huge) - return { - data = results - } -end) + return symbols + end +} -proto.on('textDocument/semanticTokens/range', function (params) - local uri = params.textDocument.uri - workspace.awaitReady() - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_SEMANTIC_RANGE, 0.5) - local core = require 'core.semantic-tokens' - local cache = files.getOpenedCache(uri) - local start, finish - if cache and not cache['firstSemantic'] then - cache['firstSemantic'] = true - start = 0 - finish = math.huge - else - start, finish = converter.unpackRange(uri, params.range) +m.register 'textDocument/semanticTokens/full' { + abortByFileUpdate = true, + ---@async + function (params) + local uri = params.textDocument.uri + workspace.awaitReady() + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_SEMANTIC_FULL, 0.5) + local core = require 'core.semantic-tokens' + local results = core(uri, 0, math.huge) + return { + data = results + } end - local results = core(uri, start, finish) - return { - data = results - } -end) +} -proto.on('textDocument/foldingRange', function (params) - local core = require 'core.folding' - local uri = params.textDocument.uri - if not files.exists(uri) then - return nil - end - local regions = core(uri) - if not regions then - return nil - end - - local results = {} - for _, region in ipairs(regions) do - local startLine = converter.packPosition(uri, region.start).line - local endLine = converter.packPosition(uri, region.finish).line - if not region.hideLastLine then - endLine = endLine - 1 - end - if startLine < endLine then - results[#results+1] = { - startLine = startLine, - endLine = endLine, - kind = region.kind, - } +m.register 'textDocument/semanticTokens/range' { + abortByFileUpdate = true, + ---@async + function (params) + local uri = params.textDocument.uri + workspace.awaitReady() + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_SEMANTIC_RANGE, 0.5) + local core = require 'core.semantic-tokens' + local cache = files.getOpenedCache(uri) + local start, finish + if cache and not cache['firstSemantic'] then + cache['firstSemantic'] = true + start = 0 + finish = math.huge + else + start, finish = converter.unpackRange(uri, params.range) end + local results = core(uri, start, finish) + return { + data = results + } end +} - return results -end) +m.register 'textDocument/foldingRange' { + abortByFileUpdate = true, + ---@async + function (params) + local core = require 'core.folding' + local uri = params.textDocument.uri + if not files.exists(uri) then + return nil + end + local regions = core(uri) + if not regions then + return nil + end -proto.on('window/workDoneProgress/cancel', function (params) - progress.cancel(params.token) -end) + local results = {} + for _, region in ipairs(regions) do + local startLine = converter.packPosition(uri, region.start).line + local endLine = converter.packPosition(uri, region.finish).line + if not region.hideLastLine then + endLine = endLine - 1 + end + if startLine < endLine then + results[#results+1] = { + startLine = startLine, + endLine = endLine, + kind = region.kind, + } + end + end -proto.on('$/didChangeVisibleRanges', function (params) - local uri = params.uri - await.close('visible:' .. uri) - await.setID('visible:' .. uri) - await.delay() - files.setVisibles(uri, params.ranges) -end) + return results + end +} -proto.on('$/status/click', function () - -- TODO: translate - local titleDiagnostic = '进行工作区诊断' - local result = client.awaitRequestMessage('Info', 'xxx', { - titleDiagnostic, - }) - if not result then - return +m.register 'window/workDoneProgress/cancel' { + function (params) + log.debug('close proto(cancel):', params.token) + progress.cancel(params.token) end - if result == titleDiagnostic then - local diagnostic = require 'provider.diagnostic' - diagnostic.diagnosticsAll(true) +} + +m.register '$/didChangeVisibleRanges' { + ---@async + function (params) + local uri = params.uri + await.close('visible:' .. uri) + await.setID('visible:' .. uri) + await.delay() + files.setVisibles(uri, params.ranges) end -end) +} -proto.on('textDocument/onTypeFormatting', function (params) - workspace.awaitReady() - local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_TYPE_FORMATTING, 0.5) - local ch = params.ch - local uri = params.textDocument.uri - if not files.exists(uri) then - return nil - end - local core = require 'core.type-formatting' - local pos = converter.unpackPosition(uri, params.position) - local edits = core(uri, pos, ch) - if not edits or #edits == 0 then - return nil - end - local tab = '\t' - if params.options.insertSpaces then - tab = (' '):rep(params.options.tabSize) - end - local results = {} - for i, edit in ipairs(edits) do - results[i] = { - range = converter.packRange(uri, edit.start, edit.finish), - newText = edit.text:gsub('\t', tab), - } +m.register '$/status/click' { + ---@async + function () + -- TODO: translate + local titleDiagnostic = '进行工作区诊断' + local result = client.awaitRequestMessage('Info', 'xxx', { + titleDiagnostic, + }) + if not result then + return + end + if result == titleDiagnostic then + local diagnostic = require 'provider.diagnostic' + diagnostic.diagnosticsAll(true) + end end - return results -end) +} -proto.on('$/cancelRequest', function (params) - proto.close(params.id, define.ErrorCodes.RequestCancelled) -end) +m.register 'textDocument/onTypeFormatting' { + abortByFileUpdate = true, + ---@async + function (params) + workspace.awaitReady() + local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_TYPE_FORMATTING, 0.5) + local ch = params.ch + local uri = params.textDocument.uri + if not files.exists(uri) then + return nil + end + local core = require 'core.type-formatting' + local pos = converter.unpackPosition(uri, params.position) + local edits = core(uri, pos, ch) + if not edits or #edits == 0 then + return nil + end + local tab = '\t' + if params.options.insertSpaces then + tab = (' '):rep(params.options.tabSize) + end + local results = {} + for i, edit in ipairs(edits) do + results[i] = { + range = converter.packRange(uri, edit.start, edit.finish), + newText = edit.text:gsub('\t', tab), + } + end + return results + end +} -proto.on('$/requestHint', function (params) - local core = require 'core.hint' - if not config.get 'Lua.hint.enable' then - return +m.register '$/cancelRequest' { + function (params) + proto.close(params.id, define.ErrorCodes.RequestCancelled) end - workspace.awaitReady() - local uri = params.textDocument.uri - local start, finish = converter.unpackRange(uri, params.range) - local results = core(uri, start, finish) - local hintResults = {} - for i, res in ipairs(results) do - hintResults[i] = { - text = res.text, - pos = converter.packPosition(uri, res.offset), - kind = res.kind, - } +} + +m.register '$/requestHint' { + ---@async + function (params) + local core = require 'core.hint' + if not config.get 'Lua.hint.enable' then + return + end + workspace.awaitReady() + local uri = params.textDocument.uri + local start, finish = converter.unpackRange(uri, params.range) + local results = core(uri, start, finish) + local hintResults = {} + for i, res in ipairs(results) do + hintResults[i] = { + text = res.text, + pos = converter.packPosition(uri, res.offset), + kind = res.kind, + } + end + return hintResults end - return hintResults -end) +} -- Hint do + ---@async local function updateHint(uri) if not config.get 'Lua.hint.enable' then return @@ -838,6 +960,10 @@ do if not visibles then return end + await.close 'updateHint' + await.setID 'updateHint' + await.delay() + workspace.awaitReady() local edits = {} local hint = require 'core.hint' local _ <close> = progress.create(lang.script.WINDOW_PROCESSING_HINT, 0.5) @@ -862,7 +988,7 @@ do files.watch(function (ev, uri) if ev == 'update' or ev == 'updateVisible' then - await.call(function () + await.call(function () ---@async updateHint(uri) end) end @@ -884,7 +1010,7 @@ config.watch(function (key, value) end end) -proto.on('$/status/refresh', refreshStatusBar) +m.register '$/status/refresh' { refreshStatusBar } files.watch(function (ev, uri) if not workspace.isReady() then @@ -893,7 +1019,8 @@ files.watch(function (ev, uri) if ev == 'update' or ev == 'remove' then for id, p in pairs(proto.holdon) do - if p.params.textDocument and p.params.textDocument.uri == uri then + if m.attributes[p.method].abortByFileUpdate then + log.debug('close proto(ContentModified):', id, p.method) proto.close(id, define.ErrorCodes.ContentModified) end end diff --git a/script/pub/pub.lua b/script/pub/pub.lua index ba57e5cb..fd780477 100644 --- a/script/pub/pub.lua +++ b/script/pub/pub.lua @@ -104,6 +104,7 @@ end ---@parma name string ---@param params any ---@return any +---@async function m.awaitTask(name, params) local info = { id = counter(), diff --git a/script/service/telemetry.lua b/script/service/telemetry.lua index 1ab3fdbe..dac72f3f 100644 --- a/script/service/telemetry.lua +++ b/script/service/telemetry.lua @@ -111,7 +111,7 @@ function m.updateConfig() end m.hasShowedMessage = true - await.call(function () + await.call(function () ---@async local enableTitle = lang.script.WINDOW_TELEMETRY_ENABLE local disableTitle = lang.script.WINDOW_TELEMETRY_DISABLE local item = proto.awaitRequest('window/showMessageRequest', { diff --git a/script/utility.lua b/script/utility.lua index b5eb4095..85c98cb1 100644 --- a/script/utility.lua +++ b/script/utility.lua @@ -691,6 +691,7 @@ function m.switch() return obj end +---@param f async fun() function m.getUpvalue(f, name) for i = 1, 999 do local uname, value = getupvalue(f, i) diff --git a/script/vm/eachDef.lua b/script/vm/eachDef.lua index 2bfad4bf..ea14ed9f 100644 --- a/script/vm/eachDef.lua +++ b/script/vm/eachDef.lua @@ -1,4 +1,4 @@ ----@type vm +---@class vm local vm = require 'vm.vm' local searcher = require 'core.searcher' diff --git a/script/vm/eachRef.lua b/script/vm/eachRef.lua index 35425818..899c04c6 100644 --- a/script/vm/eachRef.lua +++ b/script/vm/eachRef.lua @@ -1,4 +1,4 @@ ----@type vm +---@class vm local vm = require 'vm.vm' local searcher = require 'core.searcher' diff --git a/script/vm/getDocs.lua b/script/vm/getDocs.lua index 2fb2bda9..3a0765bf 100644 --- a/script/vm/getDocs.lua +++ b/script/vm/getDocs.lua @@ -1,6 +1,6 @@ local files = require 'files' local guide = require 'parser.guide' ----@type vm +---@class vm local vm = require 'vm.vm' local config = require 'config' local collector = require 'core.collector' @@ -180,6 +180,140 @@ function vm.isDeprecated(value, deep) end end +local function isAsync(value) + if value.type == 'function' then + if not value.bindDocs then + return false + end + if value._async ~= nil then + return value._async + end + for _, doc in ipairs(value.bindDocs) do + if doc.type == 'doc.async' then + value._async = true + return true + end + end + value._async = false + return false + end + return value.async == true +end + +function vm.isAsync(value, deep) + if isAsync(value) then + return true + end + if deep then + local defs = vm.getDefs(value) + if #defs == 0 then + return false + end + for _, def in ipairs(defs) do + if isAsync(def) then + return true + end + end + end + return false +end + +local function isNoDiscard(value) + if value.type == 'function' then + if not value.bindDocs then + return false + end + if value._nodiscard ~= nil then + return value._nodiscard + end + for _, doc in ipairs(value.bindDocs) do + if doc.type == 'doc.nodiscard' then + value._nodiscard = true + return true + end + end + value._nodiscard = false + return false + end + return false +end + +function vm.isNoDiscard(value, deep) + if isNoDiscard(value) then + return true + end + if deep then + local defs = vm.getDefs(value) + if #defs == 0 then + return false + end + for _, def in ipairs(defs) do + if isNoDiscard(def) then + return true + end + end + end + return false +end + +local function isCalledInFunction(param) + if not param.ref then + return false + end + local func = guide.getParentFunction(param) + for _, ref in ipairs(param.ref) do + if ref.type == 'getlocal' then + if ref.parent.type == 'call' + and guide.getParentFunction(ref) == func then + return true + end + if ref.parent.type == 'callargs' + and ref.parent[1] == ref + and guide.getParentFunction(ref) == func then + if ref.parent.parent.node.special == 'pcall' + or ref.parent.parent.node.special == 'xpcall' then + return true + end + end + end + end + return false +end + +local function isLinkedCall(node, index) + for _, def in ipairs(vm.getDefs(node)) do + if def.type == 'function' then + local param = def.args and def.args[index] + if param then + if isCalledInFunction(param) then + return true + end + end + end + end + return false +end + +function vm.isLinkedCall(node, index) + return isLinkedCall(node, index) +end + +function vm.isAsyncCall(call) + if vm.isAsync(call.node, true) then + return true + end + if not call.args then + return + end + for i, arg in ipairs(call.args) do + if vm.isAsync(arg, true) + and isLinkedCall(call.node, i) then + return true + end + end + return false +end + local function makeDiagRange(uri, doc, results) local names if doc.names then diff --git a/script/vm/getGlobals.lua b/script/vm/getGlobals.lua index 6dacda43..92fd1c8e 100644 --- a/script/vm/getGlobals.lua +++ b/script/vm/getGlobals.lua @@ -1,6 +1,6 @@ local collector = require 'core.collector' local guide = require 'parser.guide' ----@type vm +---@class vm local vm = require 'vm.vm' local noder = require 'core.noder' diff --git a/script/vm/getLinks.lua b/script/vm/getLinks.lua index d2332504..b245bdaa 100644 --- a/script/vm/getLinks.lua +++ b/script/vm/getLinks.lua @@ -1,5 +1,5 @@ local guide = require 'parser.guide' ----@type vm +---@class vm local vm = require 'vm.vm' local files = require 'files' diff --git a/script/vm/vm.lua b/script/vm/vm.lua index 6abaaa0e..aa18ea73 100644 --- a/script/vm/vm.lua +++ b/script/vm/vm.lua @@ -14,7 +14,7 @@ local weakMT = { __mode = 'kv' } _ENV = nil ----@type vm +---@class vm local m = {} function m.getArgInfo(source) diff --git a/script/workspace/require-path.lua b/script/workspace/require-path.lua index 2ec2918c..e2149bac 100644 --- a/script/workspace/require-path.lua +++ b/script/workspace/require-path.lua @@ -27,7 +27,9 @@ local function getOnePath(path, searcher) return nil end -function m.getVisiblePath(path, searchers, strict) +function m.getVisiblePath(path) + local searchers = config.get 'Lua.runtime.path' + local strict = config.get 'Lua.runtime.pathStrict' path = path:gsub('^[/\\]+', '') local uri = furi.encode(path) local libraryPath = files.getLibraryPath(uri) @@ -91,7 +93,9 @@ files.watch(function (ev) end) config.watch(function (key, value, oldValue) - if key == 'Lua.completion.requireSeparator' then + if key == 'Lua.completion.requireSeparator' + or key == 'Lua.runtime.path' + or key == 'Lua.runtime.pathStrict' then m.flush() end end) diff --git a/script/workspace/workspace.lua b/script/workspace/workspace.lua index 240596a4..099196ce 100644 --- a/script/workspace/workspace.lua +++ b/script/workspace/workspace.lua @@ -74,6 +74,7 @@ local globInteferFace = { } --- 创建排除文件匹配器 +---@async function m.getNativeMatcher() if not m.path then return nil @@ -177,6 +178,7 @@ function m.getLibraryMatchers() end --- 文件是否被忽略 +---@async function m.isIgnored(uri) local path = m.getRelativePath(uri) local ignore = m.getNativeMatcher() @@ -186,6 +188,7 @@ function m.isIgnored(uri) return ignore(path) end +---@async function m.isValidLuaUri(uri) if not files.isLua(uri) then return false @@ -198,7 +201,7 @@ function m.isValidLuaUri(uri) end local function loadFileFactory(root, progressData, isLibrary) - return function (path) + return function (path) ---@async local uri = furi.encode(path) if files.isLua(uri) then if not isLibrary and progressData.preload >= config.get 'Lua.workspace.maxPreload' then @@ -246,7 +249,7 @@ local function loadFileFactory(root, progressData, isLibrary) log.info('++++As library of:', root) files.setLibraryPath(uri, root) end - files.setText(uri, text, false, true) + files.setText(uri, text, false) else files.remove(uri) end @@ -279,6 +282,7 @@ local function loadFileFactory(root, progressData, isLibrary) end end +---@async function m.awaitLoadFile(uri) local progressBar <close> = progress.create(lang.script.WORKSPACE_LOADING) local progressData = { @@ -299,6 +303,7 @@ function m.awaitLoadFile(uri) end --- 预读工作区内所有文件 +---@async function m.awaitPreload() local diagnostic = require 'provider.diagnostic' await.close 'preload' @@ -347,7 +352,7 @@ function m.awaitPreload() if isLoadingFiles then return end - await.call(function () + await.call(function () ---@async isLoadingFiles = true while true do local loader = table.remove(progressData.loaders) @@ -391,17 +396,22 @@ function m.findUrisByFilePath(path) return resultCache[path].results, resultCache[path].posts end tracy.ZoneBeginN('findUrisByFilePath #1') + local strict = config.get 'Lua.runtime.pathStrict' local results = {} local posts = {} for uri in files.eachFile() do if not uri:find(lpath, 1, true) then goto CONTINUE end + local relat = m.getRelativePath(uri) local pathLen = #path - local curPath = furi.decode(uri) + local curPath = relat local curLen = #curPath local seg = curPath:sub(curLen - pathLen, curLen - pathLen) if seg == '/' or seg == '\\' or seg == '' then + if strict and seg ~= '' then + goto CONTINUE + end local see = curPath:sub(curLen - pathLen + 1, curLen) if see == path then results[#results+1] = uri @@ -558,6 +568,7 @@ function m.init() m.reload() end +---@async function m.awaitReload() m.ready = false m.hasHitMaxPreload = false @@ -575,6 +586,7 @@ function m.awaitReload() end ---等待工作目录加载完成 +---@async function m.awaitReady() if m.isReady() then return @@ -592,7 +604,7 @@ function m.getLoadProcess() return m.fileLoaded, m.fileFound end -files.watch(function (ev, uri) +files.watch(function (ev, uri) ---@async if ev == 'close' and m.isIgnored(uri) and not files.isLibrary(uri) then @@ -610,7 +622,7 @@ config.watch(function (key, value, oldValue) end end) -fw.event(function (changes) +fw.event(function (changes) ---@async m.awaitReady() for _, change in ipairs(changes) do local path = change.path |