summaryrefslogtreecommitdiff
path: root/server/src/core/hover/hover.lua
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/core/hover/hover.lua')
-rw-r--r--server/src/core/hover/hover.lua225
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