338 lines
9.2 KiB
Lua
338 lines
9.2 KiB
Lua
|
local files = require 'files'
|
||
|
local vm = require 'vm'
|
||
|
local config = require 'config'
|
||
|
local guide = require 'parser.guide'
|
||
|
local await = require 'await'
|
||
|
local define = require 'proto.define'
|
||
|
local lang = require 'language'
|
||
|
local substr = require 'core.substring'
|
||
|
|
||
|
---@async
|
||
|
local function typeHint(uri, results, start, finish)
|
||
|
local state = files.getState(uri)
|
||
|
if not state then
|
||
|
return
|
||
|
end
|
||
|
local mark = {}
|
||
|
guide.eachSourceBetween(state.ast, start, finish, function (source) ---@async
|
||
|
if source.type ~= 'local'
|
||
|
and source.type ~= 'setglobal'
|
||
|
and source.type ~= 'tablefield'
|
||
|
and source.type ~= 'tableindex'
|
||
|
and source.type ~= 'setfield'
|
||
|
and source.type ~= 'setindex' then
|
||
|
return
|
||
|
end
|
||
|
if source[1] == '_' then
|
||
|
return
|
||
|
end
|
||
|
if source.value and guide.isLiteral(source.value) then
|
||
|
return
|
||
|
end
|
||
|
if source.parent.type == 'funcargs' then
|
||
|
if not config.get(uri, 'Lua.hint.paramType') then
|
||
|
return
|
||
|
end
|
||
|
else
|
||
|
if not config.get(uri, 'Lua.hint.setType') then
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
await.delay()
|
||
|
local view = vm.getInfer(source):view(uri)
|
||
|
if view == 'any'
|
||
|
or view == 'unknown'
|
||
|
or view == 'nil' then
|
||
|
return
|
||
|
end
|
||
|
local src = source
|
||
|
if source.type == 'tablefield' then
|
||
|
src = source.field
|
||
|
elseif source.type == 'tableindex' then
|
||
|
src = source.index
|
||
|
end
|
||
|
if not src then
|
||
|
return
|
||
|
end
|
||
|
if mark[src] then
|
||
|
return
|
||
|
end
|
||
|
mark[src] = true
|
||
|
results[#results+1] = {
|
||
|
text = ':' .. view,
|
||
|
offset = src.finish,
|
||
|
kind = define.InlayHintKind.Type,
|
||
|
where = 'right',
|
||
|
source = source,
|
||
|
}
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
local function getParams(func)
|
||
|
if not func.args or #func.args == 0 then
|
||
|
return nil
|
||
|
end
|
||
|
local params = {}
|
||
|
for _, arg in ipairs(func.args) do
|
||
|
if arg.type == '...' then
|
||
|
break
|
||
|
end
|
||
|
params[#params+1] = arg
|
||
|
end
|
||
|
if #params == 0 then
|
||
|
return nil
|
||
|
end
|
||
|
return params
|
||
|
end
|
||
|
|
||
|
local function hasLiteralArgInCall(call)
|
||
|
if not call.args then
|
||
|
return false
|
||
|
end
|
||
|
for _, arg in ipairs(call.args) do
|
||
|
if guide.isLiteral(arg) then
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
---@async
|
||
|
local function paramName(uri, results, start, finish)
|
||
|
local paramConfig = config.get(uri, 'Lua.hint.paramName')
|
||
|
if not paramConfig or paramConfig == 'Disable' then
|
||
|
return
|
||
|
end
|
||
|
local state = files.getState(uri)
|
||
|
if not state then
|
||
|
return
|
||
|
end
|
||
|
local mark = {}
|
||
|
guide.eachSourceBetween(state.ast, start, finish, function (source) ---@async
|
||
|
if source.type ~= 'call' then
|
||
|
return
|
||
|
end
|
||
|
if paramConfig == 'Literal' and not hasLiteralArgInCall(source) then
|
||
|
return
|
||
|
end
|
||
|
if not source.args then
|
||
|
return
|
||
|
end
|
||
|
await.delay()
|
||
|
local defs = vm.getDefs(source.node)
|
||
|
if not defs then
|
||
|
return
|
||
|
end
|
||
|
local params
|
||
|
for _, def in ipairs(defs) do
|
||
|
if def.type == 'function' then
|
||
|
params = getParams(def)
|
||
|
if params then
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
if not params then
|
||
|
return
|
||
|
end
|
||
|
local firstIndex = 1
|
||
|
if source.node and source.node.type == 'getmethod' then
|
||
|
firstIndex = 2
|
||
|
end
|
||
|
for i = firstIndex, #source.args do
|
||
|
local arg = source.args[i]
|
||
|
if not mark[arg]
|
||
|
and (paramConfig ~= 'Literal' or guide.isLiteral(arg)) then
|
||
|
mark[arg] = true
|
||
|
local param = params[i]
|
||
|
if param and param[1] then
|
||
|
results[#results+1] = {
|
||
|
text = param[1] .. ':',
|
||
|
offset = arg.start,
|
||
|
kind = define.InlayHintKind.Parameter,
|
||
|
where = 'left',
|
||
|
source = param,
|
||
|
}
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
---@async
|
||
|
local function arrayIndex(uri, results, start, finish)
|
||
|
local state = files.getState(uri)
|
||
|
if not state then
|
||
|
return
|
||
|
end
|
||
|
local option = config.get(uri, 'Lua.hint.arrayIndex')
|
||
|
if option == 'Disable' then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local mixedOrLargeTable = {}
|
||
|
local function isMixedOrLargeTable(tbl)
|
||
|
if mixedOrLargeTable[tbl] ~= nil then
|
||
|
return mixedOrLargeTable[tbl]
|
||
|
end
|
||
|
if #tbl > 3 then
|
||
|
mixedOrLargeTable[tbl] = true
|
||
|
return true
|
||
|
end
|
||
|
for _, child in ipairs(tbl) do
|
||
|
if child.type ~= 'tableexp' then
|
||
|
mixedOrLargeTable[tbl] = true
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
mixedOrLargeTable[tbl] = false
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
---@async
|
||
|
guide.eachSourceType(state.ast, 'table', function (source)
|
||
|
if source.finish < start or source.start > finish then
|
||
|
return
|
||
|
end
|
||
|
await.delay()
|
||
|
if option == 'Auto' then
|
||
|
if not isMixedOrLargeTable(source) then
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
local list = {}
|
||
|
local max = 0
|
||
|
for _, field in ipairs(source) do
|
||
|
if field.type == 'tableexp'
|
||
|
and field.start < finish
|
||
|
and field.finish > start then
|
||
|
list[#list+1] = field
|
||
|
if field.tindex > max then
|
||
|
max = field.tindex
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if #list > 0 then
|
||
|
local length = #tostring(max)
|
||
|
local fmt = '[%0' .. length .. 'd]'
|
||
|
for _, field in ipairs(list) do
|
||
|
results[#results+1] = {
|
||
|
text = fmt:format(field.tindex),
|
||
|
offset = field.start,
|
||
|
kind = define.InlayHintKind.Other,
|
||
|
where = 'left',
|
||
|
source = field.parent,
|
||
|
}
|
||
|
end
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
end
|
||
|
|
||
|
---@async
|
||
|
local function awaitHint(uri, results, start, finish)
|
||
|
local awaitConfig = config.get(uri, 'Lua.hint.await')
|
||
|
if not awaitConfig then
|
||
|
return
|
||
|
end
|
||
|
local state = files.getState(uri)
|
||
|
if not state then
|
||
|
return
|
||
|
end
|
||
|
guide.eachSourceBetween(state.ast, start, finish, function (source) ---@async
|
||
|
if source.type ~= 'call' then
|
||
|
return
|
||
|
end
|
||
|
await.delay()
|
||
|
local node = source.node
|
||
|
if not vm.isAsyncCall(source) then
|
||
|
return
|
||
|
end
|
||
|
results[#results+1] = {
|
||
|
text = 'await ',
|
||
|
offset = node.start,
|
||
|
kind = define.InlayHintKind.Other,
|
||
|
where = 'left',
|
||
|
tooltip = lang.script.HOVER_AWAIT_TOOLTIP,
|
||
|
}
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
local blockTypes = {
|
||
|
'main',
|
||
|
'function',
|
||
|
'for',
|
||
|
'loop',
|
||
|
'in',
|
||
|
'do',
|
||
|
'repeat',
|
||
|
'while',
|
||
|
'ifblock',
|
||
|
'elseifblock',
|
||
|
'elseblock',
|
||
|
}
|
||
|
|
||
|
---@async
|
||
|
local function semicolonHint(uri, results, start, finish)
|
||
|
local state = files.getState(uri)
|
||
|
if not state then
|
||
|
return
|
||
|
end
|
||
|
local mode = config.get(uri, 'Lua.hint.semicolon')
|
||
|
if mode == 'Disable' then
|
||
|
return
|
||
|
end
|
||
|
local subber = substr(state)
|
||
|
---@async
|
||
|
guide.eachSourceTypes(state.ast, blockTypes, function (src)
|
||
|
await.delay()
|
||
|
for i = 1, #src - 1 do
|
||
|
local current = src[i]
|
||
|
local next = src[i+1]
|
||
|
local left = current.range or current.finish
|
||
|
local right = next.start
|
||
|
local text = subber(current.finish, right)
|
||
|
if mode == 'All' then
|
||
|
if not text:find '[,;]' then
|
||
|
results[#results+1] = {
|
||
|
text = ';',
|
||
|
offset = left,
|
||
|
kind = define.InlayHintKind.Other,
|
||
|
where = 'right',
|
||
|
}
|
||
|
end
|
||
|
elseif mode == 'SameLine' then
|
||
|
if not text:find '[,;\r\n]' then
|
||
|
results[#results+1] = {
|
||
|
text = ';',
|
||
|
offset = left,
|
||
|
kind = define.InlayHintKind.Other,
|
||
|
where = 'right',
|
||
|
}
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
if mode == 'All' then
|
||
|
local last = src[#src]
|
||
|
results[#results+1] = {
|
||
|
text = ';',
|
||
|
offset = last.range or last.finish,
|
||
|
kind = define.InlayHintKind.Other,
|
||
|
where = 'right',
|
||
|
}
|
||
|
end
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
---@async
|
||
|
return function (uri, start, finish)
|
||
|
local results = {}
|
||
|
typeHint(uri, results, start, finish)
|
||
|
paramName(uri, results, start, finish)
|
||
|
awaitHint(uri, results, start, finish)
|
||
|
arrayIndex(uri, results, start, finish)
|
||
|
semicolonHint(uri, results, start, finish)
|
||
|
return results
|
||
|
end
|