on main help sign in
fix(lsp): treat adjacent ranges as non-overlapping per LSP spec (#2322)
4596b02huaiyuWangh
internal/lsp/util/edit.go+6−2
@@ -247,14 +247,18 @@ func ApplyWorkspaceEdit(edit protocol.WorkspaceEdit) error {
247 return nil
247 return nil
248}
248}
249
249
250+// rangesOverlap checks if two LSP ranges overlap.
251+// Per the LSP specification, ranges are half-open intervals [start, end),
252+// so adjacent ranges where one's end equals another's start do NOT overlap.
253+// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#range
250func rangesOverlap(r1, r2 protocol.Range) bool {
254func rangesOverlap(r1, r2 protocol.Range) bool {
251 if r1.Start.Line > r2.End.Line || r2.Start.Line > r1.End.Line {
255 if r1.Start.Line > r2.End.Line || r2.Start.Line > r1.End.Line {
252 return false
256 return false
253 }
257 }
254− if r1.Start.Line == r2.End.Line && r1.Start.Character > r2.End.Character {
258+ if r1.Start.Line == r2.End.Line && r1.Start.Character >= r2.End.Character {
255 return false
259 return false
256 }
260 }
257− if r2.Start.Line == r1.End.Line && r2.Start.Character > r1.End.Character {
261+ if r2.Start.Line == r1.End.Line && r2.Start.Character >= r1.End.Character {
258 return false
262 return false
259 }
263 }
260 return true
264 return true
internal/lsp/util/edit_test.gonew file+68
@@ -0,0 +1,68 @@
1+package util
2+
3+import (
4+ "testing"
5+
6+ "github.com/stretchr/testify/require"
7+
8+ "github.com/charmbracelet/x/powernap/pkg/lsp/protocol"
9+)
10+
11+func TestRangesOverlap(t *testing.T) {
12+ t.Parallel()
13+
14+ tests := []struct {
15+ name string
16+ r1 protocol.Range
17+ r2 protocol.Range
18+ want bool
19+ }{
20+ {
21+ name: "adjacent ranges do not overlap",
22+ r1: protocol.Range{
23+ Start: protocol.Position{Line: 0, Character: 0},
24+ End: protocol.Position{Line: 0, Character: 5},
25+ },
26+ r2: protocol.Range{
27+ Start: protocol.Position{Line: 0, Character: 5},
28+ End: protocol.Position{Line: 0, Character: 10},
29+ },
30+ want: false,
31+ },
32+ {
33+ name: "overlapping ranges",
34+ r1: protocol.Range{
35+ Start: protocol.Position{Line: 0, Character: 0},
36+ End: protocol.Position{Line: 0, Character: 8},
37+ },
38+ r2: protocol.Range{
39+ Start: protocol.Position{Line: 0, Character: 5},
40+ End: protocol.Position{Line: 0, Character: 10},
41+ },
42+ want: true,
43+ },
44+ {
45+ name: "non-overlapping with gap",
46+ r1: protocol.Range{
47+ Start: protocol.Position{Line: 0, Character: 0},
48+ End: protocol.Position{Line: 0, Character: 3},
49+ },
50+ r2: protocol.Range{
51+ Start: protocol.Position{Line: 0, Character: 7},
52+ End: protocol.Position{Line: 0, Character: 10},
53+ },
54+ want: false,
55+ },
56+ }
57+
58+ for _, tt := range tests {
59+ t.Run(tt.name, func(t *testing.T) {
60+ t.Parallel()
61+ got := rangesOverlap(tt.r1, tt.r2)
62+ require.Equal(t, tt.want, got, "rangesOverlap(r1, r2)")
63+ // Overlap should be symmetric
64+ got2 := rangesOverlap(tt.r2, tt.r1)
65+ require.Equal(t, tt.want, got2, "rangesOverlap(r2, r1) symmetry")
66+ })
67+ }
68+}