diff options
Diffstat (limited to 'script-beta/core/completion.lua')
-rw-r--r-- | script-beta/core/completion.lua | 1284 |
1 files changed, 0 insertions, 1284 deletions
diff --git a/script-beta/core/completion.lua b/script-beta/core/completion.lua deleted file mode 100644 index 44874b39..00000000 --- a/script-beta/core/completion.lua +++ /dev/null @@ -1,1284 +0,0 @@ -local define = require 'proto.define' -local files = require 'files' -local guide = require 'parser.guide' -local matchKey = require 'core.matchkey' -local vm = require 'vm' -local getLabel = require 'core.hover.label' -local getName = require 'core.hover.name' -local getArg = require 'core.hover.arg' -local getDesc = require 'core.hover.description' -local getHover = require 'core.hover' -local config = require 'config' -local util = require 'utility' -local markdown = require 'provider.markdown' -local findSource = require 'core.find-source' -local await = require 'await' -local parser = require 'parser' -local keyWordMap = require 'core.keyword' -local workspace = require 'workspace' -local furi = require 'file-uri' -local rpath = require 'workspace.require-path' -local lang = require 'language' - -local stackID = 0 -local stacks = {} -local function stack(callback) - stackID = stackID + 1 - stacks[stackID] = callback - return stackID -end - -local function clearStack() - stacks = {} -end - -local function resolveStack(id) - local callback = stacks[id] - if not callback then - return nil - end - - -- 当进行新的 resolve 时,放弃当前的 resolve - await.close('completion.resove') - return await.await(callback, 'completion.resove') -end - -local function trim(str) - return str:match '^%s*(%S+)%s*$' -end - -local function isSpace(char) - if char == ' ' - or char == '\n' - or char == '\r' - or char == '\t' then - return true - end - return false -end - -local function skipSpace(text, offset) - for i = offset, 1, -1 do - local char = text:sub(i, i) - if not isSpace(char) then - return i - end - end - return 0 -end - -local function findWord(text, offset) - for i = offset, 1, -1 do - if not text:sub(i, i):match '[%w_]' then - if i == offset then - return nil - end - return text:sub(i+1, offset), i+1 - end - end - return text:sub(1, offset), 1 -end - -local function findSymbol(text, offset) - for i = offset, 1, -1 do - local char = text:sub(i, i) - if isSpace(char) then - goto CONTINUE - end - if char == '.' - or char == ':' - or char == '(' then - return char, i - else - return nil - end - ::CONTINUE:: - end - return nil -end - -local function findAnyPos(text, offset) - for i = offset, 1, -1 do - if not isSpace(text:sub(i, i)) then - return i - end - end - return nil -end - -local function findParent(ast, text, offset) - for i = offset, 1, -1 do - local char = text:sub(i, i) - if isSpace(char) then - goto CONTINUE - end - local oop - if char == '.' then - -- `..` 的情况 - if text:sub(i-1, i-1) == '.' then - return nil, nil - end - oop = false - elseif char == ':' then - oop = true - else - return nil, nil - end - local anyPos = findAnyPos(text, i-1) - if not anyPos then - return nil, nil - end - local parent = guide.eachSourceContain(ast.ast, anyPos, function (source) - if source.finish == anyPos then - return source - end - end) - if parent then - return parent, oop - end - ::CONTINUE:: - end - return nil, nil -end - -local function findParentInStringIndex(ast, text, offset) - local near, nearStart - guide.eachSourceContain(ast.ast, offset, function (source) - local start = guide.getStartFinish(source) - if not start then - return - end - if not nearStart or nearStart < start then - near = source - nearStart = start - end - end) - if not near or near.type ~= 'string' then - return - end - local parent = near.parent - if not parent or parent.index ~= near then - return - end - -- index不可能是oop模式 - return parent.node, false -end - -local function buildFunctionSnip(source, oop) - local name = getName(source):gsub('^.-[$.:]', '') - local defs = vm.getDefs(source, 'deep') - local args = '' - for _, def in ipairs(defs) do - local defArgs = getArg(def, oop) - if defArgs ~= '' then - args = defArgs - break - end - end - local id = 0 - args = args:gsub('[^,]+', function (arg) - id = id + 1 - return arg:gsub('^(%s*)(.+)', function (sp, word) - return ('%s${%d:%s}'):format(sp, id, word) - end) - end) - return ('%s(%s)'):format(name, args) -end - -local function buildDetail(source) - local types = vm.getInferType(source, 'deep') - local literals = vm.getInferLiteral(source, 'deep') - if literals then - return types .. ' = ' .. literals - else - return types - end -end - -local function getSnip(source) - local context = config.config.completion.displayContext - if context <= 0 then - return nil - end - local defs = vm.getRefs(source, 'deep') - for _, def in ipairs(defs) do - def = guide.getObjectValue(def) or def - if def ~= source and def.type == 'function' then - local uri = guide.getUri(def) - local text = files.getText(uri) - local lines = files.getLines(uri) - if not text then - goto CONTINUE - end - if vm.isMetaFile(uri) then - goto CONTINUE - end - local row = guide.positionOf(lines, def.start) - local firstRow = lines[row] - local lastRow = lines[math.min(row + context - 1, #lines)] - local snip = text:sub(firstRow.start, lastRow.finish) - return snip - end - ::CONTINUE:: - end -end - -local function buildDesc(source) - local hover = getHover.get(source) - local md = markdown() - md:add('lua', hover.label) - md:add('md', hover.description) - local snip = getSnip(source) - if snip then - md:add('md', '-------------') - md:add('lua', snip) - end - return md:string() -end - -local function buildFunction(results, source, oop, data) - local snipType = config.config.completion.callSnippet - if snipType == 'Disable' or snipType == 'Both' then - results[#results+1] = data - end - if snipType == 'Both' or snipType == 'Replace' then - local snipData = util.deepCopy(data) - snipData.kind = define.CompletionItemKind.Snippet - snipData.label = snipData.label .. '()' - snipData.insertText = buildFunctionSnip(source, oop) - snipData.insertTextFormat = 2 - snipData.id = stack(function () - return { - detail = buildDetail(source), - description = buildDesc(source), - } - end) - results[#results+1] = snipData - end -end - -local function isSameSource(ast, source, pos) - if not files.eq(guide.getUri(source), guide.getUri(ast.ast)) then - return false - end - if source.type == 'field' - or source.type == 'method' then - source = source.parent - end - return source.start <= pos and source.finish >= pos -end - -local function checkLocal(ast, word, offset, results) - local locals = guide.getVisibleLocals(ast.ast, offset) - for name, source in pairs(locals) do - if isSameSource(ast, source, offset) then - goto CONTINUE - end - if not matchKey(word, name) then - goto CONTINUE - end - if vm.hasType(source, 'function') then - buildFunction(results, source, false, { - label = name, - kind = define.CompletionItemKind.Function, - id = stack(function () - return { - detail = buildDetail(source), - description = buildDesc(source), - } - end), - }) - else - results[#results+1] = { - label = name, - kind = define.CompletionItemKind.Variable, - id = stack(function () - return { - detail = buildDetail(source), - description = buildDesc(source), - } - end), - } - end - ::CONTINUE:: - end -end - -local function checkFieldFromFieldToIndex(name, parent, word, start, offset) - if name:match '^[%a_][%w_]*$' then - return nil - end - local textEdit, additionalTextEdits - local uri = guide.getUri(parent) - local text = files.getText(uri) - local wordStart - if word == '' then - wordStart = text:match('()%S', start + 1) or (offset + 1) - else - wordStart = offset - #word + 1 - end - textEdit = { - start = wordStart, - finish = offset, - newText = ('[%q]'):format(name), - } - local nxt = parent.next - if nxt then - local dotStart - if nxt.type == 'setfield' - or nxt.type == 'getfield' - or nxt.type == 'tablefield' then - dotStart = nxt.dot.start - elseif nxt.type == 'setmethod' - or nxt.type == 'getmethod' then - dotStart = nxt.colon.start - end - if dotStart then - additionalTextEdits = { - { - start = dotStart, - finish = dotStart, - newText = '', - } - } - end - else - if config.config.runtime.version == 'Lua 5.1' - or config.config.runtime.version == 'LuaJIT' then - textEdit.newText = '_G' .. textEdit.newText - else - textEdit.newText = '_ENV' .. textEdit.newText - end - end - return textEdit, additionalTextEdits -end - -local function checkFieldThen(name, src, word, start, offset, parent, oop, results) - local value = guide.getObjectValue(src) or src - local kind = define.CompletionItemKind.Field - if value.type == 'function' then - if oop then - kind = define.CompletionItemKind.Method - else - kind = define.CompletionItemKind.Function - end - buildFunction(results, src, oop, { - label = name, - kind = kind, - deprecated = vm.isDeprecated(src) or nil, - id = stack(function () - return { - detail = buildDetail(src), - description = buildDesc(src), - } - end), - }) - return - end - if oop then - return - end - local literal = guide.getLiteral(value) - if literal ~= nil then - kind = define.CompletionItemKind.Enum - end - local textEdit, additionalTextEdits - if parent.next and parent.next.index then - local str = parent.next.index - textEdit = { - start = str.start + #str[2], - finish = offset, - newText = name, - } - else - textEdit, additionalTextEdits = checkFieldFromFieldToIndex(name, parent, word, start, offset) - end - results[#results+1] = { - label = name, - kind = kind, - textEdit = textEdit, - additionalTextEdits = additionalTextEdits, - id = stack(function () - return { - detail = buildDetail(src), - description = buildDesc(src), - } - end) - } -end - -local function checkFieldOfRefs(refs, ast, word, start, offset, parent, oop, results, locals, isGlobal) - local fields = {} - local count = 0 - for _, src in ipairs(refs) do - local key = vm.getKeyName(src) - if not key or key:sub(1, 1) ~= 's' then - goto CONTINUE - end - if isSameSource(ast, src, start) then - -- 由于fastGlobal的优化,全局变量只会找出一个值,有可能找出自己 - -- 所以遇到自己的时候重新找一下有没有其他定义 - if not isGlobal then - goto CONTINUE - end - if #vm.getGlobals(key) <= 1 then - goto CONTINUE - elseif not vm.isSet(src) then - src = vm.getGlobalSets(key)[1] or src - end - end - local name = key:sub(3) - if locals and locals[name] then - goto CONTINUE - end - if not matchKey(word, name, count >= 100) then - goto CONTINUE - end - local last = fields[name] - if not last then - fields[name] = src - count = count + 1 - goto CONTINUE - end - if src.type == 'tablefield' - or src.type == 'setfield' - or src.type == 'tableindex' - or src.type == 'setindex' - or src.type == 'setmethod' - or src.type == 'setglobal' then - fields[name] = src - goto CONTINUE - end - ::CONTINUE:: - end - for name, src in util.sortPairs(fields) do - checkFieldThen(name, src, word, start, offset, parent, oop, results) - end -end - -local function checkField(ast, word, start, offset, parent, oop, results) - local refs = vm.getFields(parent, 'deep') - checkFieldOfRefs(refs, ast, word, start, offset, parent, oop, results) -end - -local function checkGlobal(ast, word, start, offset, parent, oop, results) - local locals = guide.getVisibleLocals(ast.ast, offset) - local refs = vm.getGlobalSets '*' - checkFieldOfRefs(refs, ast, word, start, offset, parent, oop, results, locals, 'global') -end - -local function checkTableField(ast, word, start, results) - local source = guide.eachSourceContain(ast.ast, start, function (source) - if source.start == start - and source.parent - and source.parent.type == 'table' then - return source - end - end) - if not source then - return - end - local used = {} - guide.eachSourceType(ast.ast, 'tablefield', function (src) - if not src.field then - return - end - local key = src.field[1] - if not used[key] - and matchKey(word, key) - and src ~= source then - used[key] = true - results[#results+1] = { - label = key, - kind = define.CompletionItemKind.Property, - } - end - end) -end - -local function checkCommon(word, text, offset, results) - local used = {} - for _, result in ipairs(results) do - used[result.label] = true - end - for _, data in ipairs(keyWordMap) do - used[data[1]] = true - end - for str, pos in text:gmatch '([%a_][%w_]*)()' do - if not used[str] and pos - 1 ~= offset then - used[str] = true - if matchKey(word, str) then - results[#results+1] = { - label = str, - kind = define.CompletionItemKind.Text, - } - end - end - end -end - -local function isInString(ast, offset) - return guide.eachSourceContain(ast.ast, offset, function (source) - if source.type == 'string' then - return true - end - end) -end - -local function checkKeyWord(ast, text, start, word, hasSpace, afterLocal, results) - local snipType = config.config.completion.keywordSnippet - for _, data in ipairs(keyWordMap) do - local key = data[1] - local eq - if hasSpace then - eq = word == key - else - eq = matchKey(word, key) - end - if afterLocal and key ~= 'function' then - eq = false - end - if eq then - local replaced - local extra - if snipType == 'Both' or snipType == 'Replace' then - local func = data[2] - if func then - replaced = func(hasSpace, results) - extra = true - end - end - if snipType == 'Both' then - replaced = false - end - if not replaced then - if not hasSpace then - local item = { - label = key, - kind = define.CompletionItemKind.Keyword, - } - if extra then - table.insert(results, #results, item) - else - results[#results+1] = item - end - end - end - local checkStop = data[3] - if checkStop then - local stop = checkStop(ast, start) - if stop then - return true - end - end - end - end -end - -local function checkProvideLocal(ast, word, start, results) - local block - guide.eachSourceContain(ast.ast, start, function (source) - if source.type == 'function' - or source.type == 'main' then - block = source - end - end) - if not block then - return - end - local used = {} - guide.eachSourceType(block, 'getglobal', function (source) - if source.start > start - and not used[source[1]] - and matchKey(word, source[1]) then - used[source[1]] = true - results[#results+1] = { - label = source[1], - kind = define.CompletionItemKind.Variable, - } - end - end) - guide.eachSourceType(block, 'getlocal', function (source) - if source.start > start - and not used[source[1]] - and matchKey(word, source[1]) then - used[source[1]] = true - results[#results+1] = { - label = source[1], - kind = define.CompletionItemKind.Variable, - } - end - end) -end - -local function checkFunctionArgByDocParam(ast, word, start, results) - local func = guide.eachSourceContain(ast.ast, start, function (source) - if source.type == 'function' then - return source - end - end) - if not func then - return - end - local docs = func.bindDocs - if not docs then - return - end - local params = {} - for _, doc in ipairs(docs) do - if doc.type == 'doc.param' then - params[#params+1] = doc - end - end - local firstArg = func.args and func.args[1] - if not firstArg - or firstArg.start <= start and firstArg.finish >= start then - local firstParam = params[1] - if firstParam and matchKey(word, firstParam.param[1]) then - local label = {} - for _, param in ipairs(params) do - label[#label+1] = param.param[1] - end - results[#results+1] = { - label = table.concat(label, ', '), - kind = define.CompletionItemKind.Snippet, - } - end - end - for _, doc in ipairs(params) do - if matchKey(word, doc.param[1]) then - results[#results+1] = { - label = doc.param[1], - kind = define.CompletionItemKind.Interface, - } - end - end -end - -local function isAfterLocal(text, start) - local pos = skipSpace(text, start-1) - local word = findWord(text, pos) - return word == 'local' -end - -local function checkUri(ast, text, offset, results) - local collect = {} - local myUri = guide.getUri(ast.ast) - guide.eachSourceContain(ast.ast, offset, function (source) - if source.type ~= 'string' then - return - end - local callargs = source.parent - if not callargs or callargs.type ~= 'callargs' then - return - end - if callargs[1] ~= source then - return - end - local call = callargs.parent - local func = call.node - local literal = guide.getLiteral(source) - local libName = vm.getLibraryName(func) - if not libName then - return - end - if libName == 'require' then - for uri in files.eachFile() do - uri = files.getOriginUri(uri) - if files.eq(myUri, uri) then - goto CONTINUE - end - if vm.isMetaFile(uri) then - goto CONTINUE - end - local path = workspace.getRelativePath(uri) - local infos = rpath.getVisiblePath(path, config.config.runtime.path) - for _, info in ipairs(infos) do - if matchKey(literal, info.expect) then - if not collect[info.expect] then - collect[info.expect] = { - textEdit = { - start = source.start + #source[2], - finish = source.finish - #source[2], - } - } - end - collect[info.expect][#collect[info.expect]+1] = ([=[* [%s](%s) %s]=]):format( - path, - uri, - lang.script('HOVER_USE_LUA_PATH', info.searcher) - ) - end - end - ::CONTINUE:: - end - elseif libName == 'dofile' - or libName == 'loadfile' then - for uri in files.eachFile() do - uri = files.getOriginUri(uri) - if files.eq(myUri, uri) then - goto CONTINUE - end - if vm.isMetaFile(uri) then - goto CONTINUE - end - local path = workspace.getRelativePath(uri) - if matchKey(literal, path) then - if not collect[path] then - collect[path] = { - textEdit = { - start = source.start + #source[2], - finish = source.finish - #source[2], - } - } - end - collect[path][#collect[path]+1] = ([=[[%s](%s)]=]):format( - path, - uri - ) - end - ::CONTINUE:: - end - end - end) - for label, infos in util.sortPairs(collect) do - local mark = {} - local des = {} - for _, info in ipairs(infos) do - if not mark[info] then - mark[info] = true - des[#des+1] = info - end - end - results[#results+1] = { - label = label, - kind = define.CompletionItemKind.Reference, - description = table.concat(des, '\n'), - textEdit = infos.textEdit, - } - end -end - -local function checkLenPlusOne(ast, text, offset, results) - guide.eachSourceContain(ast.ast, offset, function (source) - if source.type == 'getindex' - or source.type == 'setindex' then - local _, pos = text:find('%s*%[%s*%#', source.node.finish) - if not pos then - return - end - local nodeText = text:sub(source.node.start, source.node.finish) - local writingText = trim(text:sub(pos + 1, offset - 1)) or '' - if not matchKey(writingText, nodeText) then - return - end - if source.parent == guide.getParentBlock(source) then - -- state - local label = text:match('%#[ \t]*', pos) .. nodeText .. '+1' - local eq = text:find('^%s*%]?%s*%=', source.finish) - local newText = label .. ']' - if not eq then - newText = newText .. ' = ' - end - results[#results+1] = { - label = label, - kind = define.CompletionItemKind.Snippet, - textEdit = { - start = pos, - finish = source.finish, - newText = newText, - }, - } - else - -- exp - local label = text:match('%#[ \t]*', pos) .. nodeText - local newText = label .. ']' - results[#results+1] = { - label = label, - kind = define.CompletionItemKind.Snippet, - textEdit = { - start = pos, - finish = source.finish, - newText = newText, - }, - } - end - end - end) -end - -local function isFuncArg(ast, offset) - return guide.eachSourceContain(ast.ast, offset, function (source) - if source.type == 'funcargs' then - return true - end - end) -end - -local function trySpecial(ast, text, offset, results) - if isInString(ast, offset) then - checkUri(ast, text, offset, results) - return - end - -- x[#x+1] - checkLenPlusOne(ast, text, offset, results) -end - -local function tryIndex(ast, text, offset, results) - local parent, oop = findParentInStringIndex(ast, text, offset) - if not parent then - return - end - local word = parent.next.index[1] - checkField(ast, word, offset, offset, parent, oop, results) -end - -local function tryWord(ast, text, offset, results) - local finish = skipSpace(text, offset) - local word, start = findWord(text, finish) - if not word then - return nil - end - local hasSpace = finish ~= offset - if isInString(ast, offset) then - else - local parent, oop = findParent(ast, text, start - 1) - if parent then - if not hasSpace then - checkField(ast, word, start, offset, parent, oop, results) - end - elseif isFuncArg(ast, offset) then - checkProvideLocal(ast, word, start, results) - checkFunctionArgByDocParam(ast, word, start, results) - else - local afterLocal = isAfterLocal(text, start) - local stop = checkKeyWord(ast, text, start, word, hasSpace, afterLocal, results) - if stop then - return - end - if not hasSpace then - if afterLocal then - checkProvideLocal(ast, word, start, results) - else - checkLocal(ast, word, start, results) - checkTableField(ast, word, start, results) - local env = guide.getENV(ast.ast, start) - checkGlobal(ast, word, start, offset, env, false, results) - end - end - end - if not hasSpace then - checkCommon(word, text, offset, results) - end - end -end - -local function trySymbol(ast, text, offset, results) - local symbol, start = findSymbol(text, offset) - if not symbol then - return nil - end - if isInString(ast, offset) then - return nil - end - if symbol == '.' - or symbol == ':' then - local parent, oop = findParent(ast, text, start) - if parent then - checkField(ast, '', start, offset, parent, oop, results) - end - end - if symbol == '(' then - checkFunctionArgByDocParam(ast, '', start, results) - end -end - -local function getCallEnums(source, index) - if source.type == 'function' and source.bindDocs then - if not source.args then - return - end - local arg - if index <= #source.args then - arg = source.args[index] - else - local lastArg = source.args[#source.args] - if lastArg.type == '...' then - arg = lastArg - else - return - end - end - for _, doc in ipairs(source.bindDocs) do - if doc.type == 'doc.param' - and doc.param[1] == arg[1] then - local enums = {} - for _, enum in ipairs(vm.getDocEnums(doc.extends)) do - enums[#enums+1] = { - label = enum[1], - description = enum.comment, - kind = define.CompletionItemKind.EnumMember, - } - end - return enums - elseif doc.type == 'doc.vararg' - and arg.type == '...' then - local enums = {} - for _, enum in ipairs(vm.getDocEnums(doc.vararg)) do - enums[#enums+1] = { - label = enum[1], - description = enum.comment, - kind = define.CompletionItemKind.EnumMember, - } - end - return enums - end - end - end -end - -local function tryLabelInString(label, arg) - if not arg or arg.type ~= 'string' then - return label - end - local str = parser:grammar(label, 'String') - if not str then - return label - end - if not matchKey(arg[1], str[1]) then - return nil - end - return util.viewString(str[1], arg[2]) -end - -local function mergeEnums(a, b, text, arg) - local mark = {} - for _, enum in ipairs(a) do - mark[enum.label] = true - end - for _, enum in ipairs(b) do - local label = tryLabelInString(enum.label, arg) - if label and not mark[label] then - mark[label] = true - local result = { - label = label, - kind = define.CompletionItemKind.EnumMember, - description = enum.description, - textEdit = arg and { - start = arg.start, - finish = arg.finish, - newText = label, - }, - } - a[#a+1] = result - end - end -end - -local function findCall(ast, text, offset) - local call - guide.eachSourceContain(ast.ast, offset, function (src) - if src.type == 'call' then - if not call or call.start < src.start then - call = src - end - end - end) - return call -end - -local function getCallArgInfo(call, text, offset) - if not call.args then - return 1, nil - end - for index, arg in ipairs(call.args) do - if arg.start <= offset and arg.finish >= offset then - return index, arg - end - end - return #call.args + 1, nil -end - -local function tryCallArg(ast, text, offset, results) - local call = findCall(ast, text, offset) - if not call then - return - end - local myResults = {} - local argIndex, arg = getCallArgInfo(call, text, offset) - if arg and arg.type == 'function' then - return - end - local defs = vm.getDefs(call.node, 'deep') - for _, def in ipairs(defs) do - def = guide.getObjectValue(def) or def - local enums = getCallEnums(def, argIndex) - if enums then - mergeEnums(myResults, enums, text, arg) - end - end - for _, enum in ipairs(myResults) do - results[#results+1] = enum - end -end - -local function getComment(ast, offset) - for _, comm in ipairs(ast.comms) do - if offset >= comm.start and offset <= comm.finish then - return comm - end - end - return nil -end - -local function tryLuaDocCate(line, results) - local word = line:sub(3) - for _, docType in ipairs { - 'class', - 'type', - 'alias', - 'param', - 'return', - 'field', - 'generic', - 'vararg', - 'overload', - 'deprecated', - 'meta', - 'version', - } do - if matchKey(word, docType) then - results[#results+1] = { - label = docType, - kind = define.CompletionItemKind.Event, - } - end - end -end - -local function getLuaDocByContain(ast, offset) - local result - local range = math.huge - guide.eachSourceContain(ast.ast.docs, offset, function (src) - if not src.start then - return - end - if range >= offset - src.start - and offset <= src.finish then - range = offset - src.start - result = src - end - end) - return result -end - -local function getLuaDocByErr(ast, text, start, offset) - local targetError - for _, err in ipairs(ast.errs) do - if err.finish <= offset - and err.start >= start then - if not text:sub(err.finish + 1, offset):find '%S' then - targetError = err - break - end - end - end - if not targetError then - return nil - end - local targetDoc - for i = #ast.ast.docs, 1, -1 do - local doc = ast.ast.docs[i] - if doc.finish <= targetError.start then - targetDoc = doc - break - end - end - return targetError, targetDoc -end - -local function tryLuaDocBySource(ast, offset, source, results) - if source.type == 'doc.extends.name' then - if source.parent.type == 'doc.class' then - for _, doc in ipairs(vm.getDocTypes '*') do - if doc.type == 'doc.class.name' - and doc.parent ~= source.parent - and matchKey(source[1], doc[1]) then - results[#results+1] = { - label = doc[1], - kind = define.CompletionItemKind.Class, - } - end - end - end - elseif source.type == 'doc.type.name' then - for _, doc in ipairs(vm.getDocTypes '*') do - if (doc.type == 'doc.class.name' or doc.type == 'doc.alias.name') - and doc.parent ~= source.parent - and matchKey(source[1], doc[1]) then - results[#results+1] = { - label = doc[1], - kind = define.CompletionItemKind.Class, - } - end - end - elseif source.type == 'doc.param.name' then - local funcs = {} - guide.eachSourceBetween(ast.ast, offset, math.huge, function (src) - if src.type == 'function' and src.start > offset then - funcs[#funcs+1] = src - end - end) - table.sort(funcs, function (a, b) - return a.start < b.start - end) - local func = funcs[1] - if not func or not func.args then - return - end - for _, arg in ipairs(func.args) do - if arg[1] and matchKey(source[1], arg[1]) then - results[#results+1] = { - label = arg[1], - kind = define.CompletionItemKind.Interface, - } - end - end - end -end - -local function tryLuaDocByErr(ast, offset, err, docState, results) - if err.type == 'LUADOC_MISS_CLASS_EXTENDS_NAME' then - for _, doc in ipairs(vm.getDocTypes '*') do - if doc.type == 'doc.class.name' - and doc.parent ~= docState then - results[#results+1] = { - label = doc[1], - kind = define.CompletionItemKind.Class, - } - end - end - elseif err.type == 'LUADOC_MISS_TYPE_NAME' then - for _, doc in ipairs(vm.getDocTypes '*') do - if (doc.type == 'doc.class.name' or doc.type == 'doc.alias.name') then - results[#results+1] = { - label = doc[1], - kind = define.CompletionItemKind.Class, - } - end - end - elseif err.type == 'LUADOC_MISS_PARAM_NAME' then - local funcs = {} - guide.eachSourceBetween(ast.ast, offset, math.huge, function (src) - if src.type == 'function' and src.start > offset then - funcs[#funcs+1] = src - end - end) - table.sort(funcs, function (a, b) - return a.start < b.start - end) - local func = funcs[1] - if not func or not func.args then - return - end - local label = {} - local insertText = {} - for i, arg in ipairs(func.args) do - if arg[1] then - label[#label+1] = arg[1] - if i == 1 then - insertText[i] = ('%s ${%d:any}'):format(arg[1], i) - else - insertText[i] = ('---@param %s ${%d:any}'):format(arg[1], i) - end - end - end - results[#results+1] = { - label = table.concat(label, ', '), - kind = define.CompletionItemKind.Snippet, - insertTextFormat = 2, - insertText = table.concat(insertText, '\n'), - } - for i, arg in ipairs(func.args) do - if arg[1] then - results[#results+1] = { - label = arg[1], - kind = define.CompletionItemKind.Interface, - } - end - end - end -end - -local function tryLuaDocFeatures(line, ast, comm, offset, results) -end - -local function tryLuaDoc(ast, text, offset, results) - local comm = getComment(ast, offset) - local line = text:sub(comm.start, offset) - if not line then - return - end - if line:sub(1, 2) ~= '-@' then - return - end - -- 尝试 ---@$ - local cate = line:match('%a*', 3) - if #cate + 2 >= #line then - tryLuaDocCate(line, results) - return - end - -- 尝试一些其他特征 - if tryLuaDocFeatures(line, ast, comm, offset, results) then - return - end - -- 根据输入中的source来补全 - local source = getLuaDocByContain(ast, offset) - if source then - tryLuaDocBySource(ast, offset, source, results) - return - end - -- 根据附近的错误消息来补全 - local err, doc = getLuaDocByErr(ast, text, comm.start, offset) - if err then - tryLuaDocByErr(ast, offset, err, doc, results) - return - end -end - -local function completion(uri, offset) - local ast = files.getAst(uri) - local text = files.getText(uri) - local results = {} - clearStack() - if ast then - if getComment(ast, offset) then - tryLuaDoc(ast, text, offset, results) - else - trySpecial(ast, text, offset, results) - tryWord(ast, text, offset, results) - tryIndex(ast, text, offset, results) - trySymbol(ast, text, offset, results) - tryCallArg(ast, text, offset, results) - end - else - local word = findWord(text, offset) - if word then - checkCommon(word, text, offset, results) - end - end - - if #results == 0 then - return nil - end - return results -end - -local function resolve(id) - return resolveStack(id) -end - -return { - completion = completion, - resolve = resolve, -} |