diff options
author | 最萌小汐 <sumneko@hotmail.com> | 2019-03-06 17:34:21 +0800 |
---|---|---|
committer | 最萌小汐 <sumneko@hotmail.com> | 2019-03-06 17:34:21 +0800 |
commit | ca0341d0760a067b6554d5b7aeb8357ec84e564f (patch) | |
tree | c2061160ad422d4b164e863176e0b667ed74b457 /server/src/core/hover | |
parent | d889c7fa1fb23e7c83a9c085baf05a1d9f1b3ac1 (diff) | |
download | lua-language-server-ca0341d0760a067b6554d5b7aeb8357ec84e564f.zip |
更新自动完成
Diffstat (limited to 'server/src/core/hover')
-rw-r--r-- | server/src/core/hover/function.lua | 109 | ||||
-rw-r--r-- | server/src/core/hover/hover.lua | 225 | ||||
-rw-r--r-- | server/src/core/hover/init.lua | 1 | ||||
-rw-r--r-- | server/src/core/hover/lib_function.lua | 194 | ||||
-rw-r--r-- | server/src/core/hover/name.lua | 77 |
5 files changed, 606 insertions, 0 deletions
diff --git a/server/src/core/hover/function.lua b/server/src/core/hover/function.lua new file mode 100644 index 00000000..6be90b06 --- /dev/null +++ b/server/src/core/hover/function.lua @@ -0,0 +1,109 @@ +local function buildValueArgs(func, object, select) + if not func then + return '', nil + end + local names = {} + local values = {} + if func:getObject() then + names[#names+1] = 'self' + end + if func.args then + for _, arg in ipairs(func.args) do + names[#names+1] = arg:getName() + end + end + if func.argValues then + for i, value in ipairs(func.argValues) do + values[i] = value:getType() + end + end + local strs = {} + local start = 1 + if object then + start = 2 + if select then + select = select + 1 + end + end + local max + if func.source then + max = #names + else + max = math.max(#names, #values) + end + for i = start, max do + if i > start then + strs[#strs+1] = ', ' + end + local name = names[i] + local value = values[i] or 'any' + if i == select then + strs[#strs+1] = '@ARG' + end + if name then + strs[#strs+1] = name .. ': ' .. value + else + strs[#strs+1] = value + end + if i == select then + strs[#strs+1] = '@ARG' + end + end + if func:hasDots() then + if max > 0 then + strs[#strs+1] = ', ' + end + strs[#strs+1] = '...' + end + local text = table.concat(strs) + local argLabel = {} + for i = 1, 2 do + local pos = text:find('@ARG', 1, true) + if pos then + if i == 1 then + argLabel[i] = pos + else + argLabel[i] = pos - 1 + end + text = text:sub(1, pos-1) .. text:sub(pos+4) + end + end + if #argLabel == 0 then + argLabel = nil + end + return text, argLabel +end + +local function buildValueReturns(func) + if not func then + return '\n -> any' + end + if not func:get 'hasReturn' then + return '' + end + local strs = {} + if func.returns then + for i, rtn in ipairs(func.returns) do + strs[i] = rtn:getType() + end + end + if #strs == 0 then + strs[1] = 'any' + end + return '\n -> ' .. table.concat(strs, ', ') +end + +return function (name, func, object, select) + local args, argLabel = buildValueArgs(func, object, select) + local returns = buildValueReturns(func) + local headLen = #('function %s('):format(name) + local title = ('function %s(%s)%s'):format(name, args, returns) + if argLabel then + argLabel[1] = argLabel[1] + headLen + argLabel[2] = argLabel[2] + headLen + end + return { + label = title, + argLabel = argLabel, + } +end 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 diff --git a/server/src/core/hover/init.lua b/server/src/core/hover/init.lua new file mode 100644 index 00000000..be5b5632 --- /dev/null +++ b/server/src/core/hover/init.lua @@ -0,0 +1 @@ +return require 'core.hover.hover' diff --git a/server/src/core/hover/lib_function.lua b/server/src/core/hover/lib_function.lua new file mode 100644 index 00000000..cb67c7f9 --- /dev/null +++ b/server/src/core/hover/lib_function.lua @@ -0,0 +1,194 @@ + +local function buildLibArgs(lib, object, select) + if not lib.args then + return '' + end + local start + if object then + start = 2 + if select then + select = select + 1 + end + else + start = 1 + end + local strs = {} + for i = start, #lib.args do + local arg = lib.args[i] + if arg.optional then + if i > start then + strs[#strs+1] = ' [' + else + strs[#strs+1] = '[' + end + end + if i > start then + strs[#strs+1] = ', ' + end + + local argStr = {} + if i == select then + argStr[#argStr+1] = '@ARG' + end + if arg.name then + argStr[#argStr+1] = ('%s: '):format(arg.name) + end + if type(arg.type) == 'table' then + argStr[#argStr+1] = table.concat(arg.type, '/') + else + argStr[#argStr+1] = arg.type or 'any' + end + if arg.default then + argStr[#argStr+1] = ('(%q)'):format(arg.default) + end + if i == select then + argStr[#argStr+1] = '@ARG' + end + + for _, str in ipairs(argStr) do + strs[#strs+1] = str + end + if arg.optional == 'self' then + strs[#strs+1] = ']' + end + end + for _, arg in ipairs(lib.args) do + if arg.optional == 'after' then + strs[#strs+1] = ']' + end + end + local text = table.concat(strs) + local argLabel = {} + for i = 1, 2 do + local pos = text:find('@ARG', 1, true) + if pos then + if i == 1 then + argLabel[i] = pos + else + argLabel[i] = pos - 1 + end + text = text:sub(1, pos-1) .. text:sub(pos+4) + end + end + if #argLabel == 0 then + argLabel = nil + end + return text, argLabel +end + +local function buildLibReturns(lib) + if not lib.returns then + return '' + end + local strs = {} + for i, rtn in ipairs(lib.returns) do + if rtn.optional then + if i > 1 then + strs[#strs+1] = ' [' + else + strs[#strs+1] = '[' + end + end + if i > 1 then + strs[#strs+1] = ', ' + end + if rtn.name then + strs[#strs+1] = ('%s: '):format(rtn.name) + end + if type(rtn.type) == 'table' then + strs[#strs+1] = table.concat(rtn.type, '/') + else + strs[#strs+1] = rtn.type or 'any' + end + if rtn.default then + strs[#strs+1] = ('(%q)'):format(rtn.default) + end + if rtn.optional == 'self' then + strs[#strs+1] = ']' + end + end + for _, rtn in ipairs(lib.returns) do + if rtn.optional == 'after' then + strs[#strs+1] = ']' + end + end + return '\n -> ' .. table.concat(strs) +end + +local function buildEnum(lib) + if not lib.enums then + return '' + end + local container = table.container() + for _, enum in ipairs(lib.enums) do + if not enum.name or (not enum.enum and not enum.code) then + goto NEXT_ENUM + end + if not container[enum.name] then + container[enum.name] = {} + if lib.args then + for _, arg in ipairs(lib.args) do + if arg.name == enum.name then + container[enum.name].type = arg.type + break + end + end + end + if lib.returns then + for _, rtn in ipairs(lib.returns) do + if rtn.name == enum.name then + container[enum.name].type = rtn.type + break + end + end + end + end + table.insert(container[enum.name], enum) + ::NEXT_ENUM:: + end + local strs = {} + for name, enums in pairs(container) do + local tp + if type(enums.type) == 'table' then + tp = table.concat(enums.type, '/') + else + tp = enums.type + end + strs[#strs+1] = ('\n%s: %s'):format(name, tp or 'any') + for _, enum in ipairs(enums) do + if enum.default then + strs[#strs+1] = '\n -> ' + else + strs[#strs+1] = '\n | ' + end + if enum.code then + strs[#strs+1] = tostring(enum.code) + else + strs[#strs+1] = ('%q'):format(enum.enum) + end + if enum.description then + strs[#strs+1] = ' -- ' .. enum.description + end + end + end + return table.concat(strs) +end + +return function (name, lib, object, select) + local args, argLabel = buildLibArgs(lib, object, select) + local returns = buildLibReturns(lib) + local enum = buildEnum(lib) + local tip = lib.description + local headLen = #('function %s('):format(name) + local title = ('function %s(%s)%s'):format(name, args, returns) + if argLabel then + argLabel[1] = argLabel[1] + headLen + argLabel[2] = argLabel[2] + headLen + end + return { + label = title, + description = tip, + enum = enum, + argLabel = argLabel, + } +end diff --git a/server/src/core/hover/name.lua b/server/src/core/hover/name.lua new file mode 100644 index 00000000..4faab3f1 --- /dev/null +++ b/server/src/core/hover/name.lua @@ -0,0 +1,77 @@ +return function (source) + local value = source:bindValue() + local func = value:getFunction() + local declarat + if func then + declarat = func.source.name + else + declarat = source + end + if not declarat then + return source:getName() or '' + end + + local key + if declarat:get 'simple' then + local simple = declarat:get 'simple' + local chars = {} + for i, obj in ipairs(simple) do + if obj.type == 'name' then + chars[i] = obj[1] + elseif obj.type == 'index' then + chars[i] = '[?]' + elseif obj.type == 'call' then + chars[i] = '(?)' + elseif obj.type == ':' then + chars[i] = ':' + elseif obj.type == '.' then + chars[i] = '.' + else + chars[i] = '*' .. obj.type + end + if obj == declarat then + break + end + end + key = table.concat(chars) + elseif declarat.type == 'name' then + key = declarat[1] + elseif declarat.type == 'string' then + key = ('%q'):format(declarat[1]) + elseif declarat.type == 'number' or declarat.type == 'boolean' then + key = tostring(declarat[1]) + elseif declarat.type == 'simple' then + local chars = {} + for i, obj in ipairs(declarat) do + if obj.type == 'name' then + chars[i] = obj[1] + elseif obj.type == 'index' then + chars[i] = '[?]' + elseif obj.type == 'call' then + chars[i] = '(?)' + elseif obj.type == ':' then + chars[i] = ':' + elseif obj.type == '.' then + chars[i] = '.' + else + chars[i] = '*' .. obj.type + end + end + -- 这里有个特殊处理 + -- function mt:func() 以 mt.func 的形式调用时 + -- hover 显示为 mt.func(self) + if chars[#chars-1] == ':' then + if not source:get 'object' then + chars[#chars-1] = '.' + end + elseif chars[#chars-1] == '.' then + if source:get 'object' then + chars[#chars-1] = ':' + end + end + key = table.concat(chars) + else + key = '' + end + return key +end |