Merge pull request #3704 from jsternberg/dap-case-insensitive-breakpoints

dap: detect breakpoints for files when the case differs
This commit is contained in:
Tõnis Tiigi
2026-03-10 18:01:49 -07:00
committed by GitHub
3 changed files with 43 additions and 13 deletions
+27 -5
View File
@@ -9,6 +9,7 @@ import (
"path"
"path/filepath"
"slices"
"strings"
"sync"
"sync/atomic"
@@ -573,7 +574,9 @@ func (b *breakpointMap) Set(fname string, sbps []dap.SourceBreakpoint) (breakpoi
// null back in the JSON if there are no breakpoints
breakpoints = []dap.Breakpoint{}
prev := b.byPath[fname]
// Use lowercase paths to normalize the case. We can only know the correct casing after
// we intersect the breakpoint map. When we report the pending breakpoint to the editor,
prev := b.getByPath(fname)
for _, sbp := range sbps {
index := slices.IndexFunc(prev, func(e dap.Breakpoint) bool {
return sbp.Line >= e.Line && sbp.Line <= e.EndLine && sbp.Column >= e.Column && sbp.Column <= e.EndColumn
@@ -589,12 +592,16 @@ func (b *breakpointMap) Set(fname string, sbps []dap.SourceBreakpoint) (breakpoi
EndLine: sbp.Line,
Column: sbp.Column,
EndColumn: sbp.Column,
Reason: "pending",
Source: &dap.Source{
Name: path.Base(fname),
Path: fname,
},
Reason: "pending",
}
}
breakpoints = append(breakpoints, bp)
}
b.byPath[fname] = breakpoints
b.setByPath(fname, breakpoints)
return breakpoints
}
@@ -615,7 +622,7 @@ func (b *breakpointMap) Intersect(ctx Context, src *pb.Source, ws string) map[di
for _, info := range src.Infos {
fname := filepath.Join(ws, info.Filename)
bps := b.byPath[fname]
bps := b.getByPath(fname)
for _, bp := range bps {
if !bp.Verified && bp.Reason != "failed" {
bp.Reason = "failed"
@@ -656,7 +663,7 @@ func (b *breakpointMap) intersect(ctx Context, src *pb.Source, locs *pb.Location
info := src.Infos[loc.SourceIndex]
fname := filepath.Join(ws, info.Filename)
bps := b.byPath[fname]
bps := b.getByPath(fname)
if len(bps) == 0 {
// No breakpoints for this file.
continue
@@ -675,6 +682,13 @@ func (b *breakpointMap) intersect(ctx Context, src *pb.Source, locs *pb.Location
bp.Verified = true
bp.Reason = ""
// The path from the source might be different than the path
// the editor sent to us just because of different casing.
// Prefer the version given to us from buildkit and tell
// the editor what the proper casing for this should be.
bp.Source.Name = path.Base(fname)
bp.Source.Path = fname
ctx.C() <- &dap.BreakpointEvent{
Event: dap.Event{Event: "breakpoint"},
Body: dap.BreakpointEventBody{
@@ -689,3 +703,11 @@ func (b *breakpointMap) intersect(ctx Context, src *pb.Source, locs *pb.Location
}
return 0
}
func (b *breakpointMap) setByPath(fname string, bps []dap.Breakpoint) {
b.byPath[strings.ToLower(fname)] = bps
}
func (b *breakpointMap) getByPath(fname string) []dap.Breakpoint {
return b.byPath[strings.ToLower(fname)]
}
+2 -2
View File
@@ -202,7 +202,7 @@ func TestBreakpointMapIntersectVerified(t *testing.T) {
assert.Len(t, digests, wantMatches)
expectedEvents := make(map[int]struct{})
for i, bp := range bm.byPath[fpath] {
for i, bp := range bm.getByPath(fpath) {
if breakpointCases[i].expectVerified {
expectedEvents[bp.Id] = struct{}{}
}
@@ -226,7 +226,7 @@ func TestBreakpointMapIntersectVerified(t *testing.T) {
}
}
stored := bm.byPath[fpath]
stored := bm.getByPath(fpath)
if assert.Len(t, stored, len(breakpointCases)) {
for i, bc := range breakpointCases {
assert.Equal(t, bc.expectVerified, stored[i].Verified, "breakpoint %d (%s) mismatch", i, bc.desc)
+14 -6
View File
@@ -228,18 +228,26 @@ func testDapBuildVerifiedBreakpoints(t *testing.T, sb integration.Sandbox) {
{
Reason: "changed",
Breakpoint: dap.Breakpoint{
Id: 1,
Line: 2,
EndLine: 2,
Id: 1,
Line: 2,
EndLine: 2,
Source: &dap.Source{
Name: "Dockerfile",
Path: path.Join(dir, "Dockerfile"),
},
Verified: true,
},
},
{
Reason: "changed",
Breakpoint: dap.Breakpoint{
Id: 2,
Line: 10,
EndLine: 10,
Id: 2,
Line: 10,
EndLine: 10,
Source: &dap.Source{
Name: "Dockerfile",
Path: path.Join(dir, "Dockerfile"),
},
Verified: false,
Reason: "failed",
},