Skip to content

Commit 65a9da3

Browse files
committed
fix watches view ignores command
1 parent d477899 commit 65a9da3

14 files changed

Lines changed: 398 additions & 9 deletions

File tree

‎lua/nvim-dapper/views/watches/init.lua‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,19 +267,32 @@ local function render(buf, ctx)
267267
local r = snapshot.render
268268
local watches = fetch._watches()
269269
local paging_rows = nil
270+
local function watch_row_nodes()
271+
local nodes = {}
272+
for i, expr in ipairs(watches) do
273+
nodes[i] = {
274+
__watch_row = true,
275+
index = i,
276+
expression = expr,
277+
variablesReference = 0,
278+
}
279+
end
280+
return nodes
281+
end
270282
if r == "no-session" then
271283
if #watches > 0 then
272284
tree.render_em_dash_values(buf, namespace, watches)
285+
last_row_nodes = watch_row_nodes()
273286
else
274287
tree.render_empty_state(buf, namespace, "no-session")
288+
last_row_nodes = {}
275289
end
276-
last_row_nodes = {}
277290
elseif r == "session-pending" then
278291
tree.render_empty_state(buf, namespace, "session-pending")
279292
last_row_nodes = {}
280293
elseif r == "running" then
281294
tree.render_loading_placeholders(buf, namespace, watches)
282-
last_row_nodes = {}
295+
last_row_nodes = watch_row_nodes()
283296
elseif r == "no-data" then
284297
tree.render_empty_state(buf, namespace, "no-data")
285298
last_row_nodes = {}
@@ -382,8 +395,11 @@ local function register_actions()
382395
end)
383396
end)
384397

385-
actions.action("remove-watch", function(ctx, _node)
398+
actions.action("remove-watch", function(ctx, node)
386399
local idx = ctx and ctx.index
400+
if type(idx) ~= "number" and node and node.__watch_row == true then
401+
idx = node.index
402+
end
387403
if type(idx) ~= "number" then return end
388404
local cur = fetch._watches()
389405
if idx < 1 or idx > #cur then return end

openspec/changes/fix-breakpoints-remove-resync/.openspec.yaml renamed to openspec/changes/archive/2026-05-24-fix-breakpoints-remove-resync/.openspec.yaml

File renamed without changes.

openspec/changes/fix-breakpoints-remove-resync/design.md renamed to openspec/changes/archive/2026-05-24-fix-breakpoints-remove-resync/design.md

File renamed without changes.

openspec/changes/fix-breakpoints-remove-resync/proposal.md renamed to openspec/changes/archive/2026-05-24-fix-breakpoints-remove-resync/proposal.md

File renamed without changes.

openspec/changes/fix-breakpoints-remove-resync/specs/breakpoints-view/spec.md renamed to openspec/changes/archive/2026-05-24-fix-breakpoints-remove-resync/specs/breakpoints-view/spec.md

File renamed without changes.

openspec/changes/fix-breakpoints-remove-resync/tasks.md renamed to openspec/changes/archive/2026-05-24-fix-breakpoints-remove-resync/tasks.md

File renamed without changes.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-05-24
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
## Context
2+
3+
The watches view exposes named actions and the keymap layer binds `dd` to
4+
`remove-watch` by dispatching with the current window in `ctx.win`. Direct
5+
tests can pass `ctx.index`, but the real keymap path does not. The render
6+
path already records cursor-row nodes in `last_row_nodes`, and top-level
7+
watch rows already carry watch identity metadata.
8+
9+
## Goals / Non-Goals
10+
11+
**Goals:**
12+
13+
- Make the default `dd` mapping remove the top-level watch under the cursor.
14+
- Keep explicit `ctx.index` dispatch working for tests and programmatic use.
15+
- Ensure `remove-watch` no-ops on child variable rows and placeholder rows.
16+
- Preserve existing persistence, generation bumping, redraw, and zero-DAP
17+
behavior for successful removal.
18+
19+
**Non-Goals:**
20+
21+
- Do not add confirmation prompts.
22+
- Do not change keymap defaults or the keymap merge layer.
23+
- Do not change watch persistence format or evaluation behavior.
24+
- Do not make child variable rows removable through `remove-watch`.
25+
26+
## Decisions
27+
28+
Use the existing row-node resolver instead of teaching keymaps about watch
29+
indices. The keymap layer should continue to dispatch generic view actions
30+
with `{ win = current_win }`; the watches view owns the mapping from cursor
31+
row to domain object.
32+
33+
Keep explicit index precedence. If `ctx.index` is present and valid, the
34+
handler removes that index as it does today. Otherwise, it examines the
35+
resolved node passed by `actions.dispatch` and uses `node.index` only when
36+
`node.__watch_row == true`. This keeps direct callers stable while fixing
37+
the interactive path.
38+
39+
Treat non-watch rows as a no-op. Expanded child variable rows can have their
40+
own indexes or variable references, but they do not represent entries in the
41+
watch list. Requiring the top-level watch marker prevents accidental list
42+
mutation while a user is inspecting children.
43+
44+
## Risks / Trade-offs
45+
46+
- Cursor node metadata could be missing in non-tree render states -> no-op,
47+
which is preferable to deleting the wrong watch.
48+
- Existing tests mostly cover explicit-index dispatch -> add cursor-driven
49+
regression coverage around the real dispatch path.
50+
- If future render states add removable watch rows, they must provide the
51+
same top-level watch marker and index.
52+
53+
## Migration Plan
54+
55+
No migration is required. Existing user configuration and persisted watch
56+
files remain valid. Rollback is limited to reverting the handler and tests.
57+
58+
## Open Questions
59+
60+
None.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
## Why
2+
3+
The watches view default keymap binds `dd` to `remove-watch`, but the keymap-driven dispatch path does not provide an explicit watch index. In practice, `dd` can no-op even when the cursor is on a top-level watch row.
4+
5+
## What Changes
6+
7+
- Make `remove-watch` support cursor-derived watch selection when no explicit `ctx.index` is provided.
8+
- Keep `remove-watch` inert on expanded child variable rows, empty-state rows, and any row that is not a top-level watch.
9+
- Preserve the existing explicit-index dispatch path for tests and programmatic callers.
10+
- Add regression coverage for keymap-style removal from the cursor row and no-op behavior on child rows.
11+
12+
## Capabilities
13+
14+
### New Capabilities
15+
16+
None.
17+
18+
### Modified Capabilities
19+
20+
- `watches-view`: Clarify that `remove-watch` removes the top-level watch under the cursor when invoked through the view keymap dispatch path, and no-ops on non-watch rows.
21+
22+
## Impact
23+
24+
- Affected code: `lua/nvim-dapper/views/watches/init.lua`, and possibly watch tree-node metadata if needed.
25+
- Affected tests: `tests/views/watches/cursor_dispatch_spec.lua` and/or `tests/views/watches/mutations_spec.lua`.
26+
- No public API, persistence schema, DAP protocol, or dependency changes.
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
## MODIFIED Requirements
2+
3+
### Requirement: Named-action surface
4+
5+
The view module SHALL expose `view.action(name, handler)` and
6+
`view.dispatch(name, ctx)`. The following actions MUST be
7+
registered by the view at module setup:
8+
9+
- `expand` — expand the expandable row at the cursor (watch
10+
or child variable).
11+
- `collapse` — collapse the row at the cursor.
12+
- `expand-all-children` — expand the row at the cursor and
13+
all its already-fetched children.
14+
- `collapse-all-children` — collapse the row and its
15+
descendants.
16+
- `jump-to-source` — open the source location for the child
17+
variable row at the cursor (watch top-level rows
18+
notify-and-no-op).
19+
- `retry` — re-issue the failed request associated with the
20+
row at the cursor.
21+
- `load-more` — issue the next page for the load-more row at
22+
the cursor.
23+
- `add-watch` — prompt via `vim.ui.input` (optionally
24+
pre-filled via `ctx.prefill`) for a new expression and
25+
append it to the list.
26+
- `remove-watch` — remove the top-level watch at the cursor,
27+
or remove `ctx.index` when an explicit index is supplied.
28+
The action MUST no-op when the cursor row is not a
29+
top-level watch row.
30+
- `edit-watch-expression` — prompt via `vim.ui.input`
31+
pre-filled with the current expression; replace on submit;
32+
no-op on cancel.
33+
34+
The view MUST NOT install any keymaps for these actions.
35+
Invoking `view.dispatch(name)` for an unregistered name MUST
36+
raise a clear error naming the missing action.
37+
38+
The three list-mutation actions are human tools per
39+
`project.md` §3.2. They MUST NOT show a confirmation prompt;
40+
the `vim.ui.input` prompt for `add-watch` and
41+
`edit-watch-expression` is used only to collect the
42+
expression text, not to confirm the action. Empty submits for
43+
`add-watch` and `edit-watch-expression` are no-ops; cancel is
44+
a no-op.
45+
46+
#### Scenario: Module setup registers the ten actions
47+
- **WHEN** the view module is loaded and `setup({})` is
48+
invoked
49+
- **THEN** `view.list_actions()` returns a table containing
50+
exactly the names `"expand"`, `"collapse"`,
51+
`"expand-all-children"`, `"collapse-all-children"`,
52+
`"jump-to-source"`, `"retry"`, `"load-more"`,
53+
`"add-watch"`, `"remove-watch"`,
54+
`"edit-watch-expression"`
55+
56+
#### Scenario: View installs no keymaps
57+
- **WHEN** the view is mounted and `on_focus` returns
58+
- **THEN** the view's buffer has no buffer-local keymaps
59+
installed by the view module
60+
61+
#### Scenario: Dispatch on unknown action raises
62+
- **WHEN** `view.dispatch("nope", ctx)` is invoked
63+
- **THEN** the call raises an error identifying the unknown
64+
action
65+
66+
#### Scenario: List-mutation actions show no confirmation prompt
67+
- **WHEN** the user invokes `remove-watch` on a row
68+
- **THEN** the view does not call `vim.ui.select` or any
69+
other confirmation surface before removing the watch
70+
- **AND** the action is identified in the spec as a *human*
71+
side-effecting tool per `project.md` §3.2
72+
73+
#### Scenario: add-watch with empty submit is a no-op
74+
- **WHEN** the user invokes `add-watch` and submits an empty
75+
string at the `vim.ui.input` prompt
76+
- **THEN** the in-memory list is unchanged
77+
- **AND** the on-disk file is not rewritten
78+
79+
#### Scenario: add-watch with cancel is a no-op
80+
- **WHEN** the user invokes `add-watch` and cancels the
81+
`vim.ui.input` prompt
82+
- **THEN** the in-memory list is unchanged
83+
- **AND** the on-disk file is not rewritten
84+
85+
#### Scenario: add-watch with non-empty submit appends and re-evaluates
86+
- **WHEN** the user invokes `add-watch` with expression `e`
87+
and a session is paused
88+
- **THEN** `e` is appended to the in-memory list
89+
- **AND** the on-disk file is rewritten atomically
90+
- **AND** exactly one `evaluate` request is issued for `e`
91+
with `context = "watch"` and `frameId = current_frame()`
92+
captured at issue time (not at response time)
93+
- **AND** the generation counter is bumped before the request
94+
is issued (so a subsequent step that arrives between issue
95+
and response advances the generation and the response is
96+
dropped per the stale-response rule)
97+
98+
#### Scenario: edit-watch-expression replaces and re-evaluates
99+
- **WHEN** the user invokes `edit-watch-expression` on the
100+
watch at index 1, submits a new expression `e2`, and a
101+
session is paused
102+
- **THEN** the watch at index 1 becomes `e2`
103+
- **AND** any prior child-tree state for that index is
104+
discarded
105+
- **AND** exactly one `evaluate` request is issued for `e2`
106+
107+
#### Scenario: remove-watch removes and persists by explicit index
108+
- **WHEN** the user invokes `remove-watch` with `ctx.index`
109+
set to watch index 2 (out of three)
110+
- **THEN** the in-memory list has two entries
111+
- **AND** the on-disk file is rewritten atomically
112+
- **AND** no DAP request is issued
113+
114+
#### Scenario: remove-watch removes and persists from cursor watch row
115+
- **WHEN** the watches view has three watches and the cursor
116+
is on the second top-level watch row
117+
- **AND** the user invokes `remove-watch` through the view
118+
keymap dispatch path with only `ctx.win`
119+
- **THEN** the second watch is removed from the in-memory list
120+
- **AND** the on-disk file is rewritten atomically
121+
- **AND** no DAP request is issued
122+
123+
#### Scenario: remove-watch no-ops on child variable row
124+
- **WHEN** the cursor is on a child variable row inside an
125+
expanded watch
126+
- **AND** the user invokes `remove-watch` through the view
127+
keymap dispatch path with only `ctx.win`
128+
- **THEN** the in-memory watch list is unchanged
129+
- **AND** the on-disk file is not rewritten
130+
- **AND** no DAP request is issued

0 commit comments

Comments
 (0)