Skip to content

Commit ed1db10

Browse files
Ensure breakpoint updates change correct breakpoint (#1543)
Problem: The sign id returned by sign_place() is buffer-local, so the ids of signs in different buffers can be the same. Due to this, set_breakpoint with condition overrides condition of another breakpoint in another buffer. Solution: * Maintain mapping of buffer → sign id → breakpoint. * Also fix other issues with wrong fields of bp. Co-authored-by: Mathias Fussenegger <[email protected]>
1 parent a479e25 commit ed1db10

File tree

2 files changed

+73
-36
lines changed

2 files changed

+73
-36
lines changed

lua/dap/breakpoints.lua

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
local api = vim.api
22
local non_empty = require('dap.utils').non_empty
33

4-
local bp_by_sign = {}
4+
---@class dap.bp
5+
---@field buf integer
6+
---@field line integer
7+
---@field condition string?
8+
---@field logMessage string?
9+
---@field hitCondition string?
10+
---@field state dap.Breakpoint?
11+
12+
---@type table<integer, table<integer, dap.bp>> buffer sign id bp
13+
local bp_by_sign_by_buf = {}
514
local ns = 'dap_breakpoints'
615
local M = {}
716

@@ -21,9 +30,9 @@ local function get_breakpoint_signs(bufexpr)
2130
return result
2231
end
2332

24-
33+
---@param bp dap.bp
2534
local function get_sign_name(bp)
26-
if bp.verified == false then
35+
if bp.state and bp.state.verified == false then
2736
return 'DapBreakpointRejected'
2837
elseif non_empty(bp.condition) then
2938
return 'DapBreakpointCondition'
@@ -38,23 +47,23 @@ end
3847
---@param breakpoint dap.Breakpoint
3948
function M.update(breakpoint)
4049
assert(breakpoint.id, "To update a breakpoint it must have an id property")
41-
for sign_id, bp in pairs(bp_by_sign) do
42-
if bp.state and bp.state.id == breakpoint.id then
43-
local verified_changed =
44-
bp.state.verified == false and breakpoint.verified
45-
or breakpoint.verified == false and bp.state.verified
46-
if verified_changed then
47-
vim.fn.sign_place(
48-
sign_id,
49-
ns,
50-
get_sign_name(bp),
51-
bp.buf,
52-
{ lnum = bp.line; priority = 21; }
53-
)
50+
for _, bp_by_sign in pairs(bp_by_sign_by_buf) do
51+
for sign_id, bp in pairs(bp_by_sign) do
52+
if bp.state and bp.state.id == breakpoint.id then
53+
local verified_changed = bp.state.verified ~= breakpoint.verified
54+
bp.state.verified = breakpoint.verified
55+
bp.state.message = breakpoint.message
56+
if verified_changed then
57+
vim.fn.sign_place(
58+
sign_id,
59+
ns,
60+
get_sign_name(bp),
61+
bp.buf,
62+
{ lnum = bp.line; priority = 21; }
63+
)
64+
end
65+
return
5466
end
55-
bp.state.verified = breakpoint.verified
56-
bp.state.message = breakpoint.message
57-
return
5867
end
5968
end
6069
end
@@ -72,7 +81,7 @@ function M.set_state(bufnr, state)
7281
return
7382
end
7483
for _, sign in pairs(signs) do
75-
local bp = bp_by_sign[sign.id]
84+
local bp = bp_by_sign_by_buf[bufnr][sign.id]
7685
if bp then
7786
bp.state = state
7887
end
@@ -95,7 +104,7 @@ function M.remove(bufnr, lnum)
95104
if signs and #signs > 0 then
96105
for _, sign in pairs(signs) do
97106
vim.fn.sign_unplace(ns, { buffer = bufnr; id = sign.id; })
98-
bp_by_sign[sign.id] = nil
107+
bp_by_sign_by_buf[bufnr][sign.id] = nil
99108
end
100109
return true
101110
else
@@ -104,11 +113,13 @@ function M.remove(bufnr, lnum)
104113
end
105114

106115
function M.remove_by_id(id)
107-
for sign_id, bp in pairs(bp_by_sign) do
108-
if bp.state and bp.state.id == id then
109-
vim.fn.sign_unplace(ns, { buffer = bp.buf, id = sign_id, })
110-
bp_by_sign[sign_id] = nil
111-
return
116+
for _, bp_by_sign in pairs(bp_by_sign_by_buf) do
117+
for sign_id, bp in pairs(bp_by_sign) do
118+
if bp.state and bp.state.id == id then
119+
vim.fn.sign_unplace(ns, { buffer = bp.buf, id = sign_id, })
120+
bp_by_sign_by_buf[bp.buf][sign_id] = nil
121+
return
122+
end
112123
end
113124
end
114125
end
@@ -120,8 +131,9 @@ function M.toggle(opts, bufnr, lnum)
120131
if M.remove(bufnr, lnum) and not opts.replace then
121132
return
122133
end
123-
local bp = {
134+
local bp = { ---@type dap.bp
124135
buf = bufnr,
136+
line = lnum,
125137
condition = opts.condition,
126138
logMessage = opts.log_message,
127139
hitCondition = opts.hit_condition
@@ -135,7 +147,10 @@ function M.toggle(opts, bufnr, lnum)
135147
{ lnum = lnum; priority = 21; }
136148
)
137149
if sign_id ~= -1 then
138-
bp_by_sign[sign_id] = bp
150+
if not bp_by_sign_by_buf[bufnr] then
151+
bp_by_sign_by_buf[bufnr] = {}
152+
end
153+
bp_by_sign_by_buf[bufnr][sign_id] = bp
139154
end
140155
end
141156

@@ -159,7 +174,7 @@ function M.get(bufexpr)
159174
local bufnr = buf_bp_signs.bufnr
160175
result[bufnr] = breakpoints
161176
for _, bp in pairs(buf_bp_signs.signs) do
162-
local bp_entry = bp_by_sign[bp.id] or {}
177+
local bp_entry = bp_by_sign_by_buf[bufnr][bp.id] or {}
163178
table.insert(breakpoints, {
164179
line = bp.lnum;
165180
condition = bp_entry.condition;
@@ -175,7 +190,7 @@ end
175190

176191
function M.clear()
177192
vim.fn.sign_unplace(ns)
178-
bp_by_sign = {}
193+
bp_by_sign_by_buf = {}
179194
end
180195

181196

spec/breakpoints_spec.lua

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ describe('breakpoints', function()
44

55
require('dap')
66
local breakpoints = require('dap.breakpoints')
7-
after_each(breakpoints.clear)
7+
after_each(function()
8+
breakpoints.clear()
9+
for _, b in ipairs(api.nvim_list_bufs()) do
10+
api.nvim_buf_delete(b, { force = true })
11+
end
12+
end)
813

914
it('can set normal breakpoints', function()
1015
breakpoints.set()
@@ -53,23 +58,40 @@ describe('breakpoints', function()
5358
local lnum = api.nvim_win_get_cursor(0)[1]
5459
breakpoints.toggle()
5560
local state = { line = lnum, id = 1 }
56-
breakpoints.set_state(api.nvim_get_current_buf(), state)
61+
local fstbuf = api.nvim_get_current_buf()
62+
breakpoints.set_state(fstbuf, state)
63+
local newbuf = api.nvim_create_buf(true, true)
64+
api.nvim_set_current_buf(newbuf)
65+
api.nvim_buf_set_lines(newbuf, 0, -1, true, {"Hello", "World"})
66+
breakpoints.toggle({}, newbuf, 2)
5767
local expected = {
58-
[1] = {
68+
[fstbuf] = {
5969
{
6070
line = lnum,
6171
state = state,
6272
},
6373
},
74+
[newbuf] = {
75+
{
76+
line = 2,
77+
}
78+
}
6479
}
6580
assert.are.same(expected, breakpoints.get())
6681
breakpoints.remove_by_id(1)
67-
assert.are.same({}, breakpoints.get())
82+
expected[fstbuf] = nil
83+
assert.are.same(expected, breakpoints.get())
6884
end)
6985

7086
it('toggle adds bp if missing, otherwise removes', function()
7187
breakpoints.toggle()
72-
assert.are.same({{{line = 1}}}, breakpoints.get())
88+
local buf = api.nvim_get_current_buf()
89+
local expected = {
90+
[buf] = {
91+
{ line = 1 },
92+
}
93+
}
94+
assert.are.same(expected, breakpoints.get())
7395
breakpoints.toggle()
7496
assert.are.same({}, breakpoints.get())
7597
end)
@@ -81,7 +103,7 @@ describe('breakpoints', function()
81103
assert.are.same(
82104
{
83105
{
84-
bufnr = 1,
106+
bufnr = buf,
85107
col = 0,
86108
lnum = 1,
87109
text = 'Hello breakpoint, Condition: x > 10'

0 commit comments

Comments
 (0)