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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ All of that in a unified, unintrusive window.

## Getting Started

Start a regular debugging session. When desired, you can use `:DapViewOpen` to start the plugin. You can switch to another section using the letter outlined in the `'winbar'` (e.g., `B` for "Breakpoints"). Explore what you can do each section by using `g?` to inspect the keymaps.
Start a regular debugging session. When desired, you can use `:DapViewOpen` to start the plugin. You can switch to another section using the letter outlined in the `'winbar'` (e.g., `B` for "Breakpoints"). Explore what you can do in each section by using `g?` to inspect the keymaps.

Once you're done debugging, you can close the plugin with `:DapViewClose` and then terminate your session as usual.

Expand Down
4 changes: 2 additions & 2 deletions docs/src/routes/api/+page.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ Shows a given view. If the specified view is already the current one, jumps to i
## Navigate

```lua
---@param opts {count: number, wrap: boolean}
---@param opts {count: number, wrap: boolean, type?: 'views' | 'sessions'}
require("dap-view").navigate(opts)
```

Switches from the current view to another one by taking the current view's index (in the winbar) and adding a count. Has some default [keymaps](keymaps).
Switches from the current view to another one by taking the current view's index (in the winbar) and adding a count (default behavior). Can also be used to navigate within sessions, if specified. Has some default [keymaps](keymaps).

[^1]: In the current tab. May close the views window in another tab.
2 changes: 1 addition & 1 deletion docs/src/routes/configuration/+page.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ return {
show = true,
-- You can add a "console" section to merge the terminal with the other views
sections = { "watches", "scopes", "exceptions", "breakpoints", "threads", "repl" },
-- Must be one of the sections declared above (except for "console")
-- Must be one of the sections declared above
default_section = "watches",
-- Configure each section individually
base_sections = {
Expand Down
3 changes: 3 additions & 0 deletions docs/src/routes/keymaps/+page.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ The help window itself has only 1 mapping: it can be closed with `q`.
| `<CR>` | Toggle filter |
| **Sessions** |
| `<CR>` | Switch to a session |
| **Terminal** |
| `]s` | Go to next session |
| `[s` | Go to previous session |
| **Navigation** |
| `]v` | Go to next view |
| `[v` | Go to previous view |
Expand Down
4 changes: 0 additions & 4 deletions docs/src/routes/known-issues/+page.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,3 @@ title: Known Issues
- Can't "toggle" a breakpoint ([#74](https://github.com/igorlfs/nvim-dap-view/issues/74))

These limitations stem from `nvim-dap`'s breakpoints API (or more so, from the lack of a proper one). A new API is [planned](https://github.com/mfussenegger/nvim-dap/issues/1388).

## The terminal buffer is cleared right after a session finishes ([#83](https://github.com/igorlfs/nvim-dap-view/issues/83))

Due to a limitation in the way multisession support is currently implemented, it's necessary to eagerly close buffers. Read [this](https://github.com/mfussenegger/nvim-dap/discussions/1523) discussion for details.
3 changes: 1 addition & 2 deletions lua/dap-view.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
require("dap-view.highlight")
require("dap-view.autocmds")
-- Connect hooks to listen to DAP events
require("dap-view.events")
require("dap-view.listeners")

local actions = require("dap-view.actions")

Expand Down
43 changes: 29 additions & 14 deletions lua/dap-view/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ local term = require("dap-view.term")
local state = require("dap-view.state")
local globals = require("dap-view.globals")
local tables = require("dap-view.util.tables")
local traversal = require("dap-view.tree.traversal")

local M = {}

Expand Down Expand Up @@ -96,12 +97,6 @@ M.open = function(hide_terminal)
-- The buffer is already being wiped out, so prevent close() from doing it again.
state.bufnr = nil

-- Prevent the following scenario: user finishes all sessions and quits via the console
-- In the next open, they would start at the console, which is forbidden
if state.current_section == "console" then
state.current_section = setup.config.winbar.default_section
end

M.close()
end)
end
Expand Down Expand Up @@ -152,32 +147,52 @@ end
---@class dapview.NavigateOpts
---@field wrap boolean
---@field count number
---@field type? "views" | "sessions"

---@param opts dapview.NavigateOpts
M.navigate = function(opts)
if not util.is_buf_valid(state.bufnr) or not util.is_win_valid(state.winnr) then
local is_session = opts.type == "sessions"

if (not util.is_buf_valid(state.bufnr) or not util.is_win_valid(state.winnr)) and not is_session then
vim.notify("Can't navigate within views: couldn't find the window")
return
end

local sections = setup.config.winbar.sections
local idx = tables.index_of(sections, state.current_section)
local session = dap.session()

if not session and is_session then
vim.notify("Can't navigate within sessions: no session running")
return
end

-- We actually need to flatten the session, because sessions can be nested
local array = is_session and traversal.flatten_sessions(dap.sessions()) or setup.config.winbar.sections
local idx, sorted_keys = unpack(tables.index_of(array, is_session and session or state.current_section) or {})

if idx == nil then
vim.notify("Can't navigate within views: couldn't find the current view")
vim.notify("Can't navigate: couldn't find the current object")
return
end

local new_idx = idx + opts.count
-- Length operator is unreliable for tables with gaps
local len = #sorted_keys

if opts.wrap then
new_idx = ((new_idx - 1) % #sections) + 1
new_idx = ((new_idx - 1) % len) + 1
else
new_idx = math.min(#sections, math.max(1, new_idx))
new_idx = math.min(len, math.max(1, new_idx))
end

local new_view = sections[new_idx]
if opts.type == "sessions" then
---@cast array table<number,dap.Session>
dap.set_session(array[sorted_keys[new_idx]])
else
---@cast array dapview.Section[]
local new_view = array[sorted_keys[new_idx]]

winbar.show_content(new_view)
winbar.show_content(new_view)
end
end

return M
103 changes: 62 additions & 41 deletions lua/dap-view/events.lua → lua/dap-view/listeners.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,30 @@ dap.listeners.on_session[SUBSCRIPTION_ID] = function(_, new)
state.current_session_id = new.id
state.current_adapter = new.config.type

-- Avoid creating useless buffers for child sessions
if new.parent == nil then
if not state.term_bufnrs[new.id] then
state.term_bufnrs[new.id] = term.setup_term_win_cmd()

if not (term_config.start_hidden or vim.tbl_contains(config.winbar.sections, "console")) then
term.open_term_buf_win()
end
end
else
state.term_bufnrs[new.id] = state.term_bufnrs[new.parent.id]
end

if not vim.tbl_contains(term_config.hide, state.current_adapter) then
-- Handle switching the buf if session is already initialized
if term.fetch_term_buf(new) and not vim.tbl_contains(term_config.hide, state.current_adapter) then
term.switch_term_buf()
end

-- TODO: upstream this?
if util.is_buf_valid(state.bufnr) and state.current_section == "sessions" then
sessions.refresh()
if util.is_buf_valid(state.bufnr) then
-- TODO: upstream this?
if state.current_section == "sessions" then
sessions.refresh()
elseif state.current_section == "scopes" then
scopes.refresh()
elseif state.current_section == "threads" then
require("dap-view.views").switch_to_view("threads")
elseif state.current_section == "console" then
require("dap-view.term").show()
end
end

-- Sync exception breakpoints
-- Does not cover session initialization
-- At this stage, the session is not fully initialized yet
require("dap-view.exceptions").update_exception_breakpoints_filters()

-- TODO maybe we should have a better way to track and update watched expressions when changing sessions
else
state.current_session_id = nil
state.current_adapter = nil
Expand All @@ -58,6 +56,26 @@ dap.listeners.after.configurationDone[SUBSCRIPTION_ID] = function()
-- Sync exception breakpoints for the newly initialized session
-- The downside is that not all adapters support `configurationDone` :/
require("dap-view.exceptions").update_exception_breakpoints_filters()

local config = setup.config
local term_config = config.windows.terminal

local has_console = vim.tbl_contains(config.winbar.sections, "console")
local hidden_adapter = vim.tbl_contains(term_config.hide, state.current_adapter)
local open_term = not term_config.start_hidden and not has_console and not hidden_adapter

-- We can't setup inside `on_session` hook because at that stage the session does not have a `term_buf`
term.setup_term_buf()

-- `term_buf` must be setup before calling open
if open_term then
term.open_term_buf_win()
end

-- Enabling "console" has priority over hiding an adapter
if has_console or not hidden_adapter then
term.switch_term_buf()
end
end

dap.listeners.after.setBreakpoints[SUBSCRIPTION_ID] = function()
Expand All @@ -71,8 +89,7 @@ dap.listeners.after.scopes[SUBSCRIPTION_ID] = function(session)
if util.is_buf_valid(state.bufnr) then
if state.current_section == "scopes" then
scopes.refresh()
end
if state.current_section == "sessions" then
elseif state.current_section == "sessions" then
sessions.refresh()
end
end
Expand All @@ -91,6 +108,20 @@ dap.listeners.after.scopes[SUBSCRIPTION_ID] = function(session)
end
end

---@type dap.RequestListener[]
local continue = { "event_continued", "continue" }

for _, listener in ipairs(continue) do
dap.listeners.after[listener][SUBSCRIPTION_ID] = function()
-- Program is no longer stopped, refresh threads to prevent user from jumping to a no longer accurate location
if state.current_section == "threads" then
require("dap-view.views").switch_to_view("threads")
end

winbar.redraw_controls()
end
end

dap.listeners.after.variables[SUBSCRIPTION_ID] = function()
if state.current_section == "watches" then
require("dap-view.views").switch_to_view("watches")
Expand Down Expand Up @@ -142,7 +173,7 @@ dap.listeners.after.initialize[SUBSCRIPTION_ID] = function(session)
end
end

dap.listeners.after.event_terminated[SUBSCRIPTION_ID] = function(session)
dap.listeners.after.event_terminated[SUBSCRIPTION_ID] = function()
-- Refresh threads view on exit to avoid showing outdated trace
if state.current_section == "threads" then
threads.show()
Expand All @@ -159,41 +190,31 @@ dap.listeners.after.event_terminated[SUBSCRIPTION_ID] = function(session)
end
end

-- TODO find a cleaner way to dispose of these buffers
local term_bufnr = state.term_bufnrs[session.id]
if util.is_buf_valid(term_bufnr) then
vim.api.nvim_buf_delete(term_bufnr, { force = true })
end
for k, v in pairs(state.term_bufnrs) do
if v == term_bufnr then
state.term_bufnrs[k] = nil
end
end

winbar.redraw_controls()
end

--- Refresh winbar on dap session state change events not having a dedicated event handler
local winbar_redraw_events = { "continue", "disconnect", "event_exited", "event_stopped", "restart" }
---@type dap.RequestListener[]
local winbar_redraw = { "disconnect", "event_exited", "event_stopped", "restart" }

for _, event in ipairs(winbar_redraw_events) do
dap.listeners.after[event][SUBSCRIPTION_ID] = winbar.redraw_controls
for _, listener in ipairs(winbar_redraw) do
dap.listeners.after[listener][SUBSCRIPTION_ID] = winbar.redraw_controls
end

local auto_open_events = { "attach", "launch" }
---@type dap.RequestListener[]
local auto_open = { "attach", "launch" }

for _, event in ipairs(auto_open_events) do
dap.listeners.before[event][SUBSCRIPTION_ID] = function()
for _, listener in ipairs(auto_open) do
dap.listeners.before[listener][SUBSCRIPTION_ID] = function()
if setup.config.auto_toggle then
require("dap-view").open()
end
end
end

local auto_close_events = { "event_terminated", "event_exited" }
local auto_close = { "event_terminated", "event_exited" }

for _, event in ipairs(auto_close_events) do
dap.listeners.before[event][SUBSCRIPTION_ID] = function()
for _, listener in ipairs(auto_close) do
dap.listeners.before[listener][SUBSCRIPTION_ID] = function()
if setup.config.auto_toggle then
require("dap-view").close()
end
Expand Down
23 changes: 13 additions & 10 deletions lua/dap-view/scopes/view.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ local widgets = require("dap-view.util.widgets")

local M = {}

local api = vim.api

local scopes_widget

local last_scopes_bufnr
Expand All @@ -24,20 +22,25 @@ local launch_and_refresh_widget = function()

-- Always refresh, otherwise outdated information may be shown
scopes_widget.refresh()
end

M.show = function()
winbar.refresh_winbar("scopes")

if views.cleanup_view(not dap.session(), "No active session") then
local session = dap.session()
if views.cleanup_view(not session, "No active session") then
return
end

launch_and_refresh_widget()
---@cast session dap.Session

local lnum_count = api.nvim_buf_line_count(state.bufnr)
local has_scopes = session.current_frame and session.current_frame.scopes and #session.current_frame.scopes > 0

views.cleanup_view(lnum_count == 1, "Debug adapter returned no scopes")
-- We can't count the number of lines here because the widget might not have refreshed yet,
-- so we resort to looking at the scopes directly
views.cleanup_view(not has_scopes, "Debug adapter returned no scopes")
end

M.show = function()
winbar.refresh_winbar("scopes")

launch_and_refresh_widget()
end

M.refresh = launch_and_refresh_widget
Expand Down
6 changes: 2 additions & 4 deletions lua/dap-view/sessions/view.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,13 @@ local launch_and_refresh_widget = function()
end

sessions_widget.refresh()

views.cleanup_view(not dap.session(), "No active session")
end

M.show = function()
winbar.refresh_winbar("sessions")

if views.cleanup_view(not dap.session(), "No active session") then
return
end

launch_and_refresh_widget()
end

Expand Down
3 changes: 0 additions & 3 deletions lua/dap-view/setup/validate/winbar.lua
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,6 @@ function M.validate(config)
local pretty_sections = vim.inspect(sections)
error("Default section (" .. default .. ") not listed as one of the sections " .. pretty_sections)
end
if default == "console" then
error("Can't use 'console' as the default section")
end
end

return M
2 changes: 0 additions & 2 deletions lua/dap-view/state.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
---@class dapview.State
---@field bufnr? integer
---@field winnr? integer
---@field term_bufnrs {[number]: number}
---@field term_winnr? integer
---@field last_term_winnr? integer
---@field threads_filter string
Expand All @@ -40,7 +39,6 @@
---@field watched_expressions table<string, dapview.ExpressionPack>
---@field cur_pos table<dapview.DefaultSection,integer?>
local M = {
term_bufnrs = {},
threads_filter = "",
threads_filter_invert = false,
exceptions_options = {},
Expand Down
Loading