mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Make the buffer-writing methods operate on a viewBuffer
write, writeCells, makeWriteable, parseInput and autoRenderHyperlinksInCurrentLine produced cells into v.buf; move them onto viewBuffer so they can write into any buffer, not just the displayed one. The display-side effects that don't belong to content production — tainting, clearing hover, updating search positions — stay behind in the View.write wrapper, which delegates the actual writing to v.buf.write(v). Render config the writer needs (Editable, colors, width, tab width, hyperlink auto-render) is read from the passed View. Behaviour-preserving: the wrapper still always targets v.buf. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
64e3d3e049
commit
0d7d16df19
|
|
@ -828,56 +828,56 @@ func (v *View) ReadPos() (x, y int) {
|
|||
}
|
||||
|
||||
// makeWriteable creates empty cells if required to make position (x, y) writeable.
|
||||
func (v *View) makeWriteable(x, y int) {
|
||||
func (b *viewBuffer) makeWriteable(x, y int) {
|
||||
// TODO: make this more efficient
|
||||
|
||||
// line `y` must be index-able (that's why `<=`)
|
||||
for len(v.buf.lines) <= y {
|
||||
if cap(v.buf.lines) > len(v.buf.lines) {
|
||||
newLen := cap(v.buf.lines)
|
||||
for len(b.lines) <= y {
|
||||
if cap(b.lines) > len(b.lines) {
|
||||
newLen := cap(b.lines)
|
||||
if newLen > y {
|
||||
newLen = y + 1
|
||||
}
|
||||
v.buf.lines = v.buf.lines[:newLen]
|
||||
b.lines = b.lines[:newLen]
|
||||
} else {
|
||||
v.buf.lines = append(v.buf.lines, lineType{})
|
||||
b.lines = append(b.lines, lineType{})
|
||||
}
|
||||
}
|
||||
// cell `x` need not be index-able (that's why `<`)
|
||||
// append should be used by `lines[y]` user if he wants to write beyond `x`
|
||||
for len(v.buf.lines[y].cells) < x {
|
||||
if cap(v.buf.lines[y].cells) > len(v.buf.lines[y].cells) {
|
||||
newLen := cap(v.buf.lines[y].cells)
|
||||
for len(b.lines[y].cells) < x {
|
||||
if cap(b.lines[y].cells) > len(b.lines[y].cells) {
|
||||
newLen := cap(b.lines[y].cells)
|
||||
if newLen > x {
|
||||
newLen = x
|
||||
}
|
||||
v.buf.lines[y].cells = v.buf.lines[y].cells[:newLen]
|
||||
b.lines[y].cells = b.lines[y].cells[:newLen]
|
||||
} else {
|
||||
v.buf.lines[y].cells = append(v.buf.lines[y].cells, cell{})
|
||||
b.lines[y].cells = append(b.lines[y].cells, cell{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// writeCells copies []cell to (v.buf.wx, v.buf.wy), and advances v.buf.wx accordingly.
|
||||
// writeCells copies []cell to (b.wx, b.wy), and advances b.wx accordingly.
|
||||
// !!! caller MUST ensure that specified location (x, y) is writeable by calling makeWriteable
|
||||
func (v *View) writeCells(cells []cell) {
|
||||
func (b *viewBuffer) writeCells(cells []cell) {
|
||||
var newLen int
|
||||
// use maximum len available
|
||||
line := v.buf.lines[v.buf.wy].cells[:cap(v.buf.lines[v.buf.wy].cells)]
|
||||
maxCopy := len(line) - v.buf.wx
|
||||
line := b.lines[b.wy].cells[:cap(b.lines[b.wy].cells)]
|
||||
maxCopy := len(line) - b.wx
|
||||
if maxCopy < len(cells) {
|
||||
copy(line[v.buf.wx:], cells[:maxCopy])
|
||||
copy(line[b.wx:], cells[:maxCopy])
|
||||
line = append(line, cells[maxCopy:]...)
|
||||
newLen = len(line)
|
||||
} else { // maxCopy >= len(cells)
|
||||
copy(line[v.buf.wx:], cells)
|
||||
newLen = v.buf.wx + len(cells)
|
||||
if newLen < len(v.buf.lines[v.buf.wy].cells) {
|
||||
newLen = len(v.buf.lines[v.buf.wy].cells)
|
||||
copy(line[b.wx:], cells)
|
||||
newLen = b.wx + len(cells)
|
||||
if newLen < len(b.lines[b.wy].cells) {
|
||||
newLen = len(b.lines[b.wy].cells)
|
||||
}
|
||||
}
|
||||
v.buf.lines[v.buf.wy].cells = line[:newLen]
|
||||
v.buf.wx += len(cells)
|
||||
b.lines[b.wy].cells = line[:newLen]
|
||||
b.wx += len(cells)
|
||||
}
|
||||
|
||||
// Write appends a byte slice into the view's internal buffer. Because
|
||||
|
|
@ -900,34 +900,44 @@ func (v *View) write(p []byte) {
|
|||
v.firstDirtyLine = min(v.firstDirtyLine, v.buf.wy)
|
||||
v.clearHover()
|
||||
|
||||
v.buf.write(v, p)
|
||||
|
||||
v.updateSearchPositions()
|
||||
}
|
||||
|
||||
// write parses p into cells and appends them to the buffer at its write cursor.
|
||||
// It only touches the buffer; the View wrapper above handles display-side
|
||||
// effects (tainting, hover, search). v supplies render config (Editable, colors,
|
||||
// width, tab width, hyperlink auto-rendering).
|
||||
func (b *viewBuffer) write(v *View, p []byte) {
|
||||
// Fill with empty cells, if writing outside current view buffer
|
||||
v.makeWriteable(v.buf.wx, v.buf.wy)
|
||||
b.makeWriteable(b.wx, b.wy)
|
||||
|
||||
finishLine := func() {
|
||||
v.autoRenderHyperlinksInCurrentLine()
|
||||
b.autoRenderHyperlinksInCurrentLine(v)
|
||||
}
|
||||
|
||||
advanceToNextLine := func() {
|
||||
v.buf.wx = 0
|
||||
v.buf.wy++
|
||||
if v.buf.wy >= len(v.buf.lines) {
|
||||
v.buf.lines = append(v.buf.lines, lineType{})
|
||||
b.wx = 0
|
||||
b.wy++
|
||||
if b.wy >= len(b.lines) {
|
||||
b.lines = append(b.lines, lineType{})
|
||||
}
|
||||
// An OSC 456 diff-metadata sequence applies only to the line it prefixes
|
||||
// (the pager re-emits one per line and never closes it), so drop it at the
|
||||
// line boundary rather than letting it carry onto a line with no metadata.
|
||||
v.buf.ei.metadata.Reset()
|
||||
b.ei.metadata.Reset()
|
||||
}
|
||||
|
||||
if v.buf.pendingNewline {
|
||||
if b.pendingNewline {
|
||||
advanceToNextLine()
|
||||
v.buf.ei.notifyRowAdvance()
|
||||
v.buf.pendingNewline = false
|
||||
b.ei.notifyRowAdvance()
|
||||
b.pendingNewline = false
|
||||
}
|
||||
|
||||
until := len(p)
|
||||
if !v.Editable && until > 0 && p[until-1] == '\n' {
|
||||
v.buf.pendingNewline = true
|
||||
b.pendingNewline = true
|
||||
until--
|
||||
}
|
||||
|
||||
|
|
@ -943,26 +953,26 @@ func (v *View) write(p []byte) {
|
|||
case characterEquals(chr, '\n') || isCRLF(chr):
|
||||
finishLine()
|
||||
advanceToNextLine()
|
||||
v.buf.ei.notifyRowAdvance()
|
||||
b.ei.notifyRowAdvance()
|
||||
case characterEquals(chr, '\r'):
|
||||
finishLine()
|
||||
v.buf.wx = 0
|
||||
v.buf.ei.notifyColumnReset()
|
||||
b.wx = 0
|
||||
b.ei.notifyColumnReset()
|
||||
default:
|
||||
truncateLine, cells := v.parseInput(chr, width, v.buf.wx, v.buf.wy)
|
||||
if cd, ok := v.buf.ei.instruction.(cursorDown); ok {
|
||||
v.buf.ei.instructionRead()
|
||||
truncateLine, cells := b.parseInput(v, chr, width, b.wx, b.wy)
|
||||
if cd, ok := b.ei.instruction.(cursorDown); ok {
|
||||
b.ei.instructionRead()
|
||||
for range cd.n {
|
||||
v.autoRenderHyperlinksInCurrentLine()
|
||||
b.autoRenderHyperlinksInCurrentLine(v)
|
||||
advanceToNextLine()
|
||||
}
|
||||
}
|
||||
if cells == nil {
|
||||
continue
|
||||
}
|
||||
v.writeCells(cells)
|
||||
b.writeCells(cells)
|
||||
if truncateLine {
|
||||
v.buf.lines[v.buf.wy].cells = v.buf.lines[v.buf.wy].cells[:v.buf.wx]
|
||||
b.lines[b.wy].cells = b.lines[b.wy].cells[:b.wx]
|
||||
}
|
||||
// Soft-wrap tracking. truncateLine is true exactly when the
|
||||
// cells are from \x1b[K filling to end of line — ConPTY
|
||||
|
|
@ -973,18 +983,16 @@ func (v *View) write(p []byte) {
|
|||
for _, c := range cells {
|
||||
totalWidth += c.width
|
||||
}
|
||||
v.buf.ei.notifyCellsWritten(totalWidth)
|
||||
b.ei.notifyCellsWritten(totalWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v.buf.pendingNewline {
|
||||
if b.pendingNewline {
|
||||
finishLine()
|
||||
} else {
|
||||
v.autoRenderHyperlinksInCurrentLine()
|
||||
b.autoRenderHyperlinksInCurrentLine(v)
|
||||
}
|
||||
|
||||
v.updateSearchPositions()
|
||||
}
|
||||
|
||||
// exported functions use the mutex. Non-exported functions are for internal use
|
||||
|
|
@ -1027,12 +1035,12 @@ var lineEndCharacters = map[string]bool{
|
|||
")": true,
|
||||
}
|
||||
|
||||
func (v *View) autoRenderHyperlinksInCurrentLine() {
|
||||
func (b *viewBuffer) autoRenderHyperlinksInCurrentLine(v *View) {
|
||||
if !v.AutoRenderHyperLinks {
|
||||
return
|
||||
}
|
||||
|
||||
line := v.buf.lines[v.buf.wy].cells
|
||||
line := b.lines[b.wy].cells
|
||||
start := 0
|
||||
for {
|
||||
linkStart := findLinkStart(line[start:])
|
||||
|
|
@ -1049,7 +1057,7 @@ func (v *View) autoRenderHyperlinksInCurrentLine() {
|
|||
link.WriteString(line[linkEnd].chr)
|
||||
}
|
||||
for i := linkStart; i < linkEnd; i++ {
|
||||
v.buf.lines[v.buf.wy].cells[i].hyperlink = link.String()
|
||||
b.lines[b.wy].cells[i].hyperlink = link.String()
|
||||
}
|
||||
start = linkEnd
|
||||
}
|
||||
|
|
@ -1058,13 +1066,13 @@ func (v *View) autoRenderHyperlinksInCurrentLine() {
|
|||
// parseInput parses char by char the input written to the View. It returns nil
|
||||
// while processing ESC sequences. Otherwise, it returns a cell slice that
|
||||
// contains the processed data.
|
||||
func (v *View) parseInput(ch []byte, width int, x int, _ int) (bool, []cell) {
|
||||
func (b *viewBuffer) parseInput(v *View, ch []byte, width int, x int, _ int) (bool, []cell) {
|
||||
cells := []cell{}
|
||||
truncateLine := false
|
||||
|
||||
isEscape, err := v.buf.ei.parseOne(ch)
|
||||
isEscape, err := b.ei.parseOne(ch)
|
||||
if err != nil {
|
||||
for _, chr := range v.buf.ei.characters() {
|
||||
for _, chr := range b.ei.characters() {
|
||||
c := cell{
|
||||
fgColor: v.FgColor,
|
||||
bgColor: v.BgColor,
|
||||
|
|
@ -1073,28 +1081,28 @@ func (v *View) parseInput(ch []byte, width int, x int, _ int) (bool, []cell) {
|
|||
}
|
||||
cells = append(cells, c)
|
||||
}
|
||||
v.buf.ei.reset()
|
||||
b.ei.reset()
|
||||
} else {
|
||||
repeatCount := 1
|
||||
if _, ok := v.buf.ei.instruction.(eraseInLineFromCursor); ok {
|
||||
if _, ok := b.ei.instruction.(eraseInLineFromCursor); ok {
|
||||
// Discard any old content past the cursor and record the
|
||||
// fill colors so draw() paints the trailing area with them.
|
||||
// This extends the bg to the right edge in both the
|
||||
// content-fits and content-wraps cases — for the latter,
|
||||
// the metadata is what reaches every wrapped segment past
|
||||
// the last word.
|
||||
v.buf.ei.instructionRead()
|
||||
b.ei.instructionRead()
|
||||
truncateLine = true
|
||||
v.buf.lines[v.buf.wy].trailingFillAttributes = &trailingFillAttributes{
|
||||
fg: v.buf.ei.curFgColor,
|
||||
bg: v.buf.ei.curBgColor,
|
||||
b.lines[b.wy].trailingFillAttributes = &trailingFillAttributes{
|
||||
fg: b.ei.curFgColor,
|
||||
bg: b.ei.curBgColor,
|
||||
}
|
||||
return truncateLine, []cell{}
|
||||
} else if cf, ok := v.buf.ei.instruction.(cursorForward); ok {
|
||||
} else if cf, ok := b.ei.instruction.(cursorForward); ok {
|
||||
// emit `n` space cells under the parser-tracked SGR — used
|
||||
// to materialize ConPTY's compressed runs of spaces (which
|
||||
// it emits as ECH+CUF instead of literal whitespace).
|
||||
v.buf.ei.instructionRead()
|
||||
b.ei.instructionRead()
|
||||
repeatCount = cf.n
|
||||
ch = []byte{' '}
|
||||
width = 1
|
||||
|
|
@ -1112,10 +1120,10 @@ func (v *View) parseInput(ch []byte, width int, x int, _ int) (bool, []cell) {
|
|||
repeatCount = tabWidth - (x % tabWidth)
|
||||
}
|
||||
c := cell{
|
||||
fgColor: v.buf.ei.curFgColor,
|
||||
bgColor: v.buf.ei.curBgColor,
|
||||
hyperlink: v.buf.ei.hyperlink.String(),
|
||||
metadata: v.buf.ei.metadata.String(),
|
||||
fgColor: b.ei.curFgColor,
|
||||
bgColor: b.ei.curBgColor,
|
||||
hyperlink: b.ei.hyperlink.String(),
|
||||
metadata: b.ei.metadata.String(),
|
||||
chr: string(ch),
|
||||
width: width,
|
||||
}
|
||||
|
|
@ -2074,7 +2082,7 @@ func (v *View) OverwriteLinesAndClearEverythingElse(lineCount int, y int, conten
|
|||
|
||||
func (v *View) setContentLineCount(lineCount int) {
|
||||
if lineCount > 0 {
|
||||
v.makeWriteable(0, lineCount-1)
|
||||
v.buf.makeWriteable(0, lineCount-1)
|
||||
}
|
||||
v.buf.lines = v.buf.lines[:lineCount]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue