Files
nvim/lua/dap/hover/node.lua
T

282 lines
6.3 KiB
Lua

local Item = require("dap.item")
local log = require("log")
---@class ow.dap.hover.Node
---@field lang string
---@field item ow.dap.Item
---@field parent ow.dap.hover.Node?
---@field children ow.dap.hover.Node[]
---@field is_expanded boolean
---@field is_last_child boolean
local Node = {}
Node.__index = Node
---@param item ow.dap.Item
---@param parent ow.dap.hover.Node?
---@param lang string
---@return ow.dap.hover.Node
function Node.new(item, parent, lang)
return setmetatable({
lang = lang,
item = item,
parent = parent,
children = {},
is_expanded = false,
is_last_child = false,
}, Node)
end
---@return integer
function Node:size()
local count = 1
if self.is_expanded and self.children then
for _, child in ipairs(self.children) do
count = count + child:size()
end
end
return count
end
---@param n integer
---@return ow.dap.hover.Node?
function Node:at(n)
if n < 1 then
return nil
end
local current = 0
---@param node ow.dap.hover.Node
local function search(node)
current = current + 1
if current == n then
return node
end
if node.is_expanded and node.children then
for _, child in ipairs(node.children) do
local found = search(child)
if found then
return found
end
end
end
end
return search(self)
end
---@param target ow.dap.hover.Node? if nil, returns index of self
---@return integer?
function Node:index_of(target)
target = target or self
local current = 0
local function search(node)
current = current + 1
if node == target then
return current
end
if node.is_expanded and node.children then
for _, child in ipairs(node.children) do
local found = search(child)
if found then
return found
end
end
end
end
return search(self)
end
---@param session dap.Session
function Node:expand_all(session)
if not self:is_expandable() then
return true
end
if not self.is_expanded then
local success = self:load_children(session)
if not success then
return false
end
self.is_expanded = true
end
for _, child in ipairs(self.children) do
local success = child:expand_all(session)
if not success then
return false
end
end
return true
end
function Node:collapse_all()
self.is_expanded = false
for _, child in ipairs(self.children) do
child:collapse_all()
end
end
---@return boolean
function Node:is_container()
return self.item.variablesReference and self.item.variablesReference > 0
or false
end
---@return boolean
function Node:is_c_lang()
return self.lang == "c" or self.lang == "cpp"
end
---@return boolean
function Node:is_c_pointer()
return self:is_c_lang()
and self:is_container()
and self.item.type:match(
"%*%s*[const%s]*[volatile%s]*[restrict%s]*$"
)
~= nil
end
---@return boolean
function Node:is_c_null_pointer()
return self:is_c_pointer() and self.item.value:match("^0[xX]0*$") ~= nil
end
---@return string
function Node:get_tree_prefix()
if not self.parent then
return ""
end
local prefix = ""
local node = self.parent
while node and node.parent do
if node.is_last_child then
prefix = " " .. prefix
else
prefix = "" .. prefix
end
node = node.parent
end
if self.is_last_child then
prefix = prefix .. "└─ "
else
prefix = prefix .. "├─ "
end
return prefix
end
---@return boolean
function Node:is_c_array_element()
return self:is_c_lang() and self.item.name:match("^%[?%d+%]?$") ~= nil
end
---@return string
function Node:format_c()
if self:is_c_array_element() then
return string.format(
"%s = (%s) %s",
self.item.name,
self.item.type,
self.item.value
)
end
if self.item.value == "" then
return string.format("%s %s", self.item.type, self.item.name)
else
return string.format(
"%s %s = %s",
self.item.type,
self.item.name,
self.item.value
)
end
end
---@return boolean
function Node:is_expandable()
return self:is_container() and not self:is_c_null_pointer()
end
---@param content ow.dap.hover.Content
function Node:format_into(content)
local tree_prefix = self:get_tree_prefix()
if tree_prefix ~= "" then
content:add(tree_prefix, "DapHoverPrefix")
end
local text
if self:is_c_lang() then
text = self:format_c()
elseif self.lang == "python" then
text = string.format(
"%s: %s = %s",
self.item.name,
self.item.type,
self.item.value
)
else
error(string.format("Formatting for %s not implemented", self.lang))
end
content:add_with_treesitter(text, self.lang)
if self:is_expandable() then
if self.is_expanded then
for _, child in ipairs(self.children) do
content:newline()
child:format_into(content)
end
else
content:add(" ...", "DapHoverExpandMarker")
end
end
end
---@async
---@param session dap.Session
---@return boolean
function Node:load_children(session)
if not self:is_container() or #self.children > 0 then
return true -- Already loaded or not a container
end
local err, resp = session:request("variables", {
variablesReference = self.item.variablesReference,
})
if err then
log.warning("Failed to get variables for %s: %s", self.item.name, err)
end
if err or not resp or #resp.variables == 0 then
return false
end
for i, var in ipairs(resp.variables) do
local item = Item.from_var(var)
local child = Node.new(item, self, self.lang)
child.is_last_child = (i == #resp.variables)
if item.name:match("^%d+$") then
item.name = "[" .. item.name .. "]"
end
table.insert(self.children, child)
end
return true
end
return Node