mirror of
https://github.com/patriceckhart/zot.git
synced 2026-06-26 21:36:31 +02:00
fix(tui): keep multiple inline images visible
Also route up/down arrows to chat scrolling even when the editor has text, while preserving slash-popup navigation.
This commit is contained in:
parent
c16e929f32
commit
43aca0b9b2
3 changed files with 50 additions and 11 deletions
|
|
@ -809,6 +809,7 @@ func (i *Interactive) redraw() {
|
|||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
start = alignSliceStartToImageBlock(chat, start, end)
|
||||
visibleChat = chat[start:end]
|
||||
}
|
||||
|
||||
|
|
@ -852,6 +853,38 @@ func (i *Interactive) redraw() {
|
|||
i.rend.Draw(frame, cursorRow, cursorCol)
|
||||
}
|
||||
|
||||
func alignSliceStartToImageBlock(chat []string, start, end int) int {
|
||||
if start <= 0 || start >= len(chat) || start >= end {
|
||||
return start
|
||||
}
|
||||
// If the slice already starts on an image row, keep it.
|
||||
if strings.Contains(chat[start], "\x1b]1337;File=") || strings.Contains(chat[start], "\x1b_G") {
|
||||
return start
|
||||
}
|
||||
// When the viewport begins inside an image block, the image escape row
|
||||
// sits just above a run of blank reservation rows, followed by an
|
||||
// "image - ..." info line. In that case, snap the slice start up to the
|
||||
// escape row so the image actually renders instead of showing only the
|
||||
// reserved blank area and metadata.
|
||||
j := start
|
||||
for j < end && strings.TrimSpace(chat[j]) == "" {
|
||||
j++
|
||||
}
|
||||
if j >= end || !strings.Contains(chat[j], "image - ") {
|
||||
return start
|
||||
}
|
||||
for k := start - 1; k >= 0; k-- {
|
||||
line := chat[k]
|
||||
if strings.Contains(line, "\x1b]1337;File=") || strings.Contains(line, "\x1b_G") {
|
||||
return k
|
||||
}
|
||||
if strings.TrimSpace(line) != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return start
|
||||
}
|
||||
|
||||
// truncateLine shortens s so it fits within n display cells, with an
|
||||
// ellipsis if trimmed. Used by the "sliding in" chips so a pasted
|
||||
// novel doesn't blow past the status line.
|
||||
|
|
@ -1166,16 +1199,17 @@ func (i *Interactive) handleKey(ctx context.Context, k tui.Key) (done bool) {
|
|||
i.scrollBy(-i.chatPage())
|
||||
return false
|
||||
case tui.KeyUp:
|
||||
// Wheel-up in alt screen sends Up arrows on most terminals.
|
||||
// When the editor is empty we use up/down for chat scroll —
|
||||
// independently of whether the agent is busy, so users can
|
||||
// scroll back through long streaming replies while they run.
|
||||
if i.ed.IsEmpty() {
|
||||
// Always use up/down for chat scrolling, even when the editor
|
||||
// contains text. This makes keyboard scrolling consistent with
|
||||
// a draft present at the cost of disabling vertical cursor
|
||||
// motion inside the multi-line editor. Keep slash-popup
|
||||
// navigation working by letting it intercept later when active.
|
||||
if !i.suggest.Active(i.ed.Value()) {
|
||||
i.scrollBy(+3)
|
||||
return false
|
||||
}
|
||||
case tui.KeyDown:
|
||||
if i.ed.IsEmpty() {
|
||||
if !i.suggest.Active(i.ed.Value()) {
|
||||
if i.scrollOffset > 0 {
|
||||
i.scrollBy(-3)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,10 +123,6 @@ func renderKitty(data []byte, maxCellsWide, maxCellsHigh int) string {
|
|||
const chunk = 4096
|
||||
var sb strings.Builder
|
||||
|
||||
// Prefix: delete any previously-placed images so old frames don't
|
||||
// linger on screen when the chat scrolls past them.
|
||||
sb.WriteString("\x1b_Ga=d\x1b\\")
|
||||
|
||||
// Pick the most constraining dimension and use only it. Kitty
|
||||
// preserves aspect ratio when exactly one of c/r is provided.
|
||||
hdr := "a=T,f=100"
|
||||
|
|
|
|||
|
|
@ -152,15 +152,24 @@ func (r *Renderer) Draw(lines []string, cursorRow, cursorCol int) {
|
|||
// is unreliable. Inline images are opt-in via ZOT_INLINE_IMAGES;
|
||||
// the common code path below is the fast cached diff.
|
||||
curHasImage := false
|
||||
curHasKittyImage := false
|
||||
for _, l := range frame {
|
||||
if containsImageEscape(l) {
|
||||
curHasImage = true
|
||||
break
|
||||
if strings.Contains(l, "\x1b_G") {
|
||||
curHasKittyImage = true
|
||||
}
|
||||
}
|
||||
}
|
||||
forceAll := curHasImage || r.prevHadImage
|
||||
if forceAll {
|
||||
w.WriteString(SeqClearScreen)
|
||||
if curHasKittyImage {
|
||||
// Delete previously placed kitty images once per frame,
|
||||
// before rewriting all rows. Doing this inside each image
|
||||
// escape makes only the last image in the frame survive.
|
||||
w.WriteString("\x1b_Ga=d\x1b\\")
|
||||
}
|
||||
}
|
||||
|
||||
full := r.prev == nil || len(r.prev) != r.rows
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue