Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 45 additions & 10 deletions lua/dap-view/console/scroll.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,49 @@ local M = {}

local api = vim.api

local autoscroll = true
---@type table<integer,boolean>
local termbuf_is_autoscrolling = {}

-- Inspired by nvim-dap-ui
-- https://github.com/rcarriga/nvim-dap-ui/blob/73a26abf4941aa27da59820fd6b028ebcdbcf932/lua/dapui/elements/console.lua#L23-L46

---@param bufnr integer
M.scroll = function(bufnr)
M.setup_autoscroll = function(bufnr)
termbuf_is_autoscrolling[bufnr] = true

api.nvim_create_autocmd({ "InsertEnter", "CursorMoved" }, {
buffer = bufnr,
group = api.nvim_create_augroup("nvim-dap-view-scroll-" .. bufnr, {}),
callback = function()
local winnr = vim.tbl_contains(setup.config.winbar.sections, "console") and state.winnr or state.term_winnr
if util.is_win_valid(winnr) then
---@cast winnr integer
local cursor = api.nvim_win_get_cursor(winnr)
autoscroll = cursor[1] == api.nvim_buf_line_count(bufnr)

if not util.is_win_valid(winnr) then
return
end

---@cast winnr integer
local cursor = api.nvim_win_get_cursor(winnr)

termbuf_is_autoscrolling[bufnr] = cursor[1] == api.nvim_buf_line_count(bufnr)
end,
})

api.nvim_buf_attach(bufnr, false, {
on_lines = function()
local winnr = vim.tbl_contains(setup.config.winbar.sections, "console") and state.winnr or state.term_winnr

if not util.is_win_valid(winnr) then
return
end

---@cast winnr integer
if autoscroll and vim.fn.mode() == "n" then
if M.is_autoscrolling(bufnr) and vim.fn.mode() == "n" then
api.nvim_win_call(winnr, function()
if api.nvim_get_current_buf() == bufnr then
-- vim.schedule ensures the cursor movement happens in the main event loop
-- otherwise the call may happen too early
-- Ensure the cursor movement happens in the main event loop
-- Otherwise the call may happen too early
vim.schedule(function()
api.nvim_win_set_cursor(winnr, { api.nvim_buf_line_count(bufnr), 0 })
M.scroll_to_bottom(winnr, bufnr)
end)
end
end)
Expand All @@ -47,4 +57,29 @@ M.scroll = function(bufnr)
})
end

---@param bufnr integer
---@return boolean?
M.is_autoscrolling = function(bufnr)
return termbuf_is_autoscrolling[bufnr]
end

---@param winnr integer
---@param bufnr integer
M.scroll_to_bottom = function(winnr, bufnr)
if util.is_win_valid(winnr) and util.is_buf_valid(bufnr) then
api.nvim_win_set_cursor(winnr, { api.nvim_buf_line_count(bufnr), 0 })

termbuf_is_autoscrolling[bufnr] = true
end
end

---@param bufnr integer
M.cleanup_autoscroll = function(bufnr)
if termbuf_is_autoscrolling[bufnr] then
api.nvim_del_augroup_by_name("nvim-dap-view-scroll-" .. bufnr)
end

termbuf_is_autoscrolling[bufnr] = nil
end

return M
20 changes: 19 additions & 1 deletion lua/dap-view/console/view.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ local winbar = require("dap-view.options.winbar")
local setup = require("dap-view.setup")
local views = require("dap-view.views")
local util = require("dap-view.util")
local scroll = require("dap-view.console.scroll")

local M = {}

Expand Down Expand Up @@ -57,12 +58,28 @@ M.show = function()

---`cleanup_view` ensures the buffer exists
---@cast term_buf integer

local is_autoscrolling
local winnr = scroll.get_winnr()

-- get the current autoscrolling state before showing section
-- should we maybe even get it before the main window opens to ensure no data race? hmm...
if winnr ~= nil then
is_autoscrolling = scroll.is_autoscrolling(term_buf)
end

api.nvim_win_call(state.winnr, function()
vim.wo[state.winnr][0].winfixbuf = false
api.nvim_set_current_buf(term_buf)
vim.wo[state.winnr][0].winfixbuf = true
end)

-- When showing a session for the first time, assume the user wants autoscroll to just work™
-- That's necessary because when a terminal buffer is created, the number of lines is assigned to the number of
-- lines in the window. This may lead to autoscroll being set to false, since the user may not be "at the bottom".
if is_autoscrolling then
scroll.scroll_to_bottom(state.winnr, term_buf)
end
end

---Hide the term win, does not affect the term buffer
Expand Down Expand Up @@ -139,7 +156,8 @@ M.setup_term_buf = function()
end

require("dap-view.console.keymaps").set_keymaps(term_bufnr)
require("dap-view.console.scroll").scroll(term_bufnr)

scroll.setup_autoscroll(term_bufnr)
end

M.switch_term_buf = function()
Expand Down
23 changes: 20 additions & 3 deletions lua/dap-view/listeners.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ local setup = require("dap-view.setup")
local refresher = require("dap-view.refresher")
local winbar = require("dap-view.options.winbar")
local traversal = require("dap-view.tree.traversal")
local scroll = require("dap-view.console.scroll")
local adapter_ = require("dap-view.util.adapter")

local SUBSCRIPTION_ID = "dap-view"
Expand All @@ -35,8 +36,20 @@ dap.listeners.on_session[SUBSCRIPTION_ID] = function(_, new)
-- At this stage, the session is not fully initialized yet
require("dap-view.exceptions").update_exception_breakpoints_filters()

if new.initialized and new.stopped_thread_id then
eval.reevaluate_all_expressions()
if new.initialized then
-- Handle autoscrolling when switching sessions
-- Straightforward way of taking into account "console" not being set
if util.is_buf_valid(new.term_buf) and scroll.is_autoscrolling(new.term_buf) then
local winnr = vim.tbl_contains(setup.config.winbar.sections, "console") and state.winnr
or state.term_winnr
if util.is_win_valid(winnr) then
---@cast winnr integer
scroll.scroll_to_bottom(winnr, new.term_buf)
end
end
if new.stopped_thread_id then
eval.reevaluate_all_expressions()
end
end
else
state.current_session_id = nil
Expand Down Expand Up @@ -183,13 +196,17 @@ dap.listeners.after.event_terminated[SUBSCRIPTION_ID] = function(session)
end

winbar.redraw_controls()

scroll.cleanup_autoscroll(session.term_buf)
end

-- The debuggee was disconnected, which may happen outside of a "regular termination"
dap.listeners.after.disconnect[SUBSCRIPTION_ID] = function()
dap.listeners.after.disconnect[SUBSCRIPTION_ID] = function(session)
refresher.refresh_session_based_views()

winbar.redraw_controls()

scroll.cleanup_autoscroll(session.term_buf)
end

local winbar_redraw = { "event_exited", "event_stopped", "restart" }
Expand Down