made search and replace functions accept RegexpGroup argument

This commit is contained in:
matthias314 2025-03-04 16:11:10 -05:00
parent 82e77d0e9a
commit 8683a1abe8
2 changed files with 88 additions and 72 deletions

View File

@ -974,7 +974,7 @@ func (h *BufPane) ReplaceCmd(args []string) {
lastMatchEnd := buffer.LocVoid() lastMatchEnd := buffer.LocVoid()
var doReplacement func() var doReplacement func()
doReplacement = func() { doReplacement = func() {
locs := h.Buf.FindDown(rgrp, searchLoc, end) locs, _ := h.Buf.FindDown(rgrp, searchLoc, end)
if locs == nil || !inRange(locs[0]) || !inRange(locs[1]) { if locs == nil || !inRange(locs[0]) || !inRange(locs[1]) {
h.Cursor.ResetSelection() h.Cursor.ResetSelection()
h.Buf.RelocateCursors() h.Buf.RelocateCursors()

View File

@ -1,6 +1,7 @@
package buffer package buffer
import ( import (
"fmt"
"regexp" "regexp"
"unicode/utf8" "unicode/utf8"
@ -32,9 +33,25 @@ func NewRegexpGroup(s string) (RegexpGroup, error) {
return rgrp, err return rgrp, err
} }
func regexpGroup(re any) (RegexpGroup, error) {
switch re := re.(type) {
case RegexpGroup:
return re, nil
case string:
return NewRegexpGroup(re)
default:
return RegexpGroup{}, fmt.Errorf(`cannot convert "%v" (of type %[1]T) to type RegexpGroup`, re)
}
}
type bytesFind func(*regexp.Regexp, []byte) []int type bytesFind func(*regexp.Regexp, []byte) []int
func (b *Buffer) findDownFunc(rgrp RegexpGroup, start, end Loc, find bytesFind) []Loc { func (b *Buffer) findDownFunc(re any, start, end Loc, find bytesFind) ([]Loc, error) {
rgrp, err := regexpGroup(re)
if err != nil {
return nil, err
}
lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1)) lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
if start.Y > b.LinesNum()-1 { if start.Y > b.LinesNum()-1 {
start.X = lastcn - 1 start.X = lastcn - 1
@ -95,30 +112,35 @@ func (b *Buffer) findDownFunc(rgrp RegexpGroup, start, end Loc, find bytesFind)
} else { // start or end of unused submatch } else { // start or end of unused submatch
return LocVoid() return LocVoid()
} }
}) }), nil
} }
} }
return nil return nil, nil
} }
type bufferFind func(*Buffer, RegexpGroup, Loc, Loc) []Loc type bufferFind func(*Buffer, any, Loc, Loc) ([]Loc, error)
// FindDown returns a slice containing the start and end positions // FindDown returns a slice containing the start and end positions
// of the first match of `rgrp` between `start` and `end`, or nil // of the first match of `re` between `start` and `end`, or nil
// if no match exists. // if no match exists.
func (b *Buffer) FindDown(rgrp RegexpGroup, start, end Loc) []Loc { func (b *Buffer) FindDown(re any, start, end Loc) ([]Loc, error) {
return b.findDownFunc(rgrp, start, end, (*regexp.Regexp).FindIndex) return b.findDownFunc(re, start, end, (*regexp.Regexp).FindIndex)
} }
// FindDownSubmatch returns a slice containing the start and end positions // FindDownSubmatch returns a slice containing the start and end positions
// of the first match of `rgrp` between `start` and `end` plus those // of the first match of `re` between `start` and `end` plus those
// of all submatches (capturing groups), or nil if no match exists. // of all submatches (capturing groups), or nil if no match exists.
// The start and end positions of an unused submatch are void. // The start and end positions of an unused submatch are void.
func (b *Buffer) FindDownSubmatch(rgrp RegexpGroup, start, end Loc) []Loc { func (b *Buffer) FindDownSubmatch(re any, start, end Loc) ([]Loc, error) {
return b.findDownFunc(rgrp, start, end, (*regexp.Regexp).FindSubmatchIndex) return b.findDownFunc(re, start, end, (*regexp.Regexp).FindSubmatchIndex)
} }
func (b *Buffer) findUpFunc(rgrp RegexpGroup, start, end Loc, find bytesFind) []Loc { func (b *Buffer) findUpFunc(re any, start, end Loc, find bytesFind) ([]Loc, error) {
rgrp, err := regexpGroup(re)
if err != nil {
return nil, err
}
lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1)) lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
if start.Y > b.LinesNum()-1 { if start.Y > b.LinesNum()-1 {
start.X = lastcn - 1 start.X = lastcn - 1
@ -139,24 +161,24 @@ func (b *Buffer) findUpFunc(rgrp RegexpGroup, start, end Loc, find bytesFind) []
from := Loc{0, i}.Clamp(start, end) from := Loc{0, i}.Clamp(start, end)
to := Loc{charCount, i}.Clamp(start, end) to := Loc{charCount, i}.Clamp(start, end)
b.findAllFuncFunc(rgrp, from, to, func(b *Buffer, rgrp RegexpGroup, start, end Loc) []Loc { b.findAllFuncFunc(rgrp, from, to, func(b *Buffer, re any, start, end Loc) ([]Loc, error) {
return b.findDownFunc(rgrp, start, end, find) return b.findDownFunc(rgrp, start, end, find)
}, func(match []Loc) { }, func(match []Loc) {
locs = match locs = match
}) })
if locs != nil { if locs != nil {
return locs return locs, nil
} }
} }
return nil return nil, nil
} }
// FindUp returns a slice containing the start and end positions // FindUp returns a slice containing the start and end positions
// of the last match of `rgrp` between `start` and `end`, or nil // of the last match of `re` between `start` and `end`, or nil
// if no match exists. // if no match exists.
func (b *Buffer) FindUp(rgrp RegexpGroup, start, end Loc) []Loc { func (b *Buffer) FindUp(re any, start, end Loc) ([]Loc, error) {
return b.findUpFunc(rgrp, start, end, func(re *regexp.Regexp, l []byte) []int { return b.findUpFunc(re, start, end, func(re *regexp.Regexp, l []byte) []int {
allMatches := re.FindAllIndex(l, -1) allMatches := re.FindAllIndex(l, -1)
if allMatches != nil { if allMatches != nil {
return allMatches[len(allMatches)-1] return allMatches[len(allMatches)-1]
@ -167,12 +189,12 @@ func (b *Buffer) FindUp(rgrp RegexpGroup, start, end Loc) []Loc {
} }
// FindUpSubmatch returns a slice containing the start and end positions // FindUpSubmatch returns a slice containing the start and end positions
// of the last match of `rgrp` between `start` and `end` plus those // of the last match of `re` between `start` and `end` plus those
// of all submatches (capturing groups), or nil if no match exists. // of all submatches (capturing groups), or nil if no match exists.
// The start and end positions of an unused submatch are void. // The start and end positions of an unused submatch are void.
func (b *Buffer) FindUpSubmatch(rgrp RegexpGroup, start, end Loc) []Loc { func (b *Buffer) FindUpSubmatch(re any, start, end Loc) ([]Loc, error) {
return b.findUpFunc(rgrp, start, end, func(re *regexp.Regexp, l []byte) []int { return b.findUpFunc(re, start, end, func(r *regexp.Regexp, l []byte) []int {
allMatches := re.FindAllSubmatchIndex(l, -1) allMatches := r.FindAllSubmatchIndex(l, -1)
if allMatches != nil { if allMatches != nil {
return allMatches[len(allMatches)-1] return allMatches[len(allMatches)-1]
} else { } else {
@ -181,11 +203,15 @@ func (b *Buffer) FindUpSubmatch(rgrp RegexpGroup, start, end Loc) []Loc {
}) })
} }
func (b *Buffer) findAllFuncFunc(r RegexpGroup, start, end Loc, find bufferFind, f func([]Loc)) int { func (b *Buffer) findAllFuncFunc(re any, start, end Loc, find bufferFind, f func([]Loc)) (int, error) {
rgrp, err := regexpGroup(re)
if err != nil {
return -1, err
}
n := 0 n := 0
loc := start loc := start
for { for {
match := find(b, r, loc, end) match, _ := find(b, rgrp, loc, end)
if match == nil { if match == nil {
break break
} }
@ -199,55 +225,45 @@ func (b *Buffer) findAllFuncFunc(r RegexpGroup, start, end Loc, find bufferFind,
break break
} }
} }
return n return n, nil
} }
// FindAllFunc calls the function `f` once for each match between `start` // FindAllFunc calls the function `f` once for each match between `start`
// and `end` of the regexp given by `s`. The argument of `f` is the slice // and `end` of the regexp given by `re`. The argument of `f` is the slice
// containing the start and end positions of the match. FindAllFunc returns // containing the start and end positions of the match. FindAllFunc returns
// the number of matches plus any error that occured when compiling the regexp. // the number of matches plus any error that occured when compiling the regexp.
func (b *Buffer) FindAllFunc(s string, start, end Loc, f func([]Loc)) (int, error) { func (b *Buffer) FindAllFunc(re any, start, end Loc, f func([]Loc)) (int, error) {
rgrp, err := NewRegexpGroup(s) return b.findAllFuncFunc(re, start, end, (*Buffer).FindDown, f)
if err == nil {
return b.findAllFuncFunc(rgrp, start, end, (*Buffer).FindDown, f), nil
} else {
return -1, err
}
} }
// FindAll returns a slice containing the start and end positions of all // FindAll returns a slice containing the start and end positions of all
// matches between `start` and `end` of the regexp given by `s`, plus any // matches between `start` and `end` of the regexp given by `re`, plus any
// error that occured when compiling the regexp. If no match is found, the // error that occured when compiling the regexp. If no match is found, the
// slice returned is nil. // slice returned is nil.
func (b *Buffer) FindAll(s string, start, end Loc) ([][]Loc, error) { func (b *Buffer) FindAll(re any, start, end Loc) ([][]Loc, error) {
var matches [][]Loc var matches [][]Loc
_, err := b.FindAllFunc(s, start, end, func(match []Loc) { _, err := b.FindAllFunc(re, start, end, func(match []Loc) {
matches = append(matches, match) matches = append(matches, match)
}) })
return matches, err return matches, err
} }
// FindAllSubmatchFunc calls the function `f` once for each match between // FindAllSubmatchFunc calls the function `f` once for each match between
// `start` and `end` of the regexp given by `s`. The argument of `f` is the // `start` and `end` of the regexp given by `re`. The argument of `f` is the
// slice containing the start and end positions of the match and all submatches // slice containing the start and end positions of the match and all submatches
// (capturing groups). FindAllSubmatch Func returns the number of matches plus // (capturing groups). FindAllSubmatch Func returns the number of matches plus
// any error that occured when compiling the regexp. // any error that occured when compiling the regexp.
func (b *Buffer) FindAllSubmatchFunc(s string, start, end Loc, f func([]Loc)) (int, error) { func (b *Buffer) FindAllSubmatchFunc(re any, start, end Loc, f func([]Loc)) (int, error) {
rgrp, err := NewRegexpGroup(s) return b.findAllFuncFunc(re, start, end, (*Buffer).FindDownSubmatch, f)
if err == nil {
return b.findAllFuncFunc(rgrp, start, end, (*Buffer).FindDownSubmatch, f), nil
} else {
return -1, err
}
} }
// FindAllSubmatch returns a slice containing the start and end positions of // FindAllSubmatch returns a slice containing the start and end positions of
// all matches and all submatches (capturing groups) between `start` and `end` // all matches and all submatches (capturing groups) between `start` and `end`
// of the regexp given by `s`, plus any error that occured when compiling // of the regexp given by `re`, plus any error that occured when compiling
// the regexp. If no match is found, the slice returned is nil. // the regexp. If no match is found, the slice returned is nil.
func (b *Buffer) FindAllSubmatch(s string, start, end Loc) ([][]Loc, error) { func (b *Buffer) FindAllSubmatch(re any, start, end Loc) ([][]Loc, error) {
var matches [][]Loc var matches [][]Loc
_, err := b.FindAllSubmatchFunc(s, start, end, func(match []Loc) { _, err := b.FindAllSubmatchFunc(re, start, end, func(match []Loc) {
matches = append(matches, match) matches = append(matches, match)
}) })
return matches, err return matches, err
@ -290,14 +306,14 @@ func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bo
var match []Loc var match []Loc
if down { if down {
match = b.FindDown(rgrp, from, end) match, _ = b.FindDown(rgrp, from, end)
if match == nil { if match == nil {
match = b.FindDown(rgrp, start, end) match, _ = b.FindDown(rgrp, start, end)
} }
} else { } else {
match = b.FindUp(rgrp, from, start) match, _ = b.FindUp(rgrp, from, start)
if match == nil { if match == nil {
match = b.FindUp(rgrp, end, start) match, _ = b.FindUp(rgrp, end, start)
} }
} }
if match != nil { if match != nil {
@ -307,33 +323,33 @@ func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bo
} }
} }
func (b *Buffer) replaceAllFuncFunc(s string, start, end Loc, find bufferFind, repl func(match []Loc) []byte) (int, Loc, error) { func (b *Buffer) replaceAllFuncFunc(re any, start, end Loc, find bufferFind, repl func(match []Loc) []byte) (int, Loc, error) {
rgrp, err := NewRegexpGroup(s) charsEnd := util.CharacterCount(b.LineBytes(end.Y))
var deltas []Delta
n, err := b.findAllFuncFunc(re, start, end, find, func(match []Loc) {
deltas = append(deltas, Delta{repl(match), match[0], match[1]})
})
if err != nil { if err != nil {
return -1, LocVoid(), err return -1, LocVoid(), err
} }
charsEnd := util.CharacterCount(b.LineBytes(end.Y))
var deltas []Delta
n := b.findAllFuncFunc(rgrp, start, end, find, func(match []Loc) {
deltas = append(deltas, Delta{repl(match), match[0], match[1]})
})
b.MultipleReplace(deltas) b.MultipleReplace(deltas)
deltaX := util.CharacterCount(b.LineBytes(end.Y)) - charsEnd deltaX := util.CharacterCount(b.LineBytes(end.Y)) - charsEnd
return n, Loc{end.X + deltaX, end.Y}, nil return n, Loc{end.X + deltaX, end.Y}, nil
} }
// ReplaceAll replaces all matches of the regexp `s` in the given area. The // ReplaceAll replaces all matches of the regexp `re` in the given area. The
// new text is obtained from `template` by replacing each variable with the // new text is obtained from `template` by replacing each variable with the
// corresponding submatch as in `Regexp.Expand`. The function returns the // corresponding submatch as in `Regexp.Expand`. The function returns the
// number of replacements made, the new end position and any error that // number of replacements made, the new end position and any error that
// occured during regexp compilation // occured during regexp compilation
func (b *Buffer) ReplaceAll(s string, start, end Loc, template []byte) (int, Loc, error) { func (b *Buffer) ReplaceAll(re any, start, end Loc, template []byte) (int, Loc, error) {
var replace []byte var replace []byte
find := func(b *Buffer, r RegexpGroup, start, end Loc) []Loc { find := func(b *Buffer, r any, start, end Loc) ([]Loc, error) {
return b.findDownFunc(r, start, end, func(re *regexp.Regexp, l []byte) []int { return b.findDownFunc(r, start, end, func(re *regexp.Regexp, l []byte) []int {
match := re.FindSubmatchIndex(l) match := re.FindSubmatchIndex(l)
if match == nil { if match == nil {
@ -344,34 +360,34 @@ func (b *Buffer) ReplaceAll(s string, start, end Loc, template []byte) (int, Loc
}) })
} }
return b.replaceAllFuncFunc(s, start, end, find, func(match []Loc) []byte { return b.replaceAllFuncFunc(re, start, end, find, func(match []Loc) []byte {
return replace return replace
}) })
} }
// ReplaceAllLiteral replaces all matches of the regexp `s` with `repl` in // ReplaceAllLiteral replaces all matches of the regexp `re` with `repl` in
// the given area. The function returns the number of replacements made, the // the given area. The function returns the number of replacements made, the
// new end position and any error that occured during regexp compilation // new end position and any error that occured during regexp compilation
func (b *Buffer) ReplaceAllLiteral(s string, start, end Loc, repl []byte) (int, Loc, error) { func (b *Buffer) ReplaceAllLiteral(re any, start, end Loc, repl []byte) (int, Loc, error) {
return b.ReplaceAllFunc(s, start, end, func([]Loc) []byte { return b.ReplaceAllFunc(re, start, end, func([]Loc) []byte {
return repl return repl
}) })
} }
// ReplaceAllFunc replaces all matches of the regexp `s` with `repl(match)` // ReplaceAllFunc replaces all matches of the regexp `re` with `repl(match)`
// in the given area, where `match` is the slice containing start and end // in the given area, where `match` is the slice containing start and end
// positions of the match. The function returns the number of replacements // positions of the match. The function returns the number of replacements
// made, the new end position and any error that occured during regexp // made, the new end position and any error that occured during regexp
// compilation // compilation
func (b *Buffer) ReplaceAllFunc(s string, start, end Loc, repl func(match []Loc) []byte) (int, Loc, error) { func (b *Buffer) ReplaceAllFunc(re any, start, end Loc, repl func(match []Loc) []byte) (int, Loc, error) {
return b.replaceAllFuncFunc(s, start, end, (*Buffer).FindDown, repl) return b.replaceAllFuncFunc(re, start, end, (*Buffer).FindDown, repl)
} }
// ReplaceAllSubmatchFunc replaces all matches of the regexp `s` with // ReplaceAllSubmatchFunc replaces all matches of the regexp `re` with
// `repl(match)` in the given area, where `match` is the slice containing // `repl(match)` in the given area, where `match` is the slice containing
// start and end positions of the match and all submatches. The function // start and end positions of the match and all submatches. The function
// returns the number of replacements made, the new end position and any // returns the number of replacements made, the new end position and any
// error that occured during regexp compilation // error that occured during regexp compilation
func (b *Buffer) ReplaceAllSubmatchFunc(s string, start, end Loc, repl func(match []Loc) []byte) (int, Loc, error) { func (b *Buffer) ReplaceAllSubmatchFunc(re any, start, end Loc, repl func(match []Loc) []byte) (int, Loc, error) {
return b.replaceAllFuncFunc(s, start, end, (*Buffer).FindDownSubmatch, repl) return b.replaceAllFuncFunc(re, start, end, (*Buffer).FindDownSubmatch, repl)
} }