Skip to content

Commit 5704643

Browse files
committed
match beginning and end of line correctly in ReplaceRegex
1 parent 31a4d1c commit 5704643

1 file changed

Lines changed: 73 additions & 33 deletions

File tree

internal/buffer/search.go

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,26 @@ func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
122122
return [2]Loc{}, false
123123
}
124124

125+
func (b *Buffer) findAll(r *regexp.Regexp, start, end Loc) [][2]Loc {
126+
matches := [][2]Loc{}
127+
loc := start
128+
for {
129+
match, found := b.findDown(r, loc, end)
130+
if !found {
131+
break
132+
}
133+
matches = append(matches, match)
134+
if match[0] != match[1] {
135+
loc = match[1]
136+
} else if match[1] != end {
137+
loc = match[1].Move(1, b)
138+
} else {
139+
break
140+
}
141+
}
142+
return matches
143+
}
144+
125145
// FindNext finds the next occurrence of a given string in the buffer
126146
// It returns the start and end location of the match (if found) and
127147
// a boolean indicating if it was found
@@ -165,53 +185,73 @@ func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bo
165185
}
166186

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

175-
netrunes := 0
176-
195+
charsEnd := util.CharacterCount(b.LineBytes(end.Y))
177196
found := 0
178197
var deltas []Delta
179-
for i := start.Y; i <= end.Y; i++ {
180-
l := b.lines[i].data
181-
charpos := 0
182-
183-
if start.Y == end.Y && i == start.Y {
184-
l = util.SliceStart(l, end.X)
185-
l = util.SliceEnd(l, start.X)
186-
charpos = start.X
187-
} else if i == start.Y {
188-
l = util.SliceEnd(l, start.X)
189-
charpos = start.X
190-
} else if i == end.Y {
191-
l = util.SliceStart(l, end.X)
192-
}
193-
newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
194-
var result []byte
198+
199+
// This replacement function works in general, but it creates a separate
200+
// modification for each match. We only use it for the first and last lines,
201+
// which may use padded regexps
202+
replaceFirstLast := func(start, end Loc) []Delta {
203+
matches := b.findAll(search, start, end)
204+
for j := len(matches) - 1; j >= 0; j-- {
205+
// if we counted upwards, the different deltas would interfere
206+
match := matches[j]
207+
var newText []byte
195208
if captureGroups {
196-
for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
197-
result = search.Expand(result, replace, in, submatches)
198-
}
209+
newText = search.ReplaceAll(b.Substr(match[0], match[1]), replace)
199210
} else {
200-
result = replace
211+
newText = replace
201212
}
202-
found++
203-
if i == end.Y {
204-
netrunes += util.CharacterCount(result) - util.CharacterCount(in)
205-
}
206-
return result
207-
})
213+
deltas = append(deltas, Delta{newText, match[0], match[1]})
214+
}
215+
found += len(matches)
216+
return deltas
217+
}
218+
219+
replaceMiddle := func(in []byte) []byte {
220+
found++
221+
var result []byte
222+
if captureGroups {
223+
match := search.FindSubmatchIndex(in)
224+
result = search.Expand(result, replace, in, match)
225+
} else {
226+
result = replace
227+
}
228+
return result
229+
}
208230

209-
from := Loc{charpos, i}
210-
to := Loc{charpos + util.CharacterCount(l), i}
231+
// first line (if different from last line)
232+
if start.Y < end.Y {
233+
n := util.CharacterCount(b.LineBytes(start.Y))
234+
startEnd := Loc{n, start.Y}
235+
deltas = replaceFirstLast(start, startEnd)
236+
}
211237

212-
deltas = append(deltas, Delta{newText, from, to})
238+
// middle lines
239+
for i := start.Y + 1; i < end.Y; i++ {
240+
l := b.LineBytes(i)
241+
n := util.CharacterCount(l)
242+
newLine := search.ReplaceAllFunc(l, replaceMiddle)
243+
deltas = append(deltas, Delta{newLine, Loc{0, i}, Loc{n, i}})
213244
}
245+
246+
// last line
247+
endStart := Loc{0, end.Y}
248+
if start.Y == end.Y {
249+
endStart = start
250+
}
251+
deltas = replaceFirstLast(endStart, end)
252+
214253
b.MultipleReplace(deltas)
215254

216-
return found, netrunes
255+
deltaEndX := util.CharacterCount(b.LineBytes(end.Y)) - charsEnd
256+
return found, deltaEndX
217257
}

0 commit comments

Comments
 (0)