Files
nvim/lua/ow/dap/hover/tree.lua
T
warg acf6fffb2f refactor(dap): simplify hover tree rendering architecture
- Add row/column tracking to Content for multi-line highlight support
- Switch from Node:format() returning Content to Node:format_into(content)
- Remove complex highlight offset calculations and manual text merging
- Use content:current_line() instead of tracking line_number in nodes
- Fix pointer child formatting to use item.name directly
- Clean up unused imports and simplify render_node method
2025-09-26 19:31:51 +02:00

138 lines
3.6 KiB
Lua

-- Tree-based DAP variable formatter with expand/collapse support
local Content = require("ow.dap.hover.content")
local Node = require("ow.dap.hover.node")
---@class ow.dap.hover.Tree
---@field session dap.Session
---@field root_node ow.dap.hover.Node?
---@field line_to_node table<integer, ow.dap.hover.Node> Map line numbers to nodes
---@field extmark_to_node table<integer, ow.dap.hover.Node> Map extmark IDs to nodes
local Tree = {}
Tree.__index = Tree
---Create a new tree formatter
---@param session dap.Session
---@return ow.dap.hover.Tree
function Tree.new(session)
return setmetatable({
session = session,
root_node = nil,
line_to_node = {},
extmark_to_node = {},
}, Tree)
end
---Build the tree from a DAP item (async)
---@async
---@param item ow.dap.Item Root item to build tree from
---@return ow.dap.hover.Node
function Tree:build(item)
local root = Node.new(item, nil)
self.root_node = root
-- For now, start with everything collapsed
-- Later we can add logic to expand first level by default
return root
end
---Load children for a node (async)
---@async
---@param node ow.dap.hover.Node
---@return boolean success Whether loading succeeded
function Tree:load_children(node)
if not node:is_container() or #node.children > 0 then
return true -- Already loaded or not a container
end
local err, resp = self.session:request("variables", {
variablesReference = node.item.variablesReference,
})
if err or not resp or #resp.variables == 0 then
return false
end
-- Create child nodes
for i, var in ipairs(resp.variables) do
local child_item = {
name = var.name,
type = var.type,
value = var.value,
variablesReference = var.variablesReference,
depth = node.item.depth + 1,
}
local child = Node.new(child_item, node)
child.is_last_child = (i == #resp.variables)
-- Format array indices properly
if child_item.name:match("^%d+$") then
child_item.name = "[" .. child_item.name .. "]"
end
table.insert(node.children, child)
end
return true
end
---Render the tree to highlighted content
---@async
---@return ow.dap.hover.Content
function Tree:render()
if not self.root_node then
return Content.new()
end
local content = Content.new()
self.line_to_node = {}
self:render_subtree(self.root_node, content)
return content
end
---Render a single node and its expanded children
---@async
---@param node ow.dap.hover.Node
---@param content ow.dap.hover.Content
function Tree:render_subtree(node, content)
-- Store line mapping
self.line_to_node[content:current_line()] = node
-- Format this node
node:format_into(self.session, content)
-- Render expanded children
if node.is_expanded then
for _, child in ipairs(node.children) do
content:newline()
self:render_subtree(child, content)
end
end
end
---Toggle expansion state of node at given line
---@async
---@param line_number integer
---@return boolean success Whether toggle succeeded
function Tree:toggle_at_line(line_number)
local node = self.line_to_node[line_number]
if not node or not node:is_container() then
return false
end
if not node.is_expanded then
-- Expanding: load children if needed
local success = self:load_children(node)
if not success then
return false
end
end
-- Toggle state
node.is_expanded = not node.is_expanded
return true
end
return Tree