fix(tui): preserve native terminal selection

This commit is contained in:
GTC2080 committed 2026-06-23 19:50:36 +10:00
1 parent 9c27591edf
commit edacf1a8c2
4 files changed
+14 -14

No files matched your search

+2 -1
View File
@@ -213,7 +213,8 @@ Chat and transcript shortcuts:
| `Ctrl+Home` / `Ctrl+End` | Jumps to the top or bottom of the transcript | Useful after long tool output. |
| `Esc` | Backs out of the current action | It un-sends a just-submitted turn before any reply, cancels a running turn, or clears non-empty input. |
| Double `Esc` on an empty idle composer | Opens the rewind picker | Same entry point as `/rewind`. |
| `Ctrl+C` / `Meta+C` / `Super+C` | Copies an active transcript selection | Without a selection it cancels a running turn, clears non-empty input, or quits on a second empty-composer press. |
| Terminal native selection | Copies transcript text | Reasonix does not enable mouse reporting by default, so terminal selection/copy remains available. |
| `Ctrl+C` | Cancels, clears, or quits | Cancels a running turn, clears non-empty input, or quits on a second empty-composer press. |
| `Ctrl+D` | Quits the TUI | Immediate quit. |
| `Ctrl+V`, `Ctrl+Shift+V`, `Meta+V`, or `Super+V` | Pastes clipboard content | The CLI tries an image first, then falls back to text or file references. |
| `/paste-image` | Pastes a clipboard image | Use it when you want image-only paste or the terminal handles text paste itself. |
+2 -1
View File
@@ -194,7 +194,8 @@ Goal、由 `todo_write` 工具驱动的实时 Todo 面板,以及已配置 prov
| `Ctrl+Home` / `Ctrl+End` | 跳到 transcript 顶部或底部 | 长工具输出后很有用。 |
| `Esc` | 退出当前最具体的动作 | 可在无回复前撤回刚提交的 turn、取消运行中的 turn,或清空非空输入。 |
| 空闲且输入为空时双击 `Esc` | 打开 rewind 选择器 | 和 `/rewind` 是同一个入口。 |
| `Ctrl+C` / `Meta+C` / `Super+C` | 复制当前 transcript 选区 | 没有选区时用于取消运行中 turn、清空非空输入;空输入下连按两次退出。 |
| 终端原生选择 | 复制 transcript 文本 | Reasonix 默认不启用鼠标报告,因此终端自己的选择/复制仍可使用。 |
| `Ctrl+C` | 取消、清空或退出 | 取消运行中的 turn、清空非空输入;空输入下连按两次退出。 |
| `Ctrl+D` | 退出 TUI | 立即退出。 |
| `Ctrl+V`、`Ctrl+Shift+V`、`Meta+V` 或 `Super+V` | 粘贴剪贴板内容 | CLI 会先尝试图片,再回退到文本或文件引用。 |
| `/paste-image` | 粘贴剪贴板图片 | 适合只想贴图片,或终端应用自己接管文本粘贴的场景。 |
+6 -8
View File
@@ -38,9 +38,9 @@ import (
)
// chatTUI is a bubbletea Model that normally owns the terminal with an
// alt-screen transcript viewport. Termux is the exception: enabling mouse mode
// prevents taps from raising the soft keyboard, so it stays in the normal buffer
// and commits finalized output to native scrollback via tea.Println.
// alt-screen transcript viewport. Termux is the exception: it stays in the
// normal buffer and commits finalized output to native scrollback via
// tea.Println so taps can still focus the soft keyboard.
type chatTUI struct {
ctrl control.SessionAPI
label string
@@ -48,8 +48,8 @@ type chatTUI struct {
width int
height int
// nativeScrollback keeps Termux out of alt-screen/mouse mode so taps still
// focus the textarea and raise the soft keyboard.
// nativeScrollback keeps Termux out of alt-screen mode so taps still focus
// the textarea and raise the soft keyboard.
nativeScrollback bool
input textarea.Model
@@ -1375,8 +1375,7 @@ func (m chatTUI) update(msg tea.Msg) (tea.Model, tea.Cmd) {
// finalize drains the committed-line queue and batches the turn's commands. In
// the default alt-screen path the queue is already mirrored in m.transcript. In
// Termux we cannot enable mouse mode without breaking soft-keyboard focus, so
// finalized lines are also emitted into the terminal's native scrollback.
// Termux finalized lines are also emitted into the terminal's native scrollback.
func finalize(m chatTUI, cmds []tea.Cmd) tea.Cmd {
if m.nativeScrollback && len(*m.pendingCommit) > 0 {
out := strings.TrimRight(clampWidth(strings.Join(*m.pendingCommit, "\n"), m.width), "\n")
@@ -2399,7 +2398,6 @@ func (m chatTUI) View() tea.View {
}
v := tea.NewView(mainArea + "\n" + strings.Join(parts, "\n"))
v.AltScreen = true
v.MouseMode = tea.MouseModeCellMotion // wheel scrolls the transcript
// Anchor the real terminal cursor at the textarea's insertion point only when
// the composer is visible. input.Cursor() is relative to the textarea; offset
// by the viewport height + rows above + the box's top border row (+1 column
+4 -4
View File
@@ -1392,8 +1392,8 @@ func TestQueueIndicatorHiddenWhenIdle(t *testing.T) {
}
// TestViewAltScreenFillsHeight proves the switch to alt-screen: View requests
// the alt buffer + mouse, and the frame is exactly the terminal height (the
// transcript viewport pads to fill above the pinned bottom region).
// the alt buffer without mouse reporting, and the frame is exactly the terminal
// height (the transcript viewport pads to fill above the pinned bottom region).
func TestViewAltScreenFillsHeight(t *testing.T) {
ctrl := control.New(control.Options{})
m := newChatTUI(ctrl, "", make(chan event.Event, 1), 80)
@@ -1404,8 +1404,8 @@ func TestViewAltScreenFillsHeight(t *testing.T) {
if !v.AltScreen {
t.Error("View must request alt-screen so resize repaints the whole grid")
}
if v.MouseMode != tea.MouseModeCellMotion {
t.Error("View must enable mouse so the wheel scrolls the transcript")
if v.MouseMode != tea.MouseModeNone {
t.Error("View must leave terminal mouse selection available by default")
}
if lines := strings.Count(v.Content, "\n") + 1; lines != 24 {
t.Errorf("alt-screen frame = %d lines, want 24 (full terminal height)", lines)