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 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2a6cb8d78e
commit
cc5d5057a7
|
|
@ -800,56 +800,56 @@ func (v *View) ReadPos() (x, y int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeWriteable creates empty cells if required to make position (x, y) writeable.
|
// 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
|
// TODO: make this more efficient
|
||||||
|
|
||||||
// line `y` must be index-able (that's why `<=`)
|
// line `y` must be index-able (that's why `<=`)
|
||||||
for len(v.buf.lines) <= y {
|
for len(b.lines) <= y {
|
||||||
if cap(v.buf.lines) > len(v.buf.lines) {
|
if cap(b.lines) > len(b.lines) {
|
||||||
newLen := cap(v.buf.lines)
|
newLen := cap(b.lines)
|
||||||
if newLen > y {
|
if newLen > y {
|
||||||
newLen = y + 1
|
newLen = y + 1
|
||||||
}
|
}
|
||||||
v.buf.lines = v.buf.lines[:newLen]
|
b.lines = b.lines[:newLen]
|
||||||
} else {
|
} 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 `<`)
|
// 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`
|
// append should be used by `lines[y]` user if he wants to write beyond `x`
|
||||||
for len(v.buf.lines[y].cells) < x {
|
for len(b.lines[y].cells) < x {
|
||||||
if cap(v.buf.lines[y].cells) > len(v.buf.lines[y].cells) {
|
if cap(b.lines[y].cells) > len(b.lines[y].cells) {
|
||||||
newLen := cap(v.buf.lines[y].cells)
|
newLen := cap(b.lines[y].cells)
|
||||||
if newLen > x {
|
if newLen > x {
|
||||||
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 {
|
} 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
|
// !!! 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
|
var newLen int
|
||||||
// use maximum len available
|
// use maximum len available
|
||||||
line := v.buf.lines[v.buf.wy].cells[:cap(v.buf.lines[v.buf.wy].cells)]
|
line := b.lines[b.wy].cells[:cap(b.lines[b.wy].cells)]
|
||||||
maxCopy := len(line) - v.buf.wx
|
maxCopy := len(line) - b.wx
|
||||||
if maxCopy < len(cells) {
|
if maxCopy < len(cells) {
|
||||||
copy(line[v.buf.wx:], cells[:maxCopy])
|
copy(line[b.wx:], cells[:maxCopy])
|
||||||
line = append(line, cells[maxCopy:]...)
|
line = append(line, cells[maxCopy:]...)
|
||||||
newLen = len(line)
|
newLen = len(line)
|
||||||
} else { // maxCopy >= len(cells)
|
} else { // maxCopy >= len(cells)
|
||||||
copy(line[v.buf.wx:], cells)
|
copy(line[b.wx:], cells)
|
||||||
newLen = v.buf.wx + len(cells)
|
newLen = b.wx + len(cells)
|
||||||
if newLen < len(v.buf.lines[v.buf.wy].cells) {
|
if newLen < len(b.lines[b.wy].cells) {
|
||||||
newLen = len(v.buf.lines[v.buf.wy].cells)
|
newLen = len(b.lines[b.wy].cells)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
v.buf.lines[v.buf.wy].cells = line[:newLen]
|
b.lines[b.wy].cells = line[:newLen]
|
||||||
v.buf.wx += len(cells)
|
b.wx += len(cells)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write appends a byte slice into the view's internal buffer. Because
|
// Write appends a byte slice into the view's internal buffer. Because
|
||||||
|
|
@ -872,30 +872,40 @@ func (v *View) write(p []byte) {
|
||||||
v.firstDirtyLine = min(v.firstDirtyLine, v.buf.wy)
|
v.firstDirtyLine = min(v.firstDirtyLine, v.buf.wy)
|
||||||
v.clearHover()
|
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
|
// 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() {
|
finishLine := func() {
|
||||||
v.autoRenderHyperlinksInCurrentLine()
|
b.autoRenderHyperlinksInCurrentLine(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
advanceToNextLine := func() {
|
advanceToNextLine := func() {
|
||||||
v.buf.wx = 0
|
b.wx = 0
|
||||||
v.buf.wy++
|
b.wy++
|
||||||
if v.buf.wy >= len(v.buf.lines) {
|
if b.wy >= len(b.lines) {
|
||||||
v.buf.lines = append(v.buf.lines, lineType{})
|
b.lines = append(b.lines, lineType{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.buf.pendingNewline {
|
if b.pendingNewline {
|
||||||
advanceToNextLine()
|
advanceToNextLine()
|
||||||
v.buf.ei.notifyRowAdvance()
|
b.ei.notifyRowAdvance()
|
||||||
v.buf.pendingNewline = false
|
b.pendingNewline = false
|
||||||
}
|
}
|
||||||
|
|
||||||
until := len(p)
|
until := len(p)
|
||||||
if !v.Editable && until > 0 && p[until-1] == '\n' {
|
if !v.Editable && until > 0 && p[until-1] == '\n' {
|
||||||
v.buf.pendingNewline = true
|
b.pendingNewline = true
|
||||||
until--
|
until--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -911,26 +921,26 @@ func (v *View) write(p []byte) {
|
||||||
case characterEquals(chr, '\n') || isCRLF(chr):
|
case characterEquals(chr, '\n') || isCRLF(chr):
|
||||||
finishLine()
|
finishLine()
|
||||||
advanceToNextLine()
|
advanceToNextLine()
|
||||||
v.buf.ei.notifyRowAdvance()
|
b.ei.notifyRowAdvance()
|
||||||
case characterEquals(chr, '\r'):
|
case characterEquals(chr, '\r'):
|
||||||
finishLine()
|
finishLine()
|
||||||
v.buf.wx = 0
|
b.wx = 0
|
||||||
v.buf.ei.notifyColumnReset()
|
b.ei.notifyColumnReset()
|
||||||
default:
|
default:
|
||||||
truncateLine, cells := v.parseInput(chr, width, v.buf.wx, v.buf.wy)
|
truncateLine, cells := b.parseInput(v, chr, width, b.wx, b.wy)
|
||||||
if cd, ok := v.buf.ei.instruction.(cursorDown); ok {
|
if cd, ok := b.ei.instruction.(cursorDown); ok {
|
||||||
v.buf.ei.instructionRead()
|
b.ei.instructionRead()
|
||||||
for range cd.n {
|
for range cd.n {
|
||||||
v.autoRenderHyperlinksInCurrentLine()
|
b.autoRenderHyperlinksInCurrentLine(v)
|
||||||
advanceToNextLine()
|
advanceToNextLine()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cells == nil {
|
if cells == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
v.writeCells(cells)
|
b.writeCells(cells)
|
||||||
if truncateLine {
|
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
|
// Soft-wrap tracking. truncateLine is true exactly when the
|
||||||
// cells are from \x1b[K filling to end of line — ConPTY
|
// cells are from \x1b[K filling to end of line — ConPTY
|
||||||
|
|
@ -941,18 +951,16 @@ func (v *View) write(p []byte) {
|
||||||
for _, c := range cells {
|
for _, c := range cells {
|
||||||
totalWidth += c.width
|
totalWidth += c.width
|
||||||
}
|
}
|
||||||
v.buf.ei.notifyCellsWritten(totalWidth)
|
b.ei.notifyCellsWritten(totalWidth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.buf.pendingNewline {
|
if b.pendingNewline {
|
||||||
finishLine()
|
finishLine()
|
||||||
} else {
|
} else {
|
||||||
v.autoRenderHyperlinksInCurrentLine()
|
b.autoRenderHyperlinksInCurrentLine(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
v.updateSearchPositions()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// exported functions use the mutex. Non-exported functions are for internal use
|
// exported functions use the mutex. Non-exported functions are for internal use
|
||||||
|
|
@ -995,12 +1003,12 @@ var lineEndCharacters = map[string]bool{
|
||||||
")": true,
|
")": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *View) autoRenderHyperlinksInCurrentLine() {
|
func (b *viewBuffer) autoRenderHyperlinksInCurrentLine(v *View) {
|
||||||
if !v.AutoRenderHyperLinks {
|
if !v.AutoRenderHyperLinks {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
line := v.buf.lines[v.buf.wy].cells
|
line := b.lines[b.wy].cells
|
||||||
start := 0
|
start := 0
|
||||||
for {
|
for {
|
||||||
linkStart := findLinkStart(line[start:])
|
linkStart := findLinkStart(line[start:])
|
||||||
|
|
@ -1017,7 +1025,7 @@ func (v *View) autoRenderHyperlinksInCurrentLine() {
|
||||||
link.WriteString(line[linkEnd].chr)
|
link.WriteString(line[linkEnd].chr)
|
||||||
}
|
}
|
||||||
for i := linkStart; i < linkEnd; i++ {
|
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
|
start = linkEnd
|
||||||
}
|
}
|
||||||
|
|
@ -1026,13 +1034,13 @@ func (v *View) autoRenderHyperlinksInCurrentLine() {
|
||||||
// parseInput parses char by char the input written to the View. It returns nil
|
// 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
|
// while processing ESC sequences. Otherwise, it returns a cell slice that
|
||||||
// contains the processed data.
|
// 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{}
|
cells := []cell{}
|
||||||
truncateLine := false
|
truncateLine := false
|
||||||
|
|
||||||
isEscape, err := v.buf.ei.parseOne(ch)
|
isEscape, err := b.ei.parseOne(ch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for _, chr := range v.buf.ei.characters() {
|
for _, chr := range b.ei.characters() {
|
||||||
c := cell{
|
c := cell{
|
||||||
fgColor: v.FgColor,
|
fgColor: v.FgColor,
|
||||||
bgColor: v.BgColor,
|
bgColor: v.BgColor,
|
||||||
|
|
@ -1041,28 +1049,28 @@ func (v *View) parseInput(ch []byte, width int, x int, _ int) (bool, []cell) {
|
||||||
}
|
}
|
||||||
cells = append(cells, c)
|
cells = append(cells, c)
|
||||||
}
|
}
|
||||||
v.buf.ei.reset()
|
b.ei.reset()
|
||||||
} else {
|
} else {
|
||||||
repeatCount := 1
|
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
|
// Discard any old content past the cursor and record the
|
||||||
// fill colors so draw() paints the trailing area with them.
|
// fill colors so draw() paints the trailing area with them.
|
||||||
// This extends the bg to the right edge in both the
|
// This extends the bg to the right edge in both the
|
||||||
// content-fits and content-wraps cases — for the latter,
|
// content-fits and content-wraps cases — for the latter,
|
||||||
// the metadata is what reaches every wrapped segment past
|
// the metadata is what reaches every wrapped segment past
|
||||||
// the last word.
|
// the last word.
|
||||||
v.buf.ei.instructionRead()
|
b.ei.instructionRead()
|
||||||
truncateLine = true
|
truncateLine = true
|
||||||
v.buf.lines[v.buf.wy].trailingFillAttributes = &trailingFillAttributes{
|
b.lines[b.wy].trailingFillAttributes = &trailingFillAttributes{
|
||||||
fg: v.buf.ei.curFgColor,
|
fg: b.ei.curFgColor,
|
||||||
bg: v.buf.ei.curBgColor,
|
bg: b.ei.curBgColor,
|
||||||
}
|
}
|
||||||
return truncateLine, []cell{}
|
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
|
// emit `n` space cells under the parser-tracked SGR — used
|
||||||
// to materialize ConPTY's compressed runs of spaces (which
|
// to materialize ConPTY's compressed runs of spaces (which
|
||||||
// it emits as ECH+CUF instead of literal whitespace).
|
// it emits as ECH+CUF instead of literal whitespace).
|
||||||
v.buf.ei.instructionRead()
|
b.ei.instructionRead()
|
||||||
repeatCount = cf.n
|
repeatCount = cf.n
|
||||||
ch = []byte{' '}
|
ch = []byte{' '}
|
||||||
width = 1
|
width = 1
|
||||||
|
|
@ -1080,9 +1088,9 @@ func (v *View) parseInput(ch []byte, width int, x int, _ int) (bool, []cell) {
|
||||||
repeatCount = tabWidth - (x % tabWidth)
|
repeatCount = tabWidth - (x % tabWidth)
|
||||||
}
|
}
|
||||||
c := cell{
|
c := cell{
|
||||||
fgColor: v.buf.ei.curFgColor,
|
fgColor: b.ei.curFgColor,
|
||||||
bgColor: v.buf.ei.curBgColor,
|
bgColor: b.ei.curBgColor,
|
||||||
hyperlink: v.buf.ei.hyperlink.String(),
|
hyperlink: b.ei.hyperlink.String(),
|
||||||
chr: string(ch),
|
chr: string(ch),
|
||||||
width: width,
|
width: width,
|
||||||
}
|
}
|
||||||
|
|
@ -1944,7 +1952,7 @@ func (v *View) OverwriteLinesAndClearEverythingElse(lineCount int, y int, conten
|
||||||
|
|
||||||
func (v *View) setContentLineCount(lineCount int) {
|
func (v *View) setContentLineCount(lineCount int) {
|
||||||
if lineCount > 0 {
|
if lineCount > 0 {
|
||||||
v.makeWriteable(0, lineCount-1)
|
v.buf.makeWriteable(0, lineCount-1)
|
||||||
}
|
}
|
||||||
v.buf.lines = v.buf.lines[:lineCount]
|
v.buf.lines = v.buf.lines[:lineCount]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue