@@ -4,6 +4,7 @@ import (
44 "bytes"
55 "fmt"
66 "os"
7+ "slices"
78 "sort"
89 "strings"
910
@@ -37,28 +38,40 @@ func applyTextEdits(uri protocol.DocumentURI, edits []protocol.TextEdit, encodin
3738 // Split into lines without the endings
3839 lines := strings.Split(string(content), lineEnding)
3940
40− // Check for overlapping edits
41− for i, edit1 := range edits {
42− for j := i + 1; j < len(edits); j++ {
43− if rangesOverlap(edit1.Range, edits[j].Range) {
44− return fmt.Errorf("overlapping edits detected between edit %d and %d", i, j)
45− }
46− }
47− }
48−
49− // Sort edits in reverse order
41+ // Sort edits once by ascending start position (end position as a stable
42+ // tiebreaker). This lets us both detect overlaps in a single linear pass
43+ // and apply edits from the bottom of the file upward.
5044 sortedEdits := make([]protocol.TextEdit, len(edits))
5145 copy(sortedEdits, edits)
5246 sort.Slice(sortedEdits, func(i, j int) bool {
53− if sortedEdits[i].Range.Start.Line != sortedEdits[j].Range.Start.Line {
54− return sortedEdits[i].Range.Start.Line > sortedEdits[j].Range.Start.Line
47+ a, b := sortedEdits[i].Range, sortedEdits[j].Range
48+ if a.Start.Line != b.Start.Line {
49+ return a.Start.Line < b.Start.Line
5550 }
56− return sortedEdits[i].Range.Start.Character > sortedEdits[j].Range.Start.Character
51+ if a.Start.Character != b.Start.Character {
52+ return a.Start.Character < b.Start.Character
53+ }
54+ if a.End.Line != b.End.Line {
55+ return a.End.Line < b.End.Line
56+ }
57+ return a.End.Character < b.End.Character
5758 })
5859
59− // Apply each edit
60− for _, edit := range sortedEdits {
61− newLines, err := applyTextEdit(lines, edit, encoding)
60+ // Detect overlapping edits in a single linear pass. Because the edits are
61+ // sorted by ascending start, any overlap manifests between two adjacent
62+ // edits, so checking neighbors is sufficient.
63+ for i := 1; i < len(sortedEdits); i++ {
64+ if rangesOverlap(sortedEdits[i-1].Range, sortedEdits[i].Range) {
65+ return fmt.Errorf("overlapping edits detected between edit %d and %d", i-1, i)
66+ }
67+ }
68+
69+ // Apply edits from the bottom of the file upward so that earlier edits'
70+ // line/character offsets are not shifted by later ones. applyTextEdit
71+ // splices the affected range in place instead of rebuilding the whole
72+ // slice per edit.
73+ for i := len(sortedEdits) - 1; i >= 0; i-- {
74+ newLines, err := applyTextEdit(lines, sortedEdits[i], encoding)
6275 if err != nil {
6376 return fmt.Errorf("failed to apply edit: %w", err)
6477 }
@@ -118,12 +131,6 @@ func applyTextEdit(lines []string, edit protocol.TextEdit, encoding powernap.Off
118131 endChar = utf32ToByteOffset(endLineContent, edit.Range.End.Character)
119132 }
120133
121− // Create result slice with initial capacity
122− result := make([]string, 0, len(lines))
123−
124− // Copy lines before edit
125− result = append(result, lines[:startLine]...)
126−
127134 // Get the prefix of the start line
128135 startLineContent := lines[startLine]
129136 if startChar < 0 || startChar > len(startLineContent) {
@@ -138,10 +145,11 @@ func applyTextEdit(lines []string, edit protocol.TextEdit, encoding powernap.Off
138145 }
139146 suffix := endLineContent[endChar:]
140147
141− // Handle the edit
148+ // Build the replacement lines for the edited range [startLine, endLine].
149+ var region []string
142150 if edit.NewText == "" {
143151 if prefix+suffix != "" {
144− result = append(result, prefix+suffix)
152+ region = []string{prefix + suffix}
145153 }
146154 } else {
147155 // Split new text into lines, being careful not to add extra newlines
@@ -150,21 +158,20 @@ func applyTextEdit(lines []string, edit protocol.TextEdit, encoding powernap.Off
150158
151159 if len(newLines) == 1 {
152160 // Single line change
153− result = append(result, prefix+newLines[0]+suffix)
161+ region = []string{prefix + newLines[0] + suffix}
154162 } else {
155163 // Multi-line change
156− result = append(result, prefix+newLines[0])
157− result = append(result, newLines[1:len(newLines)-1]...)
158− result = append(result, newLines[len(newLines)-1]+suffix)
164+ region = make([]string, 0, len(newLines))
165+ region = append(region, prefix+newLines[0])
166+ region = append(region, newLines[1:len(newLines)-1]...)
167+ region = append(region, newLines[len(newLines)-1]+suffix)
159168 }
160169 }
161170
162− // Add remaining lines
163− if endLine+1 < len(lines) {
164− result = append(result, lines[endLine+1:]...)
165− }
166−
167− return result, nil
171+ // Splice the replacement in place instead of rebuilding the whole slice.
172+ // Callers apply edits from the bottom of the file upward, so mutating the
173+ // shared slice does not disturb not-yet-applied (earlier) edits.
174+ return slices.Replace(lines, startLine, endLine+1, region...), nil
168175}
169176
170177// applyDocumentChange applies a DocumentChange (create/rename/delete operations)