Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions internal/cli/chat_tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,12 @@ func (m chatTUI) update(msg tea.Msg) (tea.Model, tea.Cmd) {
case "pgdown":
m.viewport.PageDown()
return m, finalize(m, cmds)
case "ctrl+home":
m.viewport.GotoTop()
return m, finalize(m, cmds)
case "ctrl+end":
m.viewport.GotoBottom()
return m, finalize(m, cmds)
case "ctrl+z":
return m, tea.Suspend
}
Expand Down
31 changes: 31 additions & 0 deletions internal/cli/chat_tui_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,37 @@ func TestInsertNewlineKeyBinding(t *testing.T) {
}
}

func TestCtrlHomeEndScrollKeyBindings(t *testing.T) {
ctrl := control.New(control.Options{})
ch := make(chan event.Event, 1)
notice := agentEventMsg(event.Event{Kind: event.Notice, Level: event.LevelInfo, Text: "line"})
adv := func(m chatTUI, msg tea.Msg) chatTUI {
n, _ := m.Update(msg)
return n.(chatTUI)
}

cur := adv(newChatTUI(ctrl, "", ch, 80), tea.WindowSizeMsg{Width: 80, Height: 8})
for i := 0; i < 12; i++ {
cur = adv(cur, notice)
}
// Viewport should be at the bottom after output.
if !cur.viewport.AtBottom() {
t.Fatal("viewport should start at the bottom after streaming output")
}

// Ctrl+Home should scroll to the top.
cur = adv(cur, tea.KeyPressMsg{Code: tea.KeyHome, Mod: tea.ModCtrl})
if !cur.viewport.AtTop() {
t.Fatalf("ctrl+home should scroll to top, AtTop=%v, YOffset=%d", cur.viewport.AtTop(), cur.viewport.YOffset())
}

// Ctrl+End should scroll back to the bottom.
cur = adv(cur, tea.KeyPressMsg{Code: tea.KeyEnd, Mod: tea.ModCtrl})
if !cur.viewport.AtBottom() {
t.Fatalf("ctrl+end should scroll to bottom, AtBottom=%v, YOffset=%d", cur.viewport.AtBottom(), cur.viewport.YOffset())
}
}

func TestEchoLocalCommandAddsTranscriptMarker(t *testing.T) {
m := newTestChatTUI()
m.echoLocalCommand(" /tree ")
Expand Down
2 changes: 1 addition & 1 deletion internal/i18n/messages_en.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ var English = Messages{
ChatStatusCycleHint: "shift+tab to cycle",
ChatStatusCacheNowFmt: "turn hit %s",
ChatStatusCacheAvgFmt: "avg %s",
ChatStatusPlanApproval: "Enter/y approves & executes · n/Esc keeps planning · PgUp/PgDn scrolls",
ChatStatusPlanApproval: "Enter/y approves & executes · n/Esc keeps planning · PgUp/PgDn/Ctrl+Home/End scrolls",
PlanApprovalPrompt: "Plan ready above — Enter/y to approve & execute, n/Esc to keep planning",
ChatStatusToolApproval: "1 approve once · 2 allow scope this session · 3/4 prefix or save when offered · n/Esc deny · Ctrl-C cancels turn",
AskTypeSomething: "Type something else",
Expand Down
2 changes: 1 addition & 1 deletion internal/i18n/messages_zh.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ var Chinese = Messages{
ChatStatusCycleHint: "shift+tab 循环切换",
ChatStatusCacheNowFmt: "本次命中 %s",
ChatStatusCacheAvgFmt: "平均 %s",
ChatStatusPlanApproval: "Enter/y 批准并执行 · n/Esc 继续规划 · PgUp/PgDn 滚动",
ChatStatusPlanApproval: "Enter/y 批准并执行 · n/Esc 继续规划 · PgUp/PgDn/Ctrl+Home/End 滚动",
PlanApprovalPrompt: "计划已生成(见上方)— Enter/y 批准执行,n/Esc 继续规划",
ChatStatusToolApproval: "1 本次允许 · 2 本会话允许此范围 · 提供时 3/4 为前缀或保存 · n/Esc 拒绝 · Ctrl-C 取消本轮",
AskTypeSomething: "自己输入",
Expand Down
Loading