Fix sidebar repaint and command restart navigation
This commit is contained in:
@@ -10,11 +10,13 @@ import (
|
||||
// viewportRenderer rewrites child PTY output so it lands inside the
|
||||
// main viewport instead of controlling patterm's full host terminal.
|
||||
type viewportRenderer struct {
|
||||
mu sync.Mutex
|
||||
shifter *cursorShifter
|
||||
layout terminalLayout
|
||||
row int
|
||||
col int
|
||||
mu sync.Mutex
|
||||
shifter *cursorShifter
|
||||
layout terminalLayout
|
||||
row int
|
||||
col int
|
||||
scrollTop int
|
||||
scrollBottom int
|
||||
|
||||
state viewportState
|
||||
buf []byte
|
||||
@@ -22,8 +24,9 @@ type viewportRenderer struct {
|
||||
|
||||
// scrolled is set when the chunk contained an escape that shifts
|
||||
// content row-wise within the host's scroll region — RI / IND /
|
||||
// NEL / SU / SD / IL / DL. DECSTBM constrains rows but not columns,
|
||||
// so these scrolls drag the right-hand sidebar content with them.
|
||||
// NEL / SU / SD / IL / DL, or LF / VT / FF at the bottom margin.
|
||||
// DECSTBM constrains rows but not columns, so these scrolls drag the
|
||||
// right-hand sidebar content with them.
|
||||
// OnPTYOut consumes the flag and invalidates the sidebar chrome
|
||||
// cache so the next drawSidebar repaints over the clobber.
|
||||
scrolled bool
|
||||
@@ -50,12 +53,14 @@ const (
|
||||
)
|
||||
|
||||
func newViewportRenderer(l terminalLayout) *viewportRenderer {
|
||||
return &viewportRenderer{
|
||||
vr := &viewportRenderer{
|
||||
shifter: newCursorShifter(int(l.mainTop)-1, int(l.childRows()), int(l.childCols())),
|
||||
layout: l,
|
||||
row: 1,
|
||||
col: 1,
|
||||
}
|
||||
vr.resetScrollRegion()
|
||||
return vr
|
||||
}
|
||||
|
||||
func (vr *viewportRenderer) SetLayout(l terminalLayout) {
|
||||
@@ -63,6 +68,7 @@ func (vr *viewportRenderer) SetLayout(l terminalLayout) {
|
||||
defer vr.mu.Unlock()
|
||||
vr.layout = l
|
||||
vr.shifter.SetGeometry(int(l.mainTop)-1, int(l.childRows()), int(l.childCols()))
|
||||
vr.resetScrollRegion()
|
||||
}
|
||||
|
||||
func (vr *viewportRenderer) Render(in []byte) []byte {
|
||||
@@ -82,11 +88,10 @@ func (vr *viewportRenderer) ClearViewport() []byte {
|
||||
}
|
||||
|
||||
// TookScrollAction reports whether the most recent Render emitted (or
|
||||
// forwarded) a scroll-triggering escape — RI / IND / NEL / SU / SD /
|
||||
// IL / DL — since the previous call. The flag is reset on read.
|
||||
// Callers use it to invalidate sidebar-cache state, because the host's
|
||||
// scroll region spans the full row width and any scroll there drags
|
||||
// the sidebar content downward.
|
||||
// forwarded) a scroll action since the previous call. Callers use it
|
||||
// to invalidate sidebar-cache state, because the host's scroll region
|
||||
// spans the full row width and any scroll there drags the sidebar
|
||||
// content vertically.
|
||||
func (vr *viewportRenderer) TookScrollAction() bool {
|
||||
vr.mu.Lock()
|
||||
defer vr.mu.Unlock()
|
||||
@@ -326,6 +331,22 @@ func (vr *viewportRenderer) clearLine(n int) string {
|
||||
}
|
||||
}
|
||||
|
||||
func (vr *viewportRenderer) resetScrollRegion() {
|
||||
vr.scrollTop = 1
|
||||
vr.scrollBottom = int(vr.layout.childRows())
|
||||
if vr.scrollBottom < 1 {
|
||||
vr.scrollBottom = 1
|
||||
}
|
||||
}
|
||||
|
||||
func (vr *viewportRenderer) lineFeed() {
|
||||
if vr.row >= vr.scrollTop && vr.row == vr.scrollBottom {
|
||||
vr.scrolled = true
|
||||
return
|
||||
}
|
||||
vr.row++
|
||||
}
|
||||
|
||||
// feedPrintable handles one non-ESC byte in the vpNormal state. It both
|
||||
// advances vr's cursor model and decides whether the byte should be
|
||||
// forwarded to the host. Bytes that would land past the viewport's
|
||||
@@ -342,8 +363,8 @@ func (vr *viewportRenderer) feedPrintable(b byte) {
|
||||
switch b {
|
||||
case '\r':
|
||||
vr.col = 1
|
||||
case '\n':
|
||||
vr.row++
|
||||
case '\n', '\v', '\f':
|
||||
vr.lineFeed()
|
||||
case '\b':
|
||||
if vr.col > 1 {
|
||||
vr.col--
|
||||
@@ -437,10 +458,38 @@ func (vr *viewportRenderer) trackCSI(final byte, params []byte) {
|
||||
if ok {
|
||||
vr.col -= n
|
||||
}
|
||||
case 'r':
|
||||
vr.trackScrollRegion(params)
|
||||
}
|
||||
vr.clampCursor()
|
||||
}
|
||||
|
||||
func (vr *viewportRenderer) trackScrollRegion(params []byte) {
|
||||
if len(params) == 0 {
|
||||
vr.resetScrollRegion()
|
||||
return
|
||||
}
|
||||
top, bottom, ok := parseTwoParams(params)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
maxRows := int(vr.layout.childRows())
|
||||
if maxRows < 1 {
|
||||
maxRows = 1
|
||||
}
|
||||
if top < 1 {
|
||||
top = 1
|
||||
}
|
||||
if bottom < 1 || bottom > maxRows {
|
||||
bottom = maxRows
|
||||
}
|
||||
if top >= bottom {
|
||||
return
|
||||
}
|
||||
vr.scrollTop = top
|
||||
vr.scrollBottom = bottom
|
||||
}
|
||||
|
||||
func (vr *viewportRenderer) clampCursor() {
|
||||
if vr.row < 1 {
|
||||
vr.row = 1
|
||||
|
||||
Reference in New Issue
Block a user