diff --git a/internal/buffer/loc.go b/internal/buffer/loc.go index 44f59c788..2ba0868fe 100644 --- a/internal/buffer/loc.go +++ b/internal/buffer/loc.go @@ -47,6 +47,16 @@ func (l Loc) LessEqual(b Loc) bool { return l == b } +// Clamp clamps a loc between start and end +func (l Loc) Clamp(start, end Loc) Loc { + if l.GreaterThan(end) { + return end + } else if l.LessThan(start) { + return start + } + return l +} + // The following functions require a buffer to know where newlines are // Diff returns the distance between two locations @@ -139,10 +149,5 @@ func ByteOffset(pos Loc, buf *Buffer) int { // clamps a loc within a buffer func clamp(pos Loc, la *LineArray) Loc { - if pos.GreaterEqual(la.End()) { - return la.End() - } else if pos.LessThan(la.Start()) { - return la.Start() - } - return pos + return pos.Clamp(la.Start(), la.End()) } diff --git a/internal/buffer/search.go b/internal/buffer/search.go index 4ec5d9961..b83827878 100644 --- a/internal/buffer/search.go +++ b/internal/buffer/search.go @@ -194,64 +194,50 @@ func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []b charsEnd := util.CharacterCount(b.LineBytes(end.Y)) found := 0 + var charCount int var deltas []Delta - // This replacement function works in general, but it creates a separate - // modification for each match. We only use it for the first and last lines, - // which may use padded regexps - replaceFirstLast := func(start, end Loc) []Delta { - matches := b.findAll(search, start, end) - for j := len(matches) - 1; j >= 0; j-- { - // if we counted upwards, the different deltas would interfere - match := matches[j] - var newText []byte - if captureGroups { - newText = search.ReplaceAll(b.Substr(match[0], match[1]), replace) - } else { - newText = replace + for i := start.Y; i <= end.Y; i++ { + l := b.LineBytes(i) + charCount = util.CharacterCount(l) + if i == start.Y || i == end.Y { + // This replacement code works in general, but it creates a separate + // modification for each match. We only use it for the first and last + // lines, which may use padded regexps + + from := Loc{0, i}.Clamp(start, end) + to := Loc{charCount, i}.Clamp(start, end) + matches := b.findAll(search, from, to) + found += len(matches) + + for j := len(matches) - 1; j >= 0; j-- { + // if we counted upwards, the different deltas would interfere + match := matches[j] + var newText []byte + if captureGroups { + newText = search.ReplaceAll(b.Substr(match[0], match[1]), replace) + } else { + newText = replace + } + deltas = append(deltas, Delta{newText, match[0], match[1]}) } - deltas = append(deltas, Delta{newText, match[0], match[1]}) - } - found += len(matches) - return deltas - } - - replaceMiddle := func(in []byte) []byte { - found++ - var result []byte - if captureGroups { - match := search.FindSubmatchIndex(in) - result = search.Expand(result, replace, in, match) } else { - result = replace + newLine := search.ReplaceAllFunc(l, func(in []byte) []byte { + found++ + var result []byte + if captureGroups { + match := search.FindSubmatchIndex(in) + result = search.Expand(result, replace, in, match) + } else { + result = replace + } + return result + }) + deltas = append(deltas, Delta{newLine, Loc{0, i}, Loc{charCount, i}}) } - return result - } - - // first line (if different from last line) - if start.Y < end.Y { - n := util.CharacterCount(b.LineBytes(start.Y)) - startEnd := Loc{n, start.Y} - deltas = replaceFirstLast(start, startEnd) - } - - // middle lines - for i := start.Y + 1; i < end.Y; i++ { - l := b.LineBytes(i) - n := util.CharacterCount(l) - newLine := search.ReplaceAllFunc(l, replaceMiddle) - deltas = append(deltas, Delta{newLine, Loc{0, i}, Loc{n, i}}) - } - - // last line - endStart := Loc{0, end.Y} - if start.Y == end.Y { - endStart = start } - deltas = replaceFirstLast(endStart, end) b.MultipleReplace(deltas) - deltaEndX := util.CharacterCount(b.LineBytes(end.Y)) - charsEnd - return found, deltaEndX + return found, charCount - charsEnd }