nixos/lua-lsp/script/core/signature.lua

186 lines
5.6 KiB
Lua

local files = require 'files'
local vm = require 'vm'
local hoverLabel = require 'core.hover.label'
local hoverDesc = require 'core.hover.description'
local guide = require 'parser.guide'
local lookback = require 'core.look-backward'
local function findNearCall(uri, ast, pos)
local text = files.getText(uri)
local state = files.getState(uri)
if not state or not text then
return nil
end
local nearCall
guide.eachSourceContain(ast.ast, pos, function (src)
if src.type == 'call'
or src.type == 'table'
or src.type == 'function' then
local finishOffset = guide.positionToOffset(state, src.finish)
-- call(),$
if src.finish <= pos
and text:sub(finishOffset, finishOffset) == ')' then
return
end
-- {},$
if src.finish <= pos
and text:sub(finishOffset, finishOffset) == '}' then
return
end
if not nearCall or nearCall.start <= src.start then
nearCall = src
end
end
end)
if not nearCall then
return nil
end
if nearCall.type ~= 'call' then
return nil
end
return nearCall
end
---@async
local function makeOneSignature(source, oop, index)
local label = hoverLabel(source, oop)
if not label then
return nil
end
-- 去掉返回值
label = label:gsub('%s*->.+', '')
local params = {}
local i = 0
local argStart, argLabel = label:match '()(%b())$'
local converted = argLabel
: sub(2, -2)
: gsub('%b<>', function (str)
return ('_'):rep(#str)
end)
: gsub('%b()', function (str)
return ('_'):rep(#str)
end)
: gsub('%b{}', function (str)
return ('_'):rep(#str)
end)
: gsub('[%[%]%(%)]', '_')
for start, finish in converted:gmatch '%s*()[^,]+()' do
i = i + 1
params[i] = {
label = {start + argStart - 1, finish - 1 + argStart},
}
end
-- 不定参数
if index and index > i and i > 0 then
local lastLabel = params[i].label
local text = label:sub(lastLabel[1] + 1, lastLabel[2])
if text:sub(1, 3) == '...' then
index = i
end
end
if #params < (index or 0) then
return nil
end
return {
label = label,
params = params,
index = index or 1,
description = hoverDesc(source),
}
end
---@async
local function makeSignatures(text, call, pos)
local func = call.node
local oop = func.type == 'method'
or func.type == 'getmethod'
or func.type == 'setmethod'
local index
if call.args then
local args = {}
for _, arg in ipairs(call.args) do
if arg.type ~= 'self' then
args[#args+1] = arg
end
end
local uri = guide.getUri(call)
local state = files.getState(uri)
for i, arg in ipairs(args) do
local startOffset = guide.positionToOffset(state, arg.start)
startOffset = lookback.findTargetSymbol(text, startOffset, '(')
or lookback.findTargetSymbol(text, startOffset, ',')
or startOffset
local startPos = guide.offsetToPosition(state, startOffset)
if startPos > pos then
index = i - 1
break
end
if pos <= arg.finish then
index = i
break
end
end
if not index then
local offset = guide.positionToOffset(state, pos)
local backSymbol = lookback.findSymbol(text, offset)
if backSymbol == ','
or backSymbol == '(' then
index = #args + 1
else
index = #args
end
end
end
local signs = {}
local node = vm.compileNode(func)
---@type vm.node
node = node:getData 'originNode' or node
local mark = {}
for src in node:eachObject() do
if (src.type == 'function' and not vm.isVarargFunctionWithOverloads(src))
or src.type == 'doc.type.function' then
if not mark[src] then
mark[src] = true
signs[#signs+1] = makeOneSignature(src, oop, index)
end
elseif src.type == 'global' and src.cate == 'type' then
---@cast src vm.global
for _, set in ipairs(src:getSets(guide.getUri(call))) do
if set.type == 'doc.class' then
for _, overload in ipairs(set.calls) do
local f = overload.overload
if not mark[f] then
mark[f] = true
signs[#signs+1] = makeOneSignature(f, oop, index)
end
end
end
end
end
end
return signs
end
---@async
return function (uri, pos)
local state = files.getState(uri)
local text = files.getText(uri)
if not state or not text then
return nil
end
local offset = guide.positionToOffset(state, pos)
pos = guide.offsetToPosition(state, lookback.skipSpace(text, offset))
local call = findNearCall(uri, state, pos)
if not call then
return nil
end
local signs = makeSignatures(text, call, pos)
if not signs or #signs == 0 then
return nil
end
table.sort(signs, function (a, b)
return #a.params < #b.params
end)
return signs
end