feat(tui): keyboard text selection + copy in transcript - #408
Open
alecuba16 wants to merge 1 commit into
Open
Conversation
Add keyboard-driven text selection in the transcript pane. Press `v` on an empty prompt to enter selection mode, navigate with arrows/PageUp/Down/ Home/End, and copy with Enter or Ctrl+C. Esc cancels. Key changes addressing review feedback on Kuberwastaken#387: - Dedicated entry binding: `v` on empty prompt enters selection mode (hardcoded handler like `?` for help, gated on no voice recorder and not in vim Normal/Visual mode so it doesn't shadow vim's `v`). - All selection-mode keys route through KeyContext::Transcript in keybindings.rs (up/down/pageup/pagedown/home/end/enter/escape) instead of inline KeyCode matches. Actions: selectionUp/Down/PageUp/PageDown/ GoStart/GoEnd/Copy/Cancel. - Content-anchored selection: cursor moves through screen rows within last_selectable_area (now restricted to last_msg_area instead of the full terminal). Scroll branches are reachable and clamp correctly. - total_message_lines is now written at render time (was always 0). - Visible cursor on entry: apply_selection_highlight shows a cursor bar when anchor == focus instead of early-returning. - Ctrl+D is not swallowed in selection mode (allowed to trigger exit). - Unbound keys in selection mode are swallowed by a guard, except Ctrl+C (copy + exit) and Ctrl+D (exit). Tests: 8 new (enter/exit via keybinding, context routing, cursor movement, clamping, copy exits mode, Ctrl+D not swallowed, keys swallowed in mode). CRLF preserved on keybindings.rs and commands.md.
alecuba16
force-pushed
the
pr387-selection
branch
from
September 3, 2026 09:50
72a8aa4 to
63378c9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Split B of #387. Adds keyboard-driven text selection in the transcript pane.
How it works
von an empty prompt to enter selection mode. A cursor bar appears at the bottom of the transcript.↑/↓to extend the selection.PageUp/PageDownscroll by a page,Home/Endjump to the top/bottom.EnterorCtrl+Ccopies the selected text to the clipboard and exits selection mode.Esccancels without copying.Review feedback addressed
Every item from the #387 review is fixed:
von empty prompt eats first char of messages starting withv, shadows vim'svvhandler is gated onvoice_recorder.is_none()and!matches!(vim_mode, Normal/Visual/VisualBlock), mirroring the?help toggle patternKeyContext::Transcriptbypassed — keys were hardcodedKeyCodematchesKeyContext::Transcriptinkeybindings.rs.current_key_context()returnsTranscriptwhenkb_select_modeis activemove_kb_cursor(delta)that clamps tolast_selectable_areabounds and scrolls to keep the cursor visibleventry, arrow/PageUp/Down/Home/End navigation, Enter/Ctrl+C copy, Esc cancelApp::runcode)Homescrolls to bottom becausetotal_message_linesis always 0total_message_linesis now written at render time inrender_messageslast_selectable_areais the whole terminal, not the transcriptrender_appnow setslast_selectable_areatolast_msg_area(falls back to full frame on welcome screen)apply_selection_highlightshows a cursor bar whenanchor == focusinstead of early-returning_ => return falseswallows Ctrl+Dhandle_key_eventwith realKeyEventsFiles changed
crates/core/src/keybindings.rs— Transcript context bindings updated:selectionUp/Down/PageUp/PageDown/GoStart/GoEnd/Copy/Cancel. 2 new core resolver tests.crates/tui/src/app/keys.rs—ventry handler, selection mode guard,current_key_contextreturns Transcript, action handlers, helper methods (enter_kb_selection,exit_kb_selection,move_kb_cursor,update_kb_selection)crates/tui/src/app/mod.rs—kb_select_modeandkb_cursor_rowfieldscrates/tui/src/render.rs—last_selectable_arearestricted tolast_msg_area,total_message_lineswritten at render time,apply_selection_highlightshows cursor bar whenanchor == focuscrates/tui/src/app/tests.rs— 11 new TUI tests (8 synthetic + 3 render pipeline driving realTestBackend/render_app/handle_key_event)docs/commands.md— keyboard selection sectionTests
13 new tests total:
crates/core/src/keybindings.rs):test_transcript_context_resolves_selection_actionsdrives the realKeybindingResolver::new(&UserKeybindings::default())and asserts all 8 Transcript actions (selectionUp/Down/PageUp/PageDown/GoStart/GoEnd/Copy/Cancel) resolve correctly.test_transcript_context_does_not_resolve_chat_actionsverifies context isolation (up in Chat resolves to historyPrev, not selectionUp).v, keys swallowed in selection mode, cancel with Esc, context routing, Ctrl+D not swallowed, cursor movement, clamping at bounds, copy exits mode.TestBackend+Terminal+render_app:test_render_restricts_selectable_area_to_transcriptassertslast_selectable_area.height < terminal.height, matcheslast_msg_area, andtotal_message_lines > 0(was always 0 before).test_render_selectable_area_falls_back_on_empty_transcriptverifies welcome screen fallback.test_kb_selection_full_pipeline_enter_move_copyrenders messages, then drivesv→up→down→enterthroughhandle_key_eventexercising the real keybinding resolver.All 13 pass. Clippy clean. Cargo check clean.
settings_screen::tests::all_entries_returns_expected_settings(expects ≤20 settings, gets 21) is unrelated — fails onmain, this PR does not touch settings.CRLF preservation
keybindings.rsanddocs/commands.mdare CRLF files. All additions use\r\n. No LF churn on untouched lines.