Skip to content

Commit 170560c

Browse files
committed
refactor: native sessions view
We no longer rely on nvim-dap's builtin sessions widget, we rolled our own. Having full control of the rendering is nice, as it allows changing stuff more easily on the future. Kinda related to #124: there's an ongoing effort to also get rid of the scopes widget. With that, nvim-dap-view would be completely independent from the widgets, which I think is nice? Having a single view based on widgets sucks, because it introduces some abstractions and yada yada yada. It's simple enough to be implemented "natively" with too much effort
1 parent b976fa2 commit 170560c

File tree

8 files changed

+86
-43
lines changed

8 files changed

+86
-43
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ return {
2727
- Watch expressions
2828
- Manipulate breakpoints
2929
- Navigate in the call stack
30+
- Manage ongoing debug sessions
3031
- Inspect all variables in scope[^1]
3132
- REPL
3233

docs/src/routes/home/+page.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ Use the scopes widget provided by nvim-dap
8282

8383
<img src="https://i.ibb.co/1fSHs7J1/image.png" alt="sessions view">
8484

85-
Use the sessions widget provided by nvim-dap (**disabled by default**)
85+
List all debugging sessions (**disabled by default**)
86+
87+
- Switch to a session
8688

8789
### REPL view
8890

lua/dap-view/listeners.lua

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@ local dap = require("dap")
33
local state = require("dap-view.state")
44
local breakpoints = require("dap-view.breakpoints.view")
55
local scopes = require("dap-view.scopes.view")
6-
local sessions = require("dap-view.sessions.view")
76
local util = require("dap-view.util")
87
local term = require("dap-view.console.view")
98
local setup = require("dap-view.setup")
109
local refresher = require("dap-view.refresher")
1110
local winbar = require("dap-view.options.winbar")
1211
local traversal = require("dap-view.tree.traversal")
1312
local scroll = require("dap-view.console.scroll")
14-
local adapter_ = require("dap-view.util.adapter")
1513

1614
local SUBSCRIPTION_ID = "dap-view"
1715

@@ -41,7 +39,7 @@ dap.listeners.on_session[SUBSCRIPTION_ID] = function(_, new)
4139
if util.is_buf_valid(new.term_buf) and scroll.is_autoscrolling(new.term_buf) then
4240
local winnr = vim.tbl_contains(setup.config.winbar.sections, "console") and state.winnr
4341
or state.term_winnr
44-
if util.is_win_valid(winnr) then
42+
if util.is_win_valid(winnr) and state.current_section == "console" then
4543
---@cast winnr integer
4644
scroll.scroll_to_bottom(winnr, new.term_buf)
4745
end
@@ -53,6 +51,15 @@ dap.listeners.on_session[SUBSCRIPTION_ID] = function(_, new)
5351
else
5452
state.current_session_id = nil
5553
state.current_adapter = nil
54+
55+
-- Forces a refresh when terminating the last session
56+
-- Schedule so the session can properly finish (workaroundy)
57+
--
58+
-- This is useful when terminating sessions from the js-debug-adapter
59+
-- Setup: any attach configuration, when terminating either the root or any child
60+
-- (more consistent when terminating a child)
61+
-- View: sessions
62+
vim.schedule(refresher.refresh_session_based_views)
5663
end
5764
end
5865

@@ -100,12 +107,14 @@ dap.listeners.after.setBreakpoints[SUBSCRIPTION_ID] = function()
100107
end
101108

102109
dap.listeners.after.scopes[SUBSCRIPTION_ID] = function(session)
110+
if state.current_section == "sessions" then
111+
require("dap-view.views").switch_to_view("sessions")
112+
end
113+
103114
-- nvim-dap needs a buffer to operate
104115
if util.is_buf_valid(state.bufnr) then
105116
if state.current_section == "scopes" then
106117
scopes.refresh()
107-
elseif state.current_section == "sessions" then
108-
sessions.refresh()
109118
end
110119
end
111120
if state.current_section == "threads" then
@@ -176,14 +185,7 @@ end
176185

177186
-- The debuggee has terminated
178187
dap.listeners.after.event_terminated[SUBSCRIPTION_ID] = function(session)
179-
-- When terminating, outdated sessions may be shown
180-
-- As a workaround, do not refresh for the root session from js-debug-adapter
181-
-- Steps: js-debug-adapter (chrome) + attach
182-
local is_js_adapter = adapter_.is_js_adapter(session.config.type)
183-
184-
if not is_js_adapter or session.parent then
185-
refresher.refresh_session_based_views()
186-
end
188+
refresher.refresh_session_based_views()
187189

188190
winbar.redraw_controls()
189191

lua/dap-view/refresher.lua

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
local state = require("dap-view.state")
22
local threads = require("dap-view.threads.view")
33
local scopes = require("dap-view.scopes.view")
4-
local sessions = require("dap-view.sessions.view")
54
local exceptions = require("dap-view.exceptions.view")
65
local eval = require("dap-view.watches.eval")
76
local util = require("dap-view.util")
@@ -21,13 +20,13 @@ M.refresh_session_based_views = function()
2120
exceptions.show()
2221
elseif state.current_section == "watches" then
2322
require("dap-view.views").switch_to_view("watches")
23+
elseif state.current_section == "sessions" then
24+
require("dap-view.views").switch_to_view("sessions")
2425
end
2526

2627
if util.is_buf_valid(state.bufnr) then
2728
if state.current_section == "scopes" then
2829
scopes.refresh()
29-
elseif state.current_section == "sessions" then
30-
sessions.refresh()
3130
end
3231
end
3332
end

lua/dap-view/sessions/actions.lua

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
local dap = require("dap")
2+
3+
local state = require("dap-view.state")
4+
5+
local M = {}
6+
7+
---@param line integer
8+
M.switch_to_session = function(line)
9+
local session = state.sessions_by_line[line]
10+
11+
if session == nil then
12+
vim.notify("No session under the cursor")
13+
return
14+
end
15+
16+
dap.set_session(session)
17+
end
18+
19+
return M

lua/dap-view/sessions/view.lua

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,63 @@
11
local dap = require("dap")
2-
local dap_widgets = require("dap.ui.widgets")
32

43
local views = require("dap-view.views")
5-
local winbar = require("dap-view.options.winbar")
64
local state = require("dap-view.state")
75
local util = require("dap-view.util")
8-
local widgets = require("dap-view.util.widgets")
6+
local hl = require("dap-view.util.hl")
97

108
local M = {}
119

12-
local sessions_widget
10+
local api = vim.api
1311

14-
local last_sessions_bufnr
12+
---@param children table<number,dap.Session>
13+
---@param focused_id number
14+
---@param line number
15+
local function show_children_sessions(children, focused_id, line)
16+
for _, session in pairs(children) do
17+
local content = session.id .. ": " .. session.config.name
1518

16-
local launch_and_refresh_widget = function()
17-
if last_sessions_bufnr == nil or last_sessions_bufnr ~= state.bufnr then
18-
sessions_widget = widgets.new_widget(state.bufnr, state.winnr, dap_widgets.sessions)
19-
last_sessions_bufnr = state.bufnr
19+
local parent = session.parent
20+
local num_parents = 0
21+
while parent ~= nil do
22+
parent = parent.parent
23+
num_parents = num_parents + 1
24+
end
2025

21-
sessions_widget.open()
22-
end
26+
local prefix = string.rep(" ", num_parents)
27+
local indented_content = prefix .. content
2328

24-
if not util.is_win_valid(state.winnr) then
25-
return
26-
end
29+
api.nvim_buf_set_lines(state.bufnr, line, line, true, { indented_content })
30+
31+
if focused_id == session.id then
32+
hl.hl_range("FrameCurrent", { line, 0 }, { line, -1 })
33+
end
2734

28-
sessions_widget.refresh()
35+
state.sessions_by_line[line + 1] = session
36+
37+
line = line + 1
38+
39+
line = show_children_sessions(session.children, focused_id, line)
40+
end
2941

30-
views.cleanup_view(not dap.session(), "No active session")
42+
return line
3143
end
3244

3345
M.show = function()
34-
winbar.refresh_winbar("sessions")
46+
if util.is_buf_valid(state.bufnr) and util.is_win_valid(state.winnr) then
47+
local focused = dap.session()
3548

36-
launch_and_refresh_widget()
37-
end
49+
if views.cleanup_view(focused == nil, "No active session") then
50+
return
51+
end
52+
53+
---@cast focused dap.Session
54+
55+
local line = 0
3856

39-
M.refresh = launch_and_refresh_widget
57+
line = show_children_sessions(dap.sessions(), focused.id, line)
58+
59+
api.nvim_buf_set_lines(state.bufnr, line, -1, true, {})
60+
end
61+
end
4062

4163
return M

lua/dap-view/state.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
---@field frames_by_line table<integer, dap.StackFrame>
3636
---@field expression_views_by_line table<integer, {expression: string, view: dapview.ExpressionView}>
3737
---@field variable_views_by_line table<integer, {parent_reference: number, variable: dap.Variable, view: dapview.VariableView}>
38+
---@field sessions_by_line table<integer, dap.Session>
3839
---@field watched_expressions table<string, dapview.ExpressionView>
3940
---@field expr_count integer
4041
---@field cur_pos table<dapview.DefaultSection,integer?>
@@ -47,6 +48,7 @@ local M = {
4748
frames_by_line = {},
4849
expression_views_by_line = {},
4950
variable_views_by_line = {},
51+
sessions_by_line = {},
5052
subtle_frames = false,
5153
watched_expressions = {},
5254
cur_pos = {},

lua/dap-view/views/keymaps/views.lua

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ M.views_keymaps = function()
1616
require("dap-view.views.util").jump_to_location("^(.-)|(%d+)|")
1717
elseif state.current_section == "threads" then
1818
require("dap-view.threads.actions").jump_and_set_frame(cursor_line)
19+
elseif state.current_section == "sessions" then
20+
require("dap-view.sessions.actions").switch_to_session(cursor_line)
1921
elseif state.current_section == "exceptions" then
2022
require("dap-view.exceptions.actions").toggle_exception_filter()
2123
elseif state.current_section == "scopes" or state.current_section == "sessions" then
@@ -29,12 +31,6 @@ M.views_keymaps = function()
2931
end
3032
end)()
3133
end
32-
33-
-- Selecting a session triggers a full redraw
34-
-- To properly restore the cursor position we have to call switch_to_view
35-
if state.current_section == "sessions" then
36-
require("dap-view.views").switch_to_view("sessions")
37-
end
3834
end)
3935

4036
keymap("<C-w><CR>", function()

0 commit comments

Comments
 (0)