Skip to content

Commit

Permalink
make search region padding only one rune wide
Browse files Browse the repository at this point in the history
  • Loading branch information
matthias314 committed Feb 10, 2025
1 parent 7ab16a1 commit 88f3cf5
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 46 deletions.
62 changes: 27 additions & 35 deletions internal/buffer/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package buffer

import (
"regexp"
"unicode/utf8"

"github.com/zyedidia/micro/v2/internal/util"
)
Expand Down Expand Up @@ -32,33 +31,6 @@ func NewRegexpGroup(s string) (RegexpGroup, error) {
return rgrp, err
}

func findLineParams(b *Buffer, start, end Loc, i int) ([]byte, int, int) {
l := b.LineBytes(i)
charpos := 0
padMode := 0

if i == end.Y {
nchars := util.CharacterCount(l)
end.X = util.Clamp(end.X, 0, nchars)
if end.X < nchars {
l = util.SliceStart(l, end.X+1)
padMode |= padEnd
}
}

if i == start.Y {
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
if start.X > 0 {
charpos = start.X - 1
l = util.SliceEnd(l, charpos)
padMode |= padStart
}
}

return l, charpos, padMode
}

type bytesFind func(*regexp.Regexp, []byte) []int

func (b *Buffer) findDownFunc(rgrp RegexpGroup, start, end Loc, find bytesFind) []Loc {
Expand All @@ -77,22 +49,42 @@ func (b *Buffer) findDownFunc(rgrp RegexpGroup, start, end Loc, find bytesFind)
}

for i := start.Y; i <= end.Y; i++ {
l, charpos, padMode := findLineParams(b, start, end, i)
l := b.LineBytes(i)
from, to := 0, len(l)
padMode := 0

if i == end.Y {
nchars := util.CharacterCount(l)
end.X = util.Clamp(end.X, 0, nchars)
if end.X < nchars {
padMode |= padEnd
to = util.NextRunePos(l, util.BytePosFromCharPos(l, end.X))
}
}

if i == start.Y {
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
if start.X > 0 {
padMode |= padStart
from = util.PreviousRunePos(l, util.BytePosFromCharPos(l, start.X))
}
}

match := find(rgrp[padMode], l)
s := l[from:to]
match := find(rgrp[padMode], s)

if match != nil {
if padMode&padStart != 0 {
_, size := utf8.DecodeRune(l[match[0]:])
match[0] += size
match[0] = util.NextRunePos(s, match[0])
}
if padMode&padEnd != 0 {
_, size := utf8.DecodeLastRune(l[:match[1]])
match[1] -= size
match[1] = util.PreviousRunePos(s, match[1])
}
return util.SliceMap(match, func(pos int) Loc {
if pos >= 0 {
return Loc{charpos + util.RunePos(l, pos), i}
x := util.CharacterCount(l[:from+pos])
return Loc{x, i}
} else { // unused submatches
return Loc{-1, -1}
}
Expand Down
33 changes: 33 additions & 0 deletions internal/util/unicode.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ func isMark(r rune) bool {
return unicode.In(r, unicode.Mark)
}

// PreviousRunePos returns the position of the rune preceding the one starting
// at `i` in the given byte slice, or -1 if there is no valid rune
func PreviousRunePos(b []byte, i int) int {
r, size := utf8.DecodeLastRune(b[:i])
if r == utf8.RuneError {
return -1
} else {
return i - size
}
}

// NextRunePos returns the position of the rune following the one starting
// at `i` in the given byte slice, or -1 if there is no valid rune
func NextRunePos(b []byte, i int) int {
r, size := utf8.DecodeRune(b[i:])
if r == utf8.RuneError {
return -1
} else {
return i + size
}
}

// DecodeCharacter returns the next character from an array of bytes
// A character is a rune along with any accompanying combining runes
func DecodeCharacter(b []byte) (rune, []rune, int) {
Expand Down Expand Up @@ -94,3 +116,14 @@ func CharacterCountInString(str string) int {

return s
}

// BytePosFromCharPos returns the position of the byte in `b` that
// starts first rune of the character indexed by `ci`
func BytePosFromCharPos(b []byte, ci int) int {
i := 0
for j := 0; j < ci; j++ {
_, _, size := DecodeCharacter(b[i:])
i += size
}
return i
}
16 changes: 5 additions & 11 deletions internal/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ func SliceMap[T, V any](ts []T, f func(T) V) []V {
if ts == nil {
return nil
}
vs := make([]V, len(ts))
for i, t := range ts {
vs[i] = f(t)
}
return vs
vs := make([]V, len(ts))
for i, t := range ts {
vs[i] = f(t)
}
return vs
}

// SliceEnd returns a byte slice where the index is a rune index
Expand Down Expand Up @@ -327,12 +327,6 @@ func IsBytesWhitespace(b []byte) bool {
return true
}

// RunePos returns the rune index of a given byte index
// Make sure the byte index is not between code points
func RunePos(b []byte, i int) int {
return CharacterCount(b[:i])
}

// IndexAnyUnquoted returns the first position in s of a character from chars.
// Escaped (with backslash) and quoted (with single or double quotes) characters
// are ignored. Returns -1 if not successful
Expand Down

0 comments on commit 88f3cf5

Please sign in to comment.