-- 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 Map line numbers to nodes ---@field extmark_to_node table 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