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
This commit is contained in:
2025-09-26 19:29:29 +02:00
parent cff07f8317
commit acf6fffb2f
4 changed files with 57 additions and 91 deletions
+3 -5
View File
@@ -38,11 +38,9 @@ function Window.compute_width(lines)
end end
---Create and display hover window with tree content ---Create and display hover window with tree content
---@param session dap.Session
---@param item ow.dap.Item
---@param lines string[] ---@param lines string[]
---@param content ow.dap.hover.Content ---@param content ow.dap.hover.Content
function Window.show(session, item, lines, content) function Window.show(lines, content)
-- Create buffer -- Create buffer
local orig_buf = vim.api.nvim_get_current_buf() local orig_buf = vim.api.nvim_get_current_buf()
local buf = vim.api.nvim_create_buf(false, true) local buf = vim.api.nvim_create_buf(false, true)
@@ -108,7 +106,7 @@ function Window.expand_at_cursor(buf)
local ok, err = xpcall(function() local ok, err = xpcall(function()
-- Toggle expansion -- Toggle expansion
local cursor = vim.api.nvim_win_get_cursor(Window.current_win) local cursor = vim.api.nvim_win_get_cursor(Window.current_win)
local success = Window.tree:toggle_at_line(cursor[1] - 1) local success = Window.tree:toggle_at_line(cursor[1])
if not success then if not success then
return return
end end
@@ -205,7 +203,7 @@ local function hover_eval(
Window.tree = tree Window.tree = tree
-- Show hover window -- Show hover window
Window.show(session, item, lines, content) Window.show(lines, content)
end end
---Public hover function ---Public hover function
+36 -42
View File
@@ -1,5 +1,4 @@
-- Highlighted text content builder for DAP tree display -- Highlighted text content builder for DAP tree display
local log = require("ow.log")
---@class ow.dap.hover.content.Capture ---@class ow.dap.hover.content.Capture
---@field start_col integer ---@field start_col integer
---@field end_col integer ---@field end_col integer
@@ -9,12 +8,15 @@ local log = require("ow.log")
---@class ow.dap.hover.Highlight ---@class ow.dap.hover.Highlight
---@field group string Highlight group name ---@field group string Highlight group name
---@field start_row integer Start line (0-indexed)
---@field start_col integer Start column (0-indexed) ---@field start_col integer Start column (0-indexed)
---@field end_row integer End line (0-indexed)
---@field end_col integer End column (0-indexed) ---@field end_col integer End column (0-indexed)
---@class ow.dap.hover.Content ---@class ow.dap.hover.Content
---@field text string The complete text content ---@field text string The complete text content
---@field highlights ow.dap.hover.Highlight[] List of highlights to apply ---@field highlights ow.dap.hover.Highlight[] List of highlights to apply
---@field _current_row integer Current line position (for building)
---@field _current_col integer Current column position (for building) ---@field _current_col integer Current column position (for building)
local Content = {} local Content = {}
Content.__index = Content Content.__index = Content
@@ -25,15 +27,23 @@ function Content.new()
return setmetatable({ return setmetatable({
text = "", text = "",
highlights = {}, highlights = {},
_current_row = 0,
_current_col = 0, _current_col = 0,
}, Content) }, Content)
end end
---@return integer
function Content:current_line()
return self._current_row + 1
end
---Add text with optional highlighting ---Add text with optional highlighting
---@param text string Text to add ---@param text string Text to add. May not contain line breaks.
---@param highlight_group? string Optional highlight group ---@param highlight_group? string Optional highlight group
function Content:add(text, highlight_group) function Content:add(text, highlight_group)
local start_row = self._current_row
local start_col = self._current_col local start_col = self._current_col
local end_row = self._current_row
local end_col = start_col + #text local end_col = start_col + #text
self.text = self.text .. text self.text = self.text .. text
@@ -42,16 +52,19 @@ function Content:add(text, highlight_group)
if highlight_group then if highlight_group then
table.insert(self.highlights, { table.insert(self.highlights, {
group = highlight_group, group = highlight_group,
start_row = start_row,
start_col = start_col, start_col = start_col,
end_row = end_row,
end_col = end_col, end_col = end_col,
}) })
end end
end end
---Add text with tree-sitter syntax highlighting ---Add text with tree-sitter syntax highlighting
---@param text string The text to highlight ---@param text string Text to add. May not contain line breaks.
---@param lang string Language for tree-sitter ---@param lang string Language for tree-sitter highlighting
function Content:add_with_treesitter(text, lang) function Content:add_with_treesitter(text, lang)
local start_row = self._current_row
local start_col = self._current_col local start_col = self._current_col
-- First, just add the text normally -- First, just add the text normally
@@ -76,22 +89,23 @@ function Content:add_with_treesitter(text, lang)
-- Add highlights for all captures (overlapping is fine) -- Add highlights for all captures (overlapping is fine)
for id, node in query:iter_captures(tree:root(), text, 0, -1) do for id, node in query:iter_captures(tree:root(), text, 0, -1) do
local capture_name = query.captures[id] local capture_name = query.captures[id]
local start_row, start_col_rel, end_row, end_col_rel = node:range() local start_row_rel, start_col_rel, end_row_rel, end_col_rel =
node:range()
-- TODO: keep track of text as lines instead, so we can handle multiline -- Convert to absolute positions
-- highlights local abs_start_row = start_row + start_row_rel
if start_row == end_row then -- Single line only local abs_end_row = start_row + end_row_rel
-- Convert to absolute column positions local abs_start_col = start_col + start_col_rel
local abs_start_col = start_col + start_col_rel local abs_end_col = start_col + end_col_rel
local abs_end_col = start_col + end_col_rel
-- Add the highlight -- Add the highlight
table.insert(self.highlights, { table.insert(self.highlights, {
group = "@" .. capture_name, group = "@" .. capture_name,
start_col = abs_start_col, start_row = abs_start_row,
end_col = abs_end_col, start_col = abs_start_col,
}) end_row = abs_end_row,
end end_col = abs_end_col,
})
end end
end end
@@ -99,6 +113,7 @@ end
function Content:newline() function Content:newline()
self:add("\n") self:add("\n")
self._current_col = 0 self._current_col = 0
self._current_row = self._current_row + 1
end end
---Get the lines as a table ---Get the lines as a table
@@ -110,35 +125,14 @@ end
---Apply highlights to a buffer ---Apply highlights to a buffer
---@param ns_id integer ---@param ns_id integer
---@param buf integer Buffer handle ---@param buf integer Buffer handle
---@param line_offset? integer Line offset to apply highlights at (default 0) function Content:apply_highlights(ns_id, buf)
function Content:apply_highlights(ns_id, buf, line_offset)
line_offset = line_offset or 0
local lines = self:get_lines()
local current_line = 0
local line_start_col = 0
for _, highlight in ipairs(self.highlights) do for _, highlight in ipairs(self.highlights) do
-- Find which line this highlight belongs to
while
current_line < #lines - 1
and highlight.start_col
>= line_start_col + #lines[current_line + 1] + 1
do
line_start_col = line_start_col + #lines[current_line + 1] + 1 -- +1 for newline
current_line = current_line + 1
end
-- Calculate column positions relative to line start
local start_col = highlight.start_col - line_start_col
local end_col = highlight.end_col - line_start_col
-- Apply highlight
vim.hl.range( vim.hl.range(
buf, buf,
ns_id, ns_id,
highlight.group, highlight.group,
{ line_offset + current_line, start_col }, { highlight.start_row, highlight.start_col },
{ line_offset + current_line, end_col } { highlight.end_row, highlight.end_col }
) )
end end
end end
+12 -19
View File
@@ -1,13 +1,10 @@
-- Tree node representation for DAP variables -- Tree node representation for DAP variables
local Content = require("ow.dap.hover.content")
local log = require("ow.log")
---@class ow.dap.hover.Node ---@class ow.dap.hover.Node
---@field item ow.dap.Item The DAP item this node represents ---@field item ow.dap.Item The DAP item this node represents
---@field parent ow.dap.hover.Node? Parent node ---@field parent ow.dap.hover.Node? Parent node
---@field children ow.dap.hover.Node[] Child nodes ---@field children ow.dap.hover.Node[] Child nodes
---@field is_expanded boolean Whether this node is expanded ---@field is_expanded boolean Whether this node is expanded
---@field line_number integer Buffer line number where this node is displayed
---@field is_last_child boolean Whether this is the last child of its parent ---@field is_last_child boolean Whether this is the last child of its parent
local Node = {} local Node = {}
Node.__index = Node Node.__index = Node
@@ -17,14 +14,15 @@ Node.__index = Node
---@param parent ow.dap.hover.Node? ---@param parent ow.dap.hover.Node?
---@return ow.dap.hover.Node ---@return ow.dap.hover.Node
function Node.new(item, parent) function Node.new(item, parent)
return setmetatable({ local node = setmetatable({
item = item, item = item,
parent = parent, parent = parent,
children = {}, children = {},
is_expanded = false, is_expanded = false,
line_number = -1,
is_last_child = false, is_last_child = false,
}, Node) }, Node)
return node
end end
---Check if this node represents a container (struct/array) ---Check if this node represents a container (struct/array)
@@ -79,7 +77,7 @@ function Node:is_c_pointer_child()
end end
---@return string ---@return string
function Node:format_c_expression() function Node:format_c()
if self:is_c_array_element() then if self:is_c_array_element() then
return string.format( return string.format(
"%s = (%s) %s", "%s = (%s) %s",
@@ -90,7 +88,7 @@ function Node:format_c_expression()
end end
if self:is_c_pointer_child() then if self:is_c_pointer_child() then
return string.format("*%s = %s", self.parent.item.name, self.item.value) return string.format("%s = %s", self.item.name, self.item.value)
end end
return string.format( return string.format(
@@ -101,13 +99,10 @@ function Node:format_c_expression()
) )
end end
---Format this node as highlighted content ---Format this node into the provided content
---@param session dap.Session DAP session for making requests ---@param session dap.Session DAP session for making requests
---@return ow.dap.hover.Content ---@param content ow.dap.hover.Content
function Node:format(session) function Node:format_into(session, content)
local content = Content.new()
-- Add expansion marker for containers
if self:is_container() then if self:is_container() then
local marker = self.is_expanded and "-" or "+" local marker = self.is_expanded and "-" or "+"
content:add(marker .. " ", "@comment") content:add(marker .. " ", "@comment")
@@ -115,27 +110,25 @@ function Node:format(session)
content:add(" ") content:add(" ")
end end
-- Add tree prefix
local tree_prefix = self:get_tree_prefix() local tree_prefix = self:get_tree_prefix()
if tree_prefix ~= "" then if tree_prefix ~= "" then
content:add(tree_prefix, "@comment") content:add(tree_prefix, "@comment")
end end
local stmt local text
if session.filetype == "c" or session.filetype == "cpp" then if session.filetype == "c" or session.filetype == "cpp" then
stmt = self:format_c_expression() text = self:format_c()
else else
error( error(
string.format("Formatting for %s not implemented", session.filetype) string.format("Formatting for %s not implemented", session.filetype)
) )
end end
content:add_with_treesitter(stmt, session.filetype)
content:add_with_treesitter(text, session.filetype)
if self.item.value == "" then if self.item.value == "" then
content:add("...", "@comment") content:add("...", "@comment")
end end
return content
end end
return Node return Node
+6 -25
View File
@@ -1,7 +1,6 @@
-- Tree-based DAP variable formatter with expand/collapse support -- Tree-based DAP variable formatter with expand/collapse support
local Content = require("ow.dap.hover.content") local Content = require("ow.dap.hover.content")
local Node = require("ow.dap.hover.node") local Node = require("ow.dap.hover.node")
local log = require("ow.log")
---@class ow.dap.hover.Tree ---@class ow.dap.hover.Tree
---@field session dap.Session ---@field session dap.Session
@@ -88,7 +87,7 @@ function Tree:render()
local content = Content.new() local content = Content.new()
self.line_to_node = {} self.line_to_node = {}
self:render_node(self.root_node, content, 0) self:render_subtree(self.root_node, content)
return content return content
end end
@@ -96,38 +95,20 @@ end
---@async ---@async
---@param node ow.dap.hover.Node ---@param node ow.dap.hover.Node
---@param content ow.dap.hover.Content ---@param content ow.dap.hover.Content
---@param line_number integer Current line number function Tree:render_subtree(node, content)
---@return integer new_line_number Updated line number after rendering
function Tree:render_node(node, content, line_number)
-- Store line mapping -- Store line mapping
node.line_number = line_number self.line_to_node[content:current_line()] = node
self.line_to_node[line_number] = node
-- Format this node -- Format this node
local node_content = node:format(self.session) node:format_into(self.session, content)
content.text = content.text .. node_content.text
-- Copy highlights, adjusting for current position
local text_offset = #content.text - #node_content.text
for _, highlight in ipairs(node_content.highlights) do
table.insert(content.highlights, {
group = highlight.group,
start_col = highlight.start_col + text_offset,
end_col = highlight.end_col + text_offset,
})
end
content:newline()
line_number = line_number + 1
-- Render expanded children -- Render expanded children
if node.is_expanded then if node.is_expanded then
for _, child in ipairs(node.children) do for _, child in ipairs(node.children) do
line_number = self:render_node(child, content, line_number) content:newline()
self:render_subtree(child, content)
end end
end end
return line_number
end end
---Toggle expansion state of node at given line ---Toggle expansion state of node at given line