mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-12 08:36:25 -04:00
The previous two commits stop gocui from flushing while suspended, but that guard cannot be fully airtight from gocui's side: it is a check-then-act on the suspended flag, so a flush racing the suspend itself (e.g. from a spinner goroutine) could still reach the screen just as it disengages, and tcell's disengageFinish mutates the cell buffer without holding the screen lock. Upstream now closes this at the source (gdamore/tcell#1139): draw() returns immediately on a disengaged screen, and the draw scan loop can no longer stall on the width-0 cells that a released cell buffer reports (#5309). The delta over the previously pinned snapshot is these two fixes, a CSI R input decode fix, a wasm packaging chore, and dependency bumps. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
377 lines
9.5 KiB
Go
377 lines
9.5 KiB
Go
// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package cases
|
|
|
|
import "golang.org/x/text/transform"
|
|
|
|
// A context is used for iterating over source bytes, fetching case info and
|
|
// writing to a destination buffer.
|
|
//
|
|
// Casing operations may need more than one rune of context to decide how a rune
|
|
// should be cased. Casing implementations should call checkpoint on context
|
|
// whenever it is known to be safe to return the runes processed so far.
|
|
//
|
|
// It is recommended for implementations to not allow for more than 30 case
|
|
// ignorables as lookahead (analogous to the limit in norm) and to use state if
|
|
// unbounded lookahead is needed for cased runes.
|
|
type context struct {
|
|
dst, src []byte
|
|
atEOF bool
|
|
|
|
pDst int // pDst points past the last written rune in dst.
|
|
pSrc int // pSrc points to the start of the currently scanned rune.
|
|
|
|
// checkpoints safe to return in Transform, where nDst <= pDst and nSrc <= pSrc.
|
|
nDst, nSrc int
|
|
err error
|
|
|
|
sz int // size of current rune
|
|
info info // case information of currently scanned rune
|
|
|
|
// State preserved across calls to Transform.
|
|
isMidWord bool // false if next cased letter needs to be title-cased.
|
|
}
|
|
|
|
func (c *context) Reset() {
|
|
c.isMidWord = false
|
|
}
|
|
|
|
// ret returns the return values for the Transform method. It checks whether
|
|
// there were insufficient bytes in src to complete and introduces an error
|
|
// accordingly, if necessary.
|
|
func (c *context) ret() (nDst, nSrc int, err error) {
|
|
if c.err != nil || c.nSrc == len(c.src) {
|
|
return c.nDst, c.nSrc, c.err
|
|
}
|
|
// This point is only reached by mappers if there was no short destination
|
|
// buffer. This means that the source buffer was exhausted and that c.sz was
|
|
// set to 0 by next.
|
|
if c.atEOF && c.pSrc == len(c.src) {
|
|
return c.pDst, c.pSrc, nil
|
|
}
|
|
return c.nDst, c.nSrc, transform.ErrShortSrc
|
|
}
|
|
|
|
// retSpan returns the return values for the Span method. It checks whether
|
|
// there were insufficient bytes in src to complete and introduces an error
|
|
// accordingly, if necessary.
|
|
func (c *context) retSpan() (n int, err error) {
|
|
_, nSrc, err := c.ret()
|
|
return nSrc, err
|
|
}
|
|
|
|
// checkpoint sets the return value buffer points for Transform to the current
|
|
// positions.
|
|
func (c *context) checkpoint() {
|
|
if c.err == nil {
|
|
c.nDst, c.nSrc = c.pDst, c.pSrc+c.sz
|
|
}
|
|
}
|
|
|
|
// unreadRune causes the last rune read by next to be reread on the next
|
|
// invocation of next. Only one unreadRune may be called after a call to next.
|
|
func (c *context) unreadRune() {
|
|
c.sz = 0
|
|
}
|
|
|
|
func (c *context) next() bool {
|
|
c.pSrc += c.sz
|
|
if c.pSrc == len(c.src) || c.err != nil {
|
|
c.info, c.sz = 0, 0
|
|
return false
|
|
}
|
|
v, sz := trie.lookup(c.src[c.pSrc:])
|
|
c.info, c.sz = info(v), sz
|
|
if c.sz == 0 {
|
|
if c.atEOF {
|
|
// A zero size means we have an incomplete rune. If we are atEOF,
|
|
// this means it is an illegal rune, which we will consume one
|
|
// byte at a time.
|
|
c.sz = 1
|
|
} else {
|
|
c.err = transform.ErrShortSrc
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// writeBytes adds bytes to dst.
|
|
func (c *context) writeBytes(b []byte) bool {
|
|
if len(c.dst)-c.pDst < len(b) {
|
|
c.err = transform.ErrShortDst
|
|
return false
|
|
}
|
|
// This loop is faster than using copy.
|
|
for _, ch := range b {
|
|
c.dst[c.pDst] = ch
|
|
c.pDst++
|
|
}
|
|
return true
|
|
}
|
|
|
|
// writeString writes the given string to dst.
|
|
func (c *context) writeString(s string) bool {
|
|
if len(c.dst)-c.pDst < len(s) {
|
|
c.err = transform.ErrShortDst
|
|
return false
|
|
}
|
|
// This loop is faster than using copy.
|
|
for i := 0; i < len(s); i++ {
|
|
c.dst[c.pDst] = s[i]
|
|
c.pDst++
|
|
}
|
|
return true
|
|
}
|
|
|
|
// copy writes the current rune to dst.
|
|
func (c *context) copy() bool {
|
|
return c.writeBytes(c.src[c.pSrc : c.pSrc+c.sz])
|
|
}
|
|
|
|
// copyXOR copies the current rune to dst and modifies it by applying the XOR
|
|
// pattern of the case info. It is the responsibility of the caller to ensure
|
|
// that this is a rune with a XOR pattern defined.
|
|
func (c *context) copyXOR() bool {
|
|
if !c.copy() {
|
|
return false
|
|
}
|
|
if c.info&xorIndexBit == 0 {
|
|
// Fast path for 6-bit XOR pattern, which covers most cases.
|
|
c.dst[c.pDst-1] ^= byte(c.info >> xorShift)
|
|
} else {
|
|
// Interpret XOR bits as an index.
|
|
// TODO: test performance for unrolling this loop. Verify that we have
|
|
// at least two bytes and at most three.
|
|
idx := c.info >> xorShift
|
|
for p := c.pDst - 1; ; p-- {
|
|
c.dst[p] ^= xorData[idx]
|
|
idx--
|
|
if xorData[idx] == 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// hasPrefix returns true if src[pSrc:] starts with the given string.
|
|
func (c *context) hasPrefix(s string) bool {
|
|
b := c.src[c.pSrc:]
|
|
if len(b) < len(s) {
|
|
return false
|
|
}
|
|
for i, c := range b[:len(s)] {
|
|
if c != s[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// caseType returns an info with only the case bits, normalized to either
|
|
// cLower, cUpper, cTitle or cUncased.
|
|
func (c *context) caseType() info {
|
|
cm := c.info & 0x7
|
|
if cm < 4 {
|
|
return cm
|
|
}
|
|
if cm >= cXORCase {
|
|
// xor the last bit of the rune with the case type bits.
|
|
b := c.src[c.pSrc+c.sz-1]
|
|
return info(b&1) ^ cm&0x3
|
|
}
|
|
if cm == cIgnorableCased {
|
|
return cLower
|
|
}
|
|
return cUncased
|
|
}
|
|
|
|
// lower writes the lowercase version of the current rune to dst.
|
|
func lower(c *context) bool {
|
|
ct := c.caseType()
|
|
if c.info&hasMappingMask == 0 || ct == cLower {
|
|
return c.copy()
|
|
}
|
|
if c.info&exceptionBit == 0 {
|
|
return c.copyXOR()
|
|
}
|
|
e := exceptions[c.info>>exceptionShift:]
|
|
offset := 2 + e[0]&lengthMask // size of header + fold string
|
|
if nLower := (e[1] >> lengthBits) & lengthMask; nLower != noChange {
|
|
return c.writeString(e[offset : offset+nLower])
|
|
}
|
|
return c.copy()
|
|
}
|
|
|
|
func isLower(c *context) bool {
|
|
ct := c.caseType()
|
|
if c.info&hasMappingMask == 0 || ct == cLower {
|
|
return true
|
|
}
|
|
if c.info&exceptionBit == 0 {
|
|
c.err = transform.ErrEndOfSpan
|
|
return false
|
|
}
|
|
e := exceptions[c.info>>exceptionShift:]
|
|
if nLower := (e[1] >> lengthBits) & lengthMask; nLower != noChange {
|
|
c.err = transform.ErrEndOfSpan
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// upper writes the uppercase version of the current rune to dst.
|
|
func upper(c *context) bool {
|
|
ct := c.caseType()
|
|
if c.info&hasMappingMask == 0 || ct == cUpper {
|
|
return c.copy()
|
|
}
|
|
if c.info&exceptionBit == 0 {
|
|
return c.copyXOR()
|
|
}
|
|
e := exceptions[c.info>>exceptionShift:]
|
|
offset := 2 + e[0]&lengthMask // size of header + fold string
|
|
// Get length of first special case mapping.
|
|
n := (e[1] >> lengthBits) & lengthMask
|
|
if ct == cTitle {
|
|
// The first special case mapping is for lower. Set n to the second.
|
|
if n == noChange {
|
|
n = 0
|
|
}
|
|
n, e = e[1]&lengthMask, e[n:]
|
|
}
|
|
if n != noChange {
|
|
return c.writeString(e[offset : offset+n])
|
|
}
|
|
return c.copy()
|
|
}
|
|
|
|
// isUpper reports whether the current rune is in upper case.
|
|
func isUpper(c *context) bool {
|
|
ct := c.caseType()
|
|
if c.info&hasMappingMask == 0 || ct == cUpper {
|
|
return true
|
|
}
|
|
if c.info&exceptionBit == 0 {
|
|
c.err = transform.ErrEndOfSpan
|
|
return false
|
|
}
|
|
e := exceptions[c.info>>exceptionShift:]
|
|
// Get length of first special case mapping.
|
|
n := (e[1] >> lengthBits) & lengthMask
|
|
if ct == cTitle {
|
|
n = e[1] & lengthMask
|
|
}
|
|
if n != noChange {
|
|
c.err = transform.ErrEndOfSpan
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// title writes the title case version of the current rune to dst.
|
|
func title(c *context) bool {
|
|
ct := c.caseType()
|
|
if c.info&hasMappingMask == 0 || ct == cTitle {
|
|
return c.copy()
|
|
}
|
|
if c.info&exceptionBit == 0 {
|
|
if ct == cLower {
|
|
return c.copyXOR()
|
|
}
|
|
return c.copy()
|
|
}
|
|
// Get the exception data.
|
|
e := exceptions[c.info>>exceptionShift:]
|
|
offset := 2 + e[0]&lengthMask // size of header + fold string
|
|
|
|
nFirst := (e[1] >> lengthBits) & lengthMask
|
|
if nTitle := e[1] & lengthMask; nTitle != noChange {
|
|
if nFirst != noChange {
|
|
e = e[nFirst:]
|
|
}
|
|
return c.writeString(e[offset : offset+nTitle])
|
|
}
|
|
if ct == cLower && nFirst != noChange {
|
|
// Use the uppercase version instead.
|
|
return c.writeString(e[offset : offset+nFirst])
|
|
}
|
|
// Already in correct case.
|
|
return c.copy()
|
|
}
|
|
|
|
// isTitle reports whether the current rune is in title case.
|
|
func isTitle(c *context) bool {
|
|
ct := c.caseType()
|
|
if c.info&hasMappingMask == 0 || ct == cTitle {
|
|
return true
|
|
}
|
|
if c.info&exceptionBit == 0 {
|
|
if ct == cLower {
|
|
c.err = transform.ErrEndOfSpan
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
// Get the exception data.
|
|
e := exceptions[c.info>>exceptionShift:]
|
|
if nTitle := e[1] & lengthMask; nTitle != noChange {
|
|
c.err = transform.ErrEndOfSpan
|
|
return false
|
|
}
|
|
nFirst := (e[1] >> lengthBits) & lengthMask
|
|
if ct == cLower && nFirst != noChange {
|
|
c.err = transform.ErrEndOfSpan
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// foldFull writes the foldFull version of the current rune to dst.
|
|
func foldFull(c *context) bool {
|
|
if c.info&hasMappingMask == 0 {
|
|
return c.copy()
|
|
}
|
|
ct := c.caseType()
|
|
if c.info&exceptionBit == 0 {
|
|
if ct != cLower || c.info&inverseFoldBit != 0 {
|
|
return c.copyXOR()
|
|
}
|
|
return c.copy()
|
|
}
|
|
e := exceptions[c.info>>exceptionShift:]
|
|
n := e[0] & lengthMask
|
|
if n == 0 {
|
|
if ct == cLower {
|
|
return c.copy()
|
|
}
|
|
n = (e[1] >> lengthBits) & lengthMask
|
|
}
|
|
return c.writeString(e[2 : 2+n])
|
|
}
|
|
|
|
// isFoldFull reports whether the current run is mapped to foldFull
|
|
func isFoldFull(c *context) bool {
|
|
if c.info&hasMappingMask == 0 {
|
|
return true
|
|
}
|
|
ct := c.caseType()
|
|
if c.info&exceptionBit == 0 {
|
|
if ct != cLower || c.info&inverseFoldBit != 0 {
|
|
c.err = transform.ErrEndOfSpan
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
e := exceptions[c.info>>exceptionShift:]
|
|
n := e[0] & lengthMask
|
|
if n == 0 && ct == cLower {
|
|
return true
|
|
}
|
|
c.err = transform.ErrEndOfSpan
|
|
return false
|
|
}
|