feat(tui-go): transcript message-jump + OSC52 copy
ctrl+↑/↓ jumps between your sent (user) messages in the transcript: ctrl+↑ from the bottom selects the most recent, steps to older ones (clamping); ctrl+↓ steps back and drops to tail-follow past the last. The selected message is highlighted and scrolled into view (routerRows tracks per-message row offsets and windows to the selection). `y` yanks the selected message — or the latest turn when none is selected — to the system clipboard over OSC52 (go-osc52, tmux-wrapped under $TMUX), which is SSH/Termius- safe unlike a mouse drag. A brief "✓ copied" footer note animates out (and self-stops the tick). esc drops the selection; switching sessions clears it. Tests cover the user-message jump/clamp/tail and the copy-text selection. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
package app
|
||||
|
||||
import "testing"
|
||||
|
||||
func transcriptModel(roles ...string) Model {
|
||||
m := NewModel(nil)
|
||||
m.selectedID = "s1"
|
||||
msgs := make([]RouterEntry, len(roles))
|
||||
for i, r := range roles {
|
||||
msgs[i] = RouterEntry{Role: r, Content: r + itoa(i)}
|
||||
}
|
||||
m.routerMessages["s1"] = msgs
|
||||
return m
|
||||
}
|
||||
|
||||
// ctrl+↑/↓ jumps between USER messages: from no selection ↑ grabs the most recent user
|
||||
// message, keeps stepping to older ones (clamping), and ↓ steps back down, dropping to
|
||||
// tail-follow (-1) once past the last user message.
|
||||
func TestTranscriptNavUser(t *testing.T) {
|
||||
// indices: 0 1 2 3 4
|
||||
m := transcriptModel("user", "router", "user", "router", "user")
|
||||
|
||||
if m.transcriptSel != -1 {
|
||||
t.Fatalf("initial sel = %d, want -1", m.transcriptSel)
|
||||
}
|
||||
m.transcriptNavUser(-1) // up from bottom → newest user (idx 4)
|
||||
if m.transcriptSel != 4 {
|
||||
t.Fatalf("up#1 = %d, want 4", m.transcriptSel)
|
||||
}
|
||||
m.transcriptNavUser(-1) // → user idx 2
|
||||
if m.transcriptSel != 2 {
|
||||
t.Fatalf("up#2 = %d, want 2", m.transcriptSel)
|
||||
}
|
||||
m.transcriptNavUser(-1) // → user idx 0
|
||||
if m.transcriptSel != 0 {
|
||||
t.Fatalf("up#3 = %d, want 0", m.transcriptSel)
|
||||
}
|
||||
m.transcriptNavUser(-1) // clamp at oldest
|
||||
if m.transcriptSel != 0 {
|
||||
t.Fatalf("up#4 (clamp) = %d, want 0", m.transcriptSel)
|
||||
}
|
||||
m.transcriptNavUser(1) // → user idx 2
|
||||
if m.transcriptSel != 2 {
|
||||
t.Fatalf("down#1 = %d, want 2", m.transcriptSel)
|
||||
}
|
||||
m.transcriptNavUser(1) // → user idx 4
|
||||
m.transcriptNavUser(1) // past last user → tail-follow
|
||||
if m.transcriptSel != -1 {
|
||||
t.Fatalf("down past last = %d, want -1 (tail-follow)", m.transcriptSel)
|
||||
}
|
||||
}
|
||||
|
||||
// `y` copies the selected message, or the latest user/router turn when nothing is selected.
|
||||
func TestCopyText(t *testing.T) {
|
||||
m := transcriptModel("user", "router", "tool")
|
||||
|
||||
// no selection → most recent user/router turn (the "router" at idx 1, since "tool"
|
||||
// is skipped).
|
||||
if got := m.copyText(); got != "router1" {
|
||||
t.Fatalf("copyText (no sel) = %q, want %q", got, "router1")
|
||||
}
|
||||
|
||||
m.transcriptSel = 0
|
||||
if got := m.copyText(); got != "user0" {
|
||||
t.Fatalf("copyText (sel idx 0) = %q, want %q", got, "user0")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user