jesseduffield.lazygit/vendor/github.com/gdamore/tcell/v3/cell.go
dependabot[bot] 9d128e8503
Bump github.com/gdamore/tcell/v3 from 3.4.1 to 3.4.2
Bumps [github.com/gdamore/tcell/v3](https://github.com/gdamore/tcell) from 3.4.1 to 3.4.2.
- [Release notes](https://github.com/gdamore/tcell/releases)
- [Changelog](https://github.com/gdamore/tcell/blob/main/CHANGESv3.md)
- [Commits](https://github.com/gdamore/tcell/compare/v3.4.1...v3.4.2)

---
updated-dependencies:
- dependency-name: github.com/gdamore/tcell/v3
  dependency-version: 3.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-05 14:06:21 +00:00

255 lines
7.2 KiB
Go

// Copyright 2026 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tcell
import "unicode/utf8"
type cell struct {
currStr string
lastStr string
currStyle Style
lastStyle Style
width int
lock bool
}
func (c *cell) setDirty(dirty bool) {
if dirty {
// Empty cells use currStr == "" until they are first drawn, at which
// point SetDirty(false) normalizes them to a space. Using "" as the
// dirty marker for an untouched empty cell would therefore leave
// lastStr == currStr and fail to force a redraw.
if c.currStr == "" {
c.lastStr = " "
} else {
c.lastStr = ""
}
} else {
if c.currStr == "" {
c.currStr = " "
}
c.lastStr = c.currStr
c.lastStyle = c.currStyle
}
}
// CellBuffer represents a two-dimensional array of character cells.
// This is primarily intended for use by Screen implementers; it
// contains much of the common code they need. To create one, just
// declare a variable of its type; no explicit initialization is necessary.
//
// CellBuffer is not thread safe.
type CellBuffer struct {
w int
h int
cells []cell
sanitizeContent bool
}
// Put a single styled grapheme using the given string and style
// at the same location. Note that only the first grapheme in the string
// will be displayed, using only the 1 or 2 (depending on width) cells
// located at x, y. It returns the rest of the string, and the width used.
func (cb *CellBuffer) Put(x int, y int, str string, style Style) (string, int) {
if cb.sanitizeContent {
str = stripOSCControlsIfNeeded(str)
}
return cb.put(x, y, str, style)
}
func (cb *CellBuffer) put(x int, y int, str string, style Style) (string, int) {
var width int = 0
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
var cl string
c := &cb.cells[(y*cb.w)+x]
if str == c.currStr && c.width > 0 {
// Identical re-Put (a full-screen redraw): the grapheme split is
// unchanged, so reuse the measured width instead of segmenting.
cl, width, str = str, c.width, ""
} else if len(str) > 0 && str[0] >= ' ' && str[0] <= '~' && (len(str) == 1 || str[1] < utf8.RuneSelf) {
// Printable ASCII followed by ASCII cannot be part of a larger
// grapheme cluster, so avoid constructing a grapheme iterator.
cl, width, str = str[:1], 1, str[1:]
} else {
g := textWidthOptions.StringGraphemes(str)
for width == 0 && g.Next() {
cluster := g.Value()
cl += cluster
width = g.Width()
str = str[len(cluster):]
}
}
// Wide characters: we want to mark the "wide" cells
// dirty as well as the base cell, to make sure we consider
// both cells as dirty together. We only need to do this
// if we're changing content
if width > 1 && cl != c.currStr {
// Prevent unnecessary bounds checks for first cell, since we already
// received that one.
c.setDirty(true)
for i := 1; i < width; i++ {
cb.SetDirty(x+i, y, true)
}
}
c.currStr = cl
c.width = width
if style.fg == ColorNone {
style.fg = c.currStyle.fg
}
if style.bg == ColorNone {
style.bg = c.currStyle.bg
}
c.currStyle = style
}
return str, width
}
// Get the contents of a character cell (or two adjacent cells), including the
// the style and the display width in cells. (The width can be either 1, normally,
// or 2 for East Asian full-width characters. If the width is 0, then the cell is
// is empty.)
func (cb *CellBuffer) Get(x, y int) (string, Style, int) {
var style Style
var width int
var str string
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
c := &cb.cells[(y*cb.w)+x]
str, style = c.currStr, c.currStyle
if width = c.width; width == 0 || str == "" {
width = 1
str = " "
}
}
return str, style, width
}
// Size returns the (width, height) in cells of the buffer.
func (cb *CellBuffer) Size() (int, int) {
return cb.w, cb.h
}
// Invalidate marks all characters within the buffer as dirty.
func (cb *CellBuffer) Invalidate() {
for i := range cb.cells {
cb.cells[i].setDirty(true)
}
}
// Dirty checks if a character at the given location needs to be
// refreshed on the physical display. This returns true if the cell
// content is different since the last time it was marked clean.
func (cb *CellBuffer) Dirty(x, y int) bool {
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
c := &cb.cells[(y*cb.w)+x]
if c.lock {
return false
}
if c.lastStyle != c.currStyle {
return true
}
if c.lastStr != c.currStr {
return true
}
}
return false
}
// SetDirty is normally used to indicate that a cell has
// been displayed (in which case dirty is false), or to manually
// force a cell to be marked dirty.
func (cb *CellBuffer) SetDirty(x, y int, dirty bool) {
if x >= 0 && y >= 0 && x < cb.w && y < cb.h {
c := &cb.cells[(y*cb.w)+x]
c.setDirty(dirty)
}
}
// LockCell locks a cell from being drawn, effectively marking it "clean" until
// the lock is removed. This can be used to prevent tcell from drawing a given
// cell, even if the underlying content has changed. For example, when drawing a
// sixel graphic directly to a TTY screen an implementer must lock the region
// underneath the graphic to prevent tcell from drawing on top of the graphic.
func (cb *CellBuffer) LockCell(x, y int) {
if x < 0 || y < 0 {
return
}
if x >= cb.w || y >= cb.h {
return
}
c := &cb.cells[(y*cb.w)+x]
c.lock = true
}
// UnlockCell removes a lock from the cell and marks it as dirty
func (cb *CellBuffer) UnlockCell(x, y int) {
if x < 0 || y < 0 {
return
}
if x >= cb.w || y >= cb.h {
return
}
c := &cb.cells[(y*cb.w)+x]
c.lock = false
cb.SetDirty(x, y, true)
}
// Resize is used to resize the cells array, with different dimensions,
// while preserving the original contents. The cells will be invalidated
// so that they can be redrawn.
func (cb *CellBuffer) Resize(w, h int) {
if cb.h == h && cb.w == w {
return
}
newc := make([]cell, w*h)
for y := 0; y < h && y < cb.h; y++ {
for x := 0; x < w && x < cb.w; x++ {
oc := &cb.cells[(y*cb.w)+x]
nc := &newc[(y*w)+x]
nc.currStr = oc.currStr
nc.currStyle = oc.currStyle
nc.width = oc.width
nc.lastStr = ""
}
}
cb.cells = newc
cb.h = h
cb.w = w
}
// Fill fills the entire cell buffer array with the specified character
// and style. Normally choose ' ' to clear the screen. This API doesn't
// support combining characters, or characters with a width larger than one.
// If either the foreground or background are ColorNone, then the respective
// color is unchanged.
func (cb *CellBuffer) Fill(r rune, style Style) {
for i := range cb.cells {
c := &cb.cells[i]
c.currStr = string(r)
cs := style
if cs.fg == ColorNone {
cs.fg = c.currStyle.fg
}
if cs.bg == ColorNone {
cs.bg = c.currStyle.bg
}
c.currStyle = cs
c.width = 1
}
}