local vm = require 'vm' local util = require 'utility' local config = require 'config' local await = require 'await' local guide = require 'parser.guide' ---@param uri uri ---@param keys string[] ---@param nodeMap table ---@param reachMax integer local function buildAsHash(uri, keys, nodeMap, reachMax) local lines = {} lines[#lines+1] = '{' for _, key in ipairs(keys) do local node = nodeMap[key] local isOptional = node:isOptional() if isOptional then node = node:copy() node:removeOptional() end local ifr = vm.getInfer(node) local typeView = ifr:view(uri, 'unknown') local literalView = ifr:viewLiterals() if literalView then lines[#lines+1] = (' %s%s: %s = %s,'):format( key, isOptional and '?' or '', typeView, literalView ) else lines[#lines+1] = (' %s%s: %s,'):format( key, isOptional and '?' or '', typeView ) end end if reachMax > 0 then lines[#lines+1] = (' ...(+%d)'):format(reachMax) end lines[#lines+1] = '}' return table.concat(lines, '\n') end ---@param uri uri ---@param keys string[] ---@param nodeMap table ---@param reachMax integer local function buildAsConst(uri, keys, nodeMap, reachMax) local literalMap = {} for _, key in ipairs(keys) do literalMap[key] = vm.getInfer(nodeMap[key]):viewLiterals() end table.sort(keys, function (a, b) return tonumber(literalMap[a]) < tonumber(literalMap[b]) end) local lines = {} lines[#lines+1] = '{' for _, key in ipairs(keys) do local node = nodeMap[key] local isOptional = node:isOptional() if isOptional then node = node:copy() node:removeOptional() end local typeView = vm.getInfer(node):view(uri, 'unknown') local literalView = literalMap[key] if literalView then lines[#lines+1] = (' %s%s: %s = %s,'):format( key, isOptional and '?' or '', typeView, literalView ) else lines[#lines+1] = (' %s%s: %s,'):format( key, isOptional and '?' or '', typeView ) end end if reachMax > 0 then lines[#lines+1] = (' ...(+%d)'):format(reachMax) end lines[#lines+1] = '}' return table.concat(lines, '\n') end ---@param source parser.object ---@param fields parser.object[] local function getVisibleKeyMap(source, fields) local uri = guide.getUri(source) local keys = {} local map = {} local ignored = {} for _, field in ipairs(fields) do local key = vm.viewKey(field, uri) local rawKey = guide.getKeyName(field) if rawKey and rawKey ~= key then ignored[rawKey] = true map[rawKey] = nil end if not ignored[key] and vm.isVisible(source, field) then if key and not map[key] then map[key] = true end end end for key in pairs(map) do keys[#keys+1] = key end table.sort(keys, function (a, b) if a == b then return false end local s1 = 0 local s2 = 0 if a:sub(1, 1) == '_' then s1 = s1 + 10 end if b:sub(1, 1) == '_' then s2 = s2 + 10 end if a:sub(1, 1) == '[' then s1 = s1 + 1 end if b:sub(1, 1) == '[' then s2 = s2 + 1 end if s1 == s2 then return a < b end return s1 < s2 end) return keys, map end ---@async local function getNodeMap(uri, fields, keyMap) ---@type table local nodeMap = {} for _, field in ipairs(fields) do local key = vm.viewKey(field, uri) if not key or not keyMap[key] then goto CONTINUE end await.delay() local node = vm.compileNode(field) if nodeMap[key] then nodeMap[key]:merge(node) else nodeMap[key] = node:copy() end ::CONTINUE:: end return nodeMap end ---@async ---@return string? return function (source) local uri = guide.getUri(source) local maxFields = config.get(uri, 'Lua.hover.previewFields') if maxFields <= 0 then return nil end local node = vm.compileNode(source) for n in node:eachObject() do if n.type == 'global' and n.cate == 'type' then if n.name == 'string' or (n.name ~= 'unknown' and n.name ~= 'any' and vm.isSubType(uri, n.name, 'string')) then return nil end elseif n.type == 'doc.type.string' or n.type == 'string' then return nil elseif n.type == 'doc.type.sign' then return nil end end local fields = vm.getFields(source) local keys, map = getVisibleKeyMap(source, fields) if #keys == 0 then return nil end local reachMax = #keys - maxFields if #keys > maxFields then for i = maxFields + 1, #keys do map[keys[i]] = nil keys[i] = nil end end local nodeMap = getNodeMap(uri, fields, map) local isConsts = true for i = 1, #keys do await.delay() local key = keys[i] local literal = vm.getInfer(nodeMap[key]):viewLiterals() if not tonumber(literal) then isConsts = false end end local result if isConsts then result = buildAsConst(uri, keys, nodeMap, reachMax) else result = buildAsHash(uri, keys, nodeMap, reachMax) end --if timeUp then -- result = ('\n--%s\n%s'):format(lang.script.HOVER_TABLE_TIME_UP, result) --end return result end