diff options
Diffstat (limited to 'server/src/core/hover/hover.lua')
-rw-r--r-- | server/src/core/hover/hover.lua | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/server/src/core/hover/hover.lua b/server/src/core/hover/hover.lua new file mode 100644 index 00000000..e07acf4a --- /dev/null +++ b/server/src/core/hover/hover.lua @@ -0,0 +1,225 @@ +local findLib = require 'core.find_lib' +local getFunctionHover = require 'core.hover.function' +local getFunctionHoverAsLib = require 'core.hover.lib_function' +local buildValueName = require 'core.hover.name' + +local OriginTypes = { + ['any'] = true, + ['nil'] = true, + ['integer'] = true, + ['number'] = true, + ['boolean'] = true, + ['string'] = true, + ['thread'] = true, + ['userdata'] = true, + ['table'] = true, + ['function'] = true, +} + +local function findClass(value) + -- 检查对象元表 + local metaValue = value:getMetaTable() + if not metaValue then + return nil + end + -- 检查元表中的 __name + local metaName = metaValue:rawGet('__name') + if metaName and type(metaName:getLiteral()) == 'string' then + return metaName:getLiteral() + end + -- 检查元表的 __index + local indexValue = metaValue:rawGet('__index') + if not indexValue then + return nil + end + -- 查找index方法中的以下字段: type name class + -- 允许多重继承 + return indexValue:eachChild(function (k, v) + -- 键值类型必须均为字符串 + if type(k) ~= 'string' then + return + end + if type(v:getLiteral()) ~= 'string' then + return + end + local lKey = k:lower() + if lKey == 'type' + or lKey == 'name' + or lKey == 'class' + then + -- 必须只有过一次赋值 + local hasSet = false + local ok = v:eachInfo(function (info) + if info.type == 'set' then + if hasSet then + return false + else + hasSet = true + end + end + end) + if ok == false then + return false + end + return v:getLiteral() + end + end) +end + +local function unpackTable(value) + local lines = {} + value:eachChild(function (key, child) + local kType = type(key) + if kType == 'table' then + key = ('[*%s]'):format(key:getType()) + elseif math.type(key) == 'integer' then + key = ('[%03d]'):format(key) + elseif kType == 'string' then + if key:find '^%d' or key:find '[^%w_]' then + key = ('[%q]'):format(key) + end + elseif key == '' then + key = '[*any]' + else + key = ('[%s]'):format(key) + end + + local vType = type(child:getLiteral()) + if vType == 'boolean' + or vType == 'integer' + or vType == 'number' + or vType == 'string' + then + lines[#lines+1] = ('%s: %s = %q'):format(key, child:getType(), child:getLiteral()) + else + lines[#lines+1] = ('%s: %s'):format(key, child:getType()) + end + end) + if #lines == 0 then + return '{}' + end + + -- 整理一下表 + local cleaned = {} + local used = {} + for _, line in ipairs(lines) do + if used[line] then + goto CONTINUE + end + used[line] = true + if line == '[*any]: any' then + goto CONTINUE + end + cleaned[#cleaned+1] = ' ' .. line .. ',' + :: CONTINUE :: + end + + table.sort(cleaned) + table.insert(cleaned, 1, '{') + cleaned[#cleaned+1] = '}' + return table.concat(cleaned, '\r\n') +end + +local function getValueHover(source, name, value, lib) + local valueType = value:getType() + + if not lib then + local class = findClass(value) + if class then + valueType = class + end + end + + if not OriginTypes[valueType] then + valueType = '*' .. valueType + end + + local tip + local literal + if lib then + value = lib.code or (lib.value and ('%q'):format(lib.value)) + tip = lib.description + else + literal = value:getLiteral() and ('%q'):format(value:getLiteral()) + end + + local tp + if source:bindLocal() then + tp = 'local' + elseif source:get 'global' then + tp = 'global' + elseif source:get 'simple' then + local simple = source:get 'simple' + if simple[1]:get 'global' then + tp = 'global' + else + tp = 'field' + end + else + tp = 'field' + end + + local text + if valueType == 'table' then + text = ('%s %s: %s'):format(tp, name, unpackTable(value)) + else + if literal == nil then + text = ('%s %s: %s'):format(tp, name, valueType) + else + text = ('%s %s: %s = %s'):format(tp, name, valueType, literal) + end + end + return { + label = text, + description = tip, + } +end + +local function getStringHover(result, lsp) + if not result.uri then + return nil + end + if not lsp or not lsp.workspace then + return nil + end + local path = lsp.workspace:relativePathByUri(result.uri) + return { + description = ('[%s](%s)'):format(path:string(), result.uri), + } +end + +local function hoverAsValue(source, lsp, select) + local lib, fullkey = findLib(source) + local value = source:bindValue() + local name = fullkey or buildValueName(source) + + local hover + if value:getType() == 'function' then + local object = source:get 'object' + if lib then + hover = getFunctionHoverAsLib(name, lib, object, select) + else + hover = getFunctionHover(name, value:getFunction(), object, select) + end + else + hover = getValueHover(source, name, value, lib) + end + + if not hover then + return nil + end + hover.name = name + return hover +end + +return function (source, lsp, select) + if not source then + return nil + end + if source.type ~= 'name' then + return + end + if source:bindValue() then + return hoverAsValue(source, lsp, select) + end +end |