Skip to content

Commit 4f5806e

Browse files
committed
match beginning and end of line correctly in ReplaceRegex
1 parent 43c9c30 commit 4f5806e

2 files changed

Lines changed: 71 additions & 41 deletions

File tree

internal/buffer/loc.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ func (l Loc) LessEqual(b Loc) bool {
4747
return l == b
4848
}
4949

50+
// Clamp clamps a loc between start and end
51+
func (l Loc) Clamp(start, end Loc) Loc {
52+
if l.GreaterEqual(end) {
53+
return end
54+
} else if l.LessThan(start) {
55+
return start
56+
}
57+
return l
58+
}
59+
5060
// The following functions require a buffer to know where newlines are
5161

5262
// Diff returns the distance between two locations
@@ -139,10 +149,5 @@ func ByteOffset(pos Loc, buf *Buffer) int {
139149

140150
// clamps a loc within a buffer
141151
func clamp(pos Loc, la *LineArray) Loc {
142-
if pos.GreaterEqual(la.End()) {
143-
return la.End()
144-
} else if pos.LessThan(la.Start()) {
145-
return la.Start()
146-
}
147-
return pos
152+
return pos.Clamp(la.Start(), la.End())
148153
}

internal/buffer/search.go

Lines changed: 60 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,26 @@ func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
127127
return [2]Loc{}, false
128128
}
129129

130+
func (b *Buffer) findAll(r *regexp.Regexp, start, end Loc) [][2]Loc {
131+
matches := [][2]Loc{}
132+
loc := start
133+
for {
134+
match, found := b.findDown(r, loc, end)
135+
if !found {
136+
break
137+
}
138+
matches = append(matches, match)
139+
if match[0] != match[1] {
140+
loc = match[1]
141+
} else if match[1] != end {
142+
loc = match[1].Move(1, b)
143+
} else {
144+
break
145+
}
146+
}
147+
return matches
148+
}
149+
130150
// FindNext finds the next occurrence of a given string in the buffer
131151
// It returns the start and end location of the match (if found) and
132152
// a boolean indicating if it was found
@@ -170,53 +190,58 @@ func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bo
170190
}
171191

172192
// ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area
173-
// and returns the number of replacements made and the number of runes
193+
// and returns the number of replacements made and the number of characters
174194
// added or removed on the last line of the range
175195
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte, captureGroups bool) (int, int) {
176196
if start.GreaterThan(end) {
177197
start, end = end, start
178198
}
179199

180-
netrunes := 0
181-
200+
charsEnd := util.CharacterCount(b.LineBytes(end.Y))
182201
found := 0
183202
var deltas []Delta
203+
184204
for i := start.Y; i <= end.Y; i++ {
185-
l := b.lines[i].data
186-
charpos := 0
187-
188-
if start.Y == end.Y && i == start.Y {
189-
l = util.SliceStart(l, end.X)
190-
l = util.SliceEnd(l, start.X)
191-
charpos = start.X
192-
} else if i == start.Y {
193-
l = util.SliceEnd(l, start.X)
194-
charpos = start.X
195-
} else if i == end.Y {
196-
l = util.SliceStart(l, end.X)
197-
}
198-
newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
199-
var result []byte
200-
if captureGroups {
201-
for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
202-
result = search.Expand(result, replace, in, submatches)
205+
l := b.LineBytes(i)
206+
charCount := util.CharacterCount(l)
207+
if (i == start.Y && start.X > 0) || (i == end.Y && end.X < charCount) {
208+
// This replacement code works in general, but it creates a separate
209+
// modification for each match. We only use it for the first and last
210+
// lines, which may use padded regexps
211+
212+
from := Loc{0, i}.Clamp(start, end)
213+
to := Loc{charCount, i}.Clamp(start, end)
214+
matches := b.findAll(search, from, to)
215+
found += len(matches)
216+
217+
for j := len(matches) - 1; j >= 0; j-- {
218+
// if we counted upwards, the different deltas would interfere
219+
match := matches[j]
220+
var newText []byte
221+
if captureGroups {
222+
newText = search.ReplaceAll(b.Substr(match[0], match[1]), replace)
223+
} else {
224+
newText = replace
203225
}
204-
} else {
205-
result = replace
206-
}
207-
found++
208-
if i == end.Y {
209-
netrunes += util.CharacterCount(result) - util.CharacterCount(in)
226+
deltas = append(deltas, Delta{newText, match[0], match[1]})
210227
}
211-
return result
212-
})
213-
214-
from := Loc{charpos, i}
215-
to := Loc{charpos + util.CharacterCount(l), i}
216-
217-
deltas = append(deltas, Delta{newText, from, to})
228+
} else {
229+
newLine := search.ReplaceAllFunc(l, func(in []byte) []byte {
230+
found++
231+
var result []byte
232+
if captureGroups {
233+
match := search.FindSubmatchIndex(in)
234+
result = search.Expand(result, replace, in, match)
235+
} else {
236+
result = replace
237+
}
238+
return result
239+
})
240+
deltas = append(deltas, Delta{newLine, Loc{0, i}, Loc{charCount, i}})
241+
}
218242
}
243+
219244
b.MultipleReplace(deltas)
220245

221-
return found, netrunes
246+
return found, util.CharacterCount(b.LineBytes(end.Y)) - charsEnd
222247
}

0 commit comments

Comments
 (0)