369 lines
11 KiB
Lua
369 lines
11 KiB
Lua
|
local files = require 'files'
|
||
|
local vm = require 'vm'
|
||
|
local define = require 'proto.define'
|
||
|
local findSource = require 'core.find-source'
|
||
|
local util = require 'utility'
|
||
|
local guide = require 'parser.guide'
|
||
|
|
||
|
---@async
|
||
|
local function eachRef(source, callback)
|
||
|
local refs = vm.getRefs(source, function (_)
|
||
|
return false
|
||
|
end)
|
||
|
for _, ref in ipairs(refs) do
|
||
|
callback(ref)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function eachLocal(source, callback)
|
||
|
callback(source)
|
||
|
if source.ref then
|
||
|
for _, ref in ipairs(source.ref) do
|
||
|
callback(ref)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
---@async
|
||
|
local function find(source, uri, callback)
|
||
|
if source.type == 'local' then
|
||
|
eachLocal(source, callback)
|
||
|
elseif source.type == 'getlocal'
|
||
|
or source.type == 'setlocal' then
|
||
|
eachLocal(source.node, callback)
|
||
|
elseif source.type == 'field'
|
||
|
or source.type == 'method' then
|
||
|
eachRef(source.parent, callback)
|
||
|
elseif source.type == 'getindex'
|
||
|
or source.type == 'setindex'
|
||
|
or source.type == 'tableindex' then
|
||
|
eachRef(source, callback)
|
||
|
elseif source.type == 'setglobal'
|
||
|
or source.type == 'getglobal' then
|
||
|
eachRef(source, callback)
|
||
|
elseif source.type == 'goto'
|
||
|
or source.type == 'label' then
|
||
|
eachRef(source, callback)
|
||
|
elseif source.type == 'string'
|
||
|
and source.parent
|
||
|
and source.parent.index == source then
|
||
|
eachRef(source.parent, callback)
|
||
|
elseif source.type == 'string'
|
||
|
or source.type == 'boolean'
|
||
|
or source.type == 'number'
|
||
|
or source.type == 'integer'
|
||
|
or source.type == 'nil' then
|
||
|
callback(source)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function checkInIf(state, source, text, position)
|
||
|
-- 检查 end
|
||
|
local endB = guide.positionToOffset(state, source.finish)
|
||
|
local endA = endB - #'end' + 1
|
||
|
if position >= source.finish - #'end'
|
||
|
and position <= source.finish
|
||
|
and text:sub(endA, endB) == 'end' then
|
||
|
return true
|
||
|
end
|
||
|
-- 检查每个子模块
|
||
|
for _, block in ipairs(source) do
|
||
|
for i = 1, #block.keyword, 2 do
|
||
|
local start = block.keyword[i]
|
||
|
local finish = block.keyword[i+1]
|
||
|
if position >= start and position <= finish then
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
local function makeIf(state, source, text, callback)
|
||
|
-- end
|
||
|
local endB = guide.positionToOffset(state, source.finish)
|
||
|
local endA = endB - #'end' + 1
|
||
|
if text:sub(endA, endB) == 'end' then
|
||
|
callback(source.finish - #'end', source.finish)
|
||
|
end
|
||
|
-- 每个子模块
|
||
|
for _, block in ipairs(source) do
|
||
|
for i = 1, #block.keyword, 2 do
|
||
|
local start = block.keyword[i]
|
||
|
local finish = block.keyword[i+1]
|
||
|
callback(start, finish)
|
||
|
end
|
||
|
end
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
local function findKeyWord(state, text, position, callback)
|
||
|
guide.eachSourceContain(state.ast, position, function (source)
|
||
|
if source.type == 'do'
|
||
|
or source.type == 'function'
|
||
|
or source.type == 'loop'
|
||
|
or source.type == 'in'
|
||
|
or source.type == 'while'
|
||
|
or source.type == 'repeat' then
|
||
|
local ok
|
||
|
for i = 1, #source.keyword, 2 do
|
||
|
local start = source.keyword[i]
|
||
|
local finish = source.keyword[i+1]
|
||
|
if position >= start and position <= finish then
|
||
|
ok = true
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
if ok then
|
||
|
for i = 1, #source.keyword, 2 do
|
||
|
local start = source.keyword[i]
|
||
|
local finish = source.keyword[i+1]
|
||
|
callback(start, finish)
|
||
|
end
|
||
|
end
|
||
|
elseif source.type == 'if' then
|
||
|
local ok = checkInIf(state, source, text, position)
|
||
|
if ok then
|
||
|
makeIf(state, source, text, callback)
|
||
|
end
|
||
|
end
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
local function isRegion(str)
|
||
|
if str:sub(1, #'region') == 'region'
|
||
|
or str:sub(1, #'#region') == '#region' then
|
||
|
return true
|
||
|
end
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
local function isEndRegion(str)
|
||
|
if str:sub(1, #'endregion') == 'endregion'
|
||
|
or str:sub(1, #'#endregion') == '#endregion' then
|
||
|
return true
|
||
|
end
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
local function checkRegion(ast, text, offset, callback)
|
||
|
local count
|
||
|
local start, finish
|
||
|
local selected
|
||
|
for i, comment in ipairs(ast.comms) do
|
||
|
if comment.type == 'comment.short' then
|
||
|
if comment.start <= offset
|
||
|
and comment.finish >= offset then
|
||
|
local ltext = comment.text:lower()
|
||
|
ltext = util.trim(ltext, 'left')
|
||
|
if isRegion(ltext) then
|
||
|
start = comment.start - 2
|
||
|
count = 1
|
||
|
selected = i
|
||
|
elseif isEndRegion(ltext) then
|
||
|
finish = comment.finish
|
||
|
count = 1
|
||
|
selected = i
|
||
|
else
|
||
|
return
|
||
|
end
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
if not selected then
|
||
|
return
|
||
|
end
|
||
|
if start then
|
||
|
for i = selected + 1, #ast.comms do
|
||
|
local comment = ast.comms[i]
|
||
|
if comment.type == 'comment.short' then
|
||
|
local ltext = comment.text:lower()
|
||
|
ltext = util.trim(ltext, 'left')
|
||
|
if isRegion(ltext) then
|
||
|
count = count + 1
|
||
|
elseif isEndRegion(ltext) then
|
||
|
count = count - 1
|
||
|
if count == 0 then
|
||
|
callback(start, comment.finish)
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
if finish then
|
||
|
for i = selected - 1, 1, -1 do
|
||
|
local comment = ast.comms[i]
|
||
|
if comment.type == 'comment.short' then
|
||
|
local ltext = comment.text:lower()
|
||
|
ltext = util.trim(ltext, 'left')
|
||
|
if isEndRegion(ltext) then
|
||
|
count = count + 1
|
||
|
elseif isRegion(ltext) then
|
||
|
count = count - 1
|
||
|
if count == 0 then
|
||
|
callback(comment.start - 2, finish)
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local accept = {
|
||
|
['label'] = true,
|
||
|
['goto'] = true,
|
||
|
['local'] = true,
|
||
|
['setlocal'] = true,
|
||
|
['getlocal'] = true,
|
||
|
['field'] = true,
|
||
|
['method'] = true,
|
||
|
['tablefield'] = true,
|
||
|
['setglobal'] = true,
|
||
|
['getglobal'] = true,
|
||
|
['string'] = true,
|
||
|
['boolean'] = true,
|
||
|
['number'] = true,
|
||
|
['integer'] = true,
|
||
|
['nil'] = true,
|
||
|
}
|
||
|
|
||
|
local function isLiteralValue(source)
|
||
|
if not guide.isLiteral(source) then
|
||
|
return false
|
||
|
end
|
||
|
if source.parent and source.parent.index == source then
|
||
|
return false
|
||
|
end
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
---@async
|
||
|
return function (uri, offset)
|
||
|
local state = files.getState(uri)
|
||
|
if not state then
|
||
|
return nil
|
||
|
end
|
||
|
local text = files.getText(uri)
|
||
|
local results = {}
|
||
|
local mark = {}
|
||
|
|
||
|
local source = findSource(state, offset, accept)
|
||
|
if source then
|
||
|
local isGlobal = guide.isGlobal(source)
|
||
|
local isLiteral = isLiteralValue(source)
|
||
|
find(source, uri, function (target)
|
||
|
if not target then
|
||
|
return
|
||
|
end
|
||
|
if mark[target] then
|
||
|
return
|
||
|
end
|
||
|
mark[target] = true
|
||
|
if isGlobal ~= guide.isGlobal(target) then
|
||
|
return
|
||
|
end
|
||
|
if isLiteral ~= isLiteralValue(target) then
|
||
|
return
|
||
|
end
|
||
|
if uri ~= guide.getUri(target) then
|
||
|
return
|
||
|
end
|
||
|
local kind
|
||
|
if target.type == 'getfield' then
|
||
|
target = target.field
|
||
|
kind = define.DocumentHighlightKind.Read
|
||
|
elseif target.type == 'setfield'
|
||
|
or target.type == 'tablefield' then
|
||
|
target = target.field
|
||
|
kind = define.DocumentHighlightKind.Write
|
||
|
elseif target.type == 'getmethod' then
|
||
|
target = target.method
|
||
|
kind = define.DocumentHighlightKind.Read
|
||
|
elseif target.type == 'setmethod' then
|
||
|
target = target.method
|
||
|
kind = define.DocumentHighlightKind.Write
|
||
|
elseif target.type == 'getindex' then
|
||
|
target = target.index
|
||
|
kind = define.DocumentHighlightKind.Read
|
||
|
elseif target.type == 'field' then
|
||
|
if target.parent.type == 'getfield' then
|
||
|
kind = define.DocumentHighlightKind.Read
|
||
|
else
|
||
|
kind = define.DocumentHighlightKind.Write
|
||
|
end
|
||
|
elseif target.type == 'method' then
|
||
|
if target.parent.type == 'getmethod' then
|
||
|
kind = define.DocumentHighlightKind.Read
|
||
|
else
|
||
|
kind = define.DocumentHighlightKind.Write
|
||
|
end
|
||
|
elseif target.type == 'index' then
|
||
|
if target.parent.type == 'getindex' then
|
||
|
kind = define.DocumentHighlightKind.Read
|
||
|
else
|
||
|
kind = define.DocumentHighlightKind.Write
|
||
|
end
|
||
|
elseif target.type == 'index' then
|
||
|
if target.parent.type == 'getindex' then
|
||
|
kind = define.DocumentHighlightKind.Read
|
||
|
else
|
||
|
kind = define.DocumentHighlightKind.Write
|
||
|
end
|
||
|
elseif target.type == 'setindex'
|
||
|
or target.type == 'tableindex' then
|
||
|
target = target.index
|
||
|
kind = define.DocumentHighlightKind.Write
|
||
|
elseif target.type == 'getlocal'
|
||
|
or target.type == 'getglobal'
|
||
|
or target.type == 'goto' then
|
||
|
kind = define.DocumentHighlightKind.Read
|
||
|
elseif target.type == 'setlocal'
|
||
|
or target.type == 'local'
|
||
|
or target.type == 'setglobal'
|
||
|
or target.type == 'label' then
|
||
|
kind = define.DocumentHighlightKind.Write
|
||
|
elseif target.type == 'string'
|
||
|
or target.type == 'boolean'
|
||
|
or target.type == 'number'
|
||
|
or target.type == 'integer'
|
||
|
or target.type == 'nil' then
|
||
|
kind = define.DocumentHighlightKind.Text
|
||
|
else
|
||
|
return
|
||
|
end
|
||
|
if not target then
|
||
|
return
|
||
|
end
|
||
|
results[#results+1] = {
|
||
|
start = target.start,
|
||
|
finish = target.finish,
|
||
|
kind = kind,
|
||
|
}
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
findKeyWord(state, text, offset, function (start, finish)
|
||
|
results[#results+1] = {
|
||
|
start = start,
|
||
|
finish = finish,
|
||
|
kind = define.DocumentHighlightKind.Text
|
||
|
}
|
||
|
end)
|
||
|
|
||
|
checkRegion(state, text, offset, function (start, finish)
|
||
|
results[#results+1] = {
|
||
|
start = start,
|
||
|
finish = finish,
|
||
|
kind = define.DocumentHighlightKind.Text
|
||
|
}
|
||
|
end)
|
||
|
|
||
|
if #results == 0 then
|
||
|
return nil
|
||
|
end
|
||
|
return results
|
||
|
end
|