feat(dap.hover): update buffer incrementally on expand/collapse
This commit is contained in:
+39
-21
@@ -1,4 +1,5 @@
|
|||||||
-- DAP hover implementation with tree-based display
|
-- DAP hover implementation with tree-based display
|
||||||
|
local Content = require("ow.dap.hover.content")
|
||||||
local Item = require("ow.dap.item")
|
local Item = require("ow.dap.item")
|
||||||
local Tree = require("ow.dap.hover.tree")
|
local Tree = require("ow.dap.hover.tree")
|
||||||
local log = require("ow.log")
|
local log = require("ow.log")
|
||||||
@@ -45,6 +46,7 @@ function Window.show(lines, content)
|
|||||||
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)
|
||||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
||||||
|
vim.api.nvim_set_option_value("modifiable", false, { buf = buf })
|
||||||
|
|
||||||
-- Create window (initially hidden for size calculation)
|
-- Create window (initially hidden for size calculation)
|
||||||
local win = vim.api.nvim_open_win(buf, false, {
|
local win = vim.api.nvim_open_win(buf, false, {
|
||||||
@@ -66,7 +68,7 @@ function Window.show(lines, content)
|
|||||||
})
|
})
|
||||||
|
|
||||||
-- Apply syntax highlighting
|
-- Apply syntax highlighting
|
||||||
content:apply_highlights(Window.ns_id, buf)
|
content:apply_highlights(Window.ns_id, buf, 0)
|
||||||
|
|
||||||
-- Store window reference
|
-- Store window reference
|
||||||
Window.current_win = win
|
Window.current_win = win
|
||||||
@@ -94,6 +96,17 @@ function Window.show(lines, content)
|
|||||||
end, { buffer = buf, nowait = true })
|
end, { buffer = buf, nowait = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param buf integer
|
||||||
|
---@param callback fun()
|
||||||
|
function Window.update_buffer(buf, callback)
|
||||||
|
local prev_scrolloff = vim.wo[Window.current_win].scrolloff
|
||||||
|
vim.wo[Window.current_win].scrolloff = 0
|
||||||
|
vim.bo[buf].modifiable = true
|
||||||
|
callback()
|
||||||
|
vim.bo[buf].modifiable = false
|
||||||
|
vim.wo[Window.current_win].scrolloff = prev_scrolloff
|
||||||
|
end
|
||||||
|
|
||||||
---Expand/collapse item at cursor position
|
---Expand/collapse item at cursor position
|
||||||
---@param buf integer
|
---@param buf integer
|
||||||
function Window.expand_at_cursor(buf)
|
function Window.expand_at_cursor(buf)
|
||||||
@@ -105,36 +118,40 @@ function Window.expand_at_cursor(buf)
|
|||||||
coroutine.wrap(function()
|
coroutine.wrap(function()
|
||||||
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 lnum = vim.api.nvim_win_get_cursor(Window.current_win)[1]
|
||||||
local success = Window.tree:toggle_at_line(cursor[1])
|
local node = Window.tree:get_node_at_line(lnum)
|
||||||
|
if not node or not node:is_container() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local prev_node_count = Window.tree:count_subtree_nodes(node)
|
||||||
|
|
||||||
|
local success = Window.tree:toggle_node(node)
|
||||||
if not success then
|
if not success then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local content = Window.tree:render()
|
local content = Content.new()
|
||||||
|
Window.tree:render_subtree(node, content)
|
||||||
local lines = content:get_lines()
|
local lines = content:get_lines()
|
||||||
|
|
||||||
-- TODO: possible to only update the affected lines?
|
Window.update_buffer(buf, function()
|
||||||
-- Update buffer content
|
vim.api.nvim_buf_set_lines(
|
||||||
vim.api.nvim_set_option_value(
|
buf,
|
||||||
"scrolloff",
|
lnum - 1,
|
||||||
0,
|
lnum - 1 + prev_node_count,
|
||||||
{ win = Window.current_win }
|
true,
|
||||||
)
|
lines
|
||||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
|
||||||
vim.api.nvim_set_option_value(
|
|
||||||
"scrolloff",
|
|
||||||
vim.o.scrolloff,
|
|
||||||
{ win = Window.current_win }
|
|
||||||
)
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
-- Re-apply highlights
|
-- Apply highlights
|
||||||
vim.api.nvim_buf_clear_namespace(buf, -1, 0, -1) -- Clear old highlights
|
content:apply_highlights(Window.ns_id, buf, lnum - 1)
|
||||||
content:apply_highlights(Window.ns_id, buf)
|
|
||||||
|
|
||||||
-- Adjust window size
|
-- Adjust window size
|
||||||
|
local all_lines = vim.api.nvim_buf_get_lines(buf, 0, -1, true)
|
||||||
vim.api.nvim_win_set_config(Window.current_win, {
|
vim.api.nvim_win_set_config(Window.current_win, {
|
||||||
width = Window.compute_width(lines),
|
width = Window.compute_width(all_lines),
|
||||||
})
|
})
|
||||||
local text_height =
|
local text_height =
|
||||||
vim.api.nvim_win_text_height(Window.current_win, {}).all
|
vim.api.nvim_win_text_height(Window.current_win, {}).all
|
||||||
@@ -152,7 +169,7 @@ function Window.expand_at_cursor(buf)
|
|||||||
end)()
|
end)()
|
||||||
end
|
end
|
||||||
|
|
||||||
---Main hover entry point (async)
|
---Main hover entry point
|
||||||
---@async
|
---@async
|
||||||
---@param expr string Expression to evaluate
|
---@param expr string Expression to evaluate
|
||||||
---@param session dap.Session DAP session
|
---@param session dap.Session DAP session
|
||||||
@@ -207,6 +224,7 @@ local function hover_eval(
|
|||||||
end
|
end
|
||||||
|
|
||||||
---Public hover function
|
---Public hover function
|
||||||
|
---@async
|
||||||
local function hover_async()
|
local function hover_async()
|
||||||
-- Check if hover window is already open - focus it instead
|
-- Check if hover window is already open - focus it instead
|
||||||
if Window.current_win and vim.api.nvim_win_is_valid(Window.current_win) then
|
if Window.current_win and vim.api.nvim_win_is_valid(Window.current_win) then
|
||||||
|
|||||||
@@ -125,14 +125,15 @@ 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
|
||||||
function Content:apply_highlights(ns_id, buf)
|
---@param row_offset integer
|
||||||
|
function Content:apply_highlights(ns_id, buf, row_offset)
|
||||||
for _, highlight in ipairs(self.highlights) do
|
for _, highlight in ipairs(self.highlights) do
|
||||||
vim.hl.range(
|
vim.hl.range(
|
||||||
buf,
|
buf,
|
||||||
ns_id,
|
ns_id,
|
||||||
highlight.group,
|
highlight.group,
|
||||||
{ highlight.start_row, highlight.start_col },
|
{ row_offset + highlight.start_row, highlight.start_col },
|
||||||
{ highlight.end_row, highlight.end_col }
|
{ row_offset + highlight.end_row, highlight.end_col }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+54
-26
@@ -4,9 +4,7 @@ local Node = require("ow.dap.hover.node")
|
|||||||
|
|
||||||
---@class ow.dap.hover.Tree
|
---@class ow.dap.hover.Tree
|
||||||
---@field session dap.Session
|
---@field session dap.Session
|
||||||
---@field root_node ow.dap.hover.Node?
|
---@field root 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 = {}
|
local Tree = {}
|
||||||
Tree.__index = Tree
|
Tree.__index = Tree
|
||||||
|
|
||||||
@@ -22,20 +20,19 @@ function Tree.new(session)
|
|||||||
}, Tree)
|
}, Tree)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Build the tree from a DAP item (async)
|
---Build the tree from a DAP item
|
||||||
---@async
|
---@async
|
||||||
---@param item ow.dap.Item Root item to build tree from
|
---@param item ow.dap.Item Root item to build tree from
|
||||||
---@return ow.dap.hover.Node
|
|
||||||
function Tree:build(item)
|
function Tree:build(item)
|
||||||
local root = Node.new(item, nil)
|
self.root = Node.new(item, nil)
|
||||||
self.root_node = root
|
|
||||||
|
|
||||||
-- For now, start with everything collapsed
|
if self.root:is_container() then
|
||||||
-- Later we can add logic to expand first level by default
|
self:load_children(self.root)
|
||||||
return root
|
self.root.is_expanded = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---Load children for a node (async)
|
---Load children for a node
|
||||||
---@async
|
---@async
|
||||||
---@param node ow.dap.hover.Node
|
---@param node ow.dap.hover.Node
|
||||||
---@return boolean success Whether loading succeeded
|
---@return boolean success Whether loading succeeded
|
||||||
@@ -80,14 +77,13 @@ end
|
|||||||
---@async
|
---@async
|
||||||
---@return ow.dap.hover.Content
|
---@return ow.dap.hover.Content
|
||||||
function Tree:render()
|
function Tree:render()
|
||||||
if not self.root_node then
|
if not self.root then
|
||||||
return Content.new()
|
return Content.new()
|
||||||
end
|
end
|
||||||
|
|
||||||
local content = Content.new()
|
local content = Content.new()
|
||||||
self.line_to_node = {}
|
|
||||||
|
|
||||||
self:render_subtree(self.root_node, content)
|
self:render_subtree(self.root, content)
|
||||||
return content
|
return content
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -96,9 +92,6 @@ end
|
|||||||
---@param node ow.dap.hover.Node
|
---@param node ow.dap.hover.Node
|
||||||
---@param content ow.dap.hover.Content
|
---@param content ow.dap.hover.Content
|
||||||
function Tree:render_subtree(node, content)
|
function Tree:render_subtree(node, content)
|
||||||
-- Store line mapping
|
|
||||||
self.line_to_node[content:current_line()] = node
|
|
||||||
|
|
||||||
-- Format this node
|
-- Format this node
|
||||||
node:format_into(self.session, content)
|
node:format_into(self.session, content)
|
||||||
|
|
||||||
@@ -111,18 +104,53 @@ function Tree:render_subtree(node, content)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---Toggle expansion state of node at given line
|
---@param node ow.dap.hover.Node
|
||||||
---@async
|
---@return integer
|
||||||
---@param line_number integer
|
function Tree:count_subtree_nodes(node)
|
||||||
---@return boolean success Whether toggle succeeded
|
local count = 1
|
||||||
function Tree:toggle_at_line(line_number)
|
|
||||||
local node = self.line_to_node[line_number]
|
if node.is_expanded then
|
||||||
if not node or not node:is_container() then
|
for _, child in ipairs(node.children) do
|
||||||
return false
|
count = count + self:count_subtree_nodes(child)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param target_line integer
|
||||||
|
---@return ow.dap.hover.Node?
|
||||||
|
function Tree:get_node_at_line(target_line)
|
||||||
|
local current_line = 0
|
||||||
|
|
||||||
|
---@param node ow.dap.hover.Node
|
||||||
|
local function search(node)
|
||||||
|
current_line = current_line + 1
|
||||||
|
if current_line == target_line 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
|
||||||
|
|
||||||
|
if self.root then
|
||||||
|
return search(self.root)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Toggle expansion state of node at given line
|
||||||
|
---@async
|
||||||
|
---@param node ow.dap.hover.Node
|
||||||
|
---@return boolean success Whether toggle succeeded
|
||||||
|
function Tree:toggle_node(node)
|
||||||
if not node.is_expanded then
|
if not node.is_expanded then
|
||||||
-- Expanding: load children if needed
|
|
||||||
local success = self:load_children(node)
|
local success = self:load_children(node)
|
||||||
if not success then
|
if not success then
|
||||||
return false
|
return false
|
||||||
|
|||||||
Reference in New Issue
Block a user