Input Handling
Source attribution: Implementation details traced from
references/aider/at commitb9050e1d,references/codex/at commit4ab44e2c5,references/opencode/at commit7ed449974, andreferences/opentui/at commitf4712b9.
Feature Definition
Section titled “Feature Definition”A terminal-based coding agent needs a sophisticated input system. The user types prompts, navigates history, triggers slash commands, attaches files, pastes code blocks, and occasionally sends Ctrl+C to interrupt a long-running agent turn. All of this happens in a raw terminal where the application receives byte sequences, not semantic key events.
The hard problems are:
- Key disambiguation: Terminals encode keys as ANSI escape sequences.
EscapeandAlt+aboth start with\x1b. Arrow keys with modifiers vary across terminal emulators. The Kitty keyboard protocol fixes this, but only some terminals support it. - Paste detection: Without bracketed paste mode, a pasted block of text arrives as a rapid sequence of individual key events. The agent must distinguish “the user typed 50 characters in 8ms” (paste) from “the user pressed keys individually” (typing). Getting this wrong causes slash command triggers, submit-on-Enter, and mode toggles during pastes.
- Slash commands and autocomplete:
/modelat the start of input should trigger a command popup, not insert a literal slash.@should trigger file/agent autocompletion. These triggers must coexist with normal text input and handle edge cases (slash in the middle of text,@in an email address). - Multiline editing: A prompt editor needs cursor movement, word navigation, line operations, selection, undo/redo, and kill/yank — essentially a minimal text editor. The keybindings must not conflict with the application’s global shortcuts.
- External process handoff: When the user invokes an external editor (Vim,
$EDITOR) or the agent spawns a subprocess, the TUI must release stdin, disable raw mode, and cleanly resume when the external process exits — including flushing any buffered input that leaked across the boundary.
This page covers input capture and keybinding systems. For rendering, see TUI Rendering.
Aider Implementation
Section titled “Aider Implementation”Reference: references/aider/aider/io.py, aider/commands.py | Commit: b9050e1d
Aider delegates all input handling to Python’s prompt_toolkit library. It doesn’t manage raw terminal input directly — prompt_toolkit handles ANSI parsing, keybinding dispatch, completion popups, and multiline editing.
PromptSession Setup
Section titled “PromptSession Setup”The input loop lives in InputOutput.get_input() (io.py, lines 523-734). Session configuration (lines 347-366):
session_kwargs = { "input": self.input, "output": self.output, "lexer": PygmentsLexer(MarkdownLexer), "editing_mode": self.editingmode, # EditingMode.VI or EditingMode.EMACS}if self.editingmode == EditingMode.VI: session_kwargs["cursor"] = ModalCursorShapeConfig()if self.input_history_file is not None: session_kwargs["history"] = FileHistory(self.input_history_file)Aider supports both Emacs and Vi editing modes. In Vi mode, ModalCursorShapeConfig() changes the cursor shape between insert and normal modes. History is persisted to a file via FileHistory.
The prompt call (lines 656-666):
line = self.prompt_session.prompt( show, default=default, completer=completer_instance, reserve_space_for_menu=4, complete_style=CompleteStyle.MULTI_COLUMN, key_bindings=kb, complete_while_typing=True, prompt_continuation=get_continuation,)complete_while_typing=True triggers completion popups as the user types, without requiring Tab. reserve_space_for_menu=4 keeps 4 lines at the bottom for the completion dropdown.
Key Bindings
Section titled “Key Bindings”Key bindings are defined via prompt_toolkit’s decorator API in io.py (lines 575-635):
kb = KeyBindings()
@kb.add(Keys.ControlZ, filter=Condition(lambda: hasattr(signal, "SIGTSTP")))def _(event): event.app.suspend_to_background()
@kb.add("c-space")def _(event): event.current_buffer.insert_text(" ")Registered bindings:
| Binding | Action |
|---|---|
Ctrl+Z | Suspend to background (Unix only, gated by SIGTSTP check) |
Ctrl+Space | Insert literal space (overrides system-level input method toggle) |
Ctrl+Up / Ctrl+Down | Navigate input history backward/forward |
Ctrl+X, Ctrl+E | Open current input in $EDITOR (Emacs-style edit-and-execute) |
Multiline Mode Logic
Section titled “Multiline Mode Logic”Lines 612-634 implement context-dependent Enter behavior:
- Multiline mode (
self.multiline):Enterinserts a newline,Alt+Enter(orMeta+Enter) submits. Vi navigation mode (InputMode.NAVIGATION) overrides this — Enter submits regardless. - Normal mode:
Entersubmits,Alt+Enterinserts a newline.
The toggle is controlled by --multiline flag and the /multiline-mode command.
Slash Command System
Section titled “Slash Command System”Commands are defined in commands.py. Detection (line 255):
def is_command(self, inp): return inp[0] in "/!"The ! prefix runs shell commands. / invokes named commands.
Auto-discovery (lines 276-285): Commands are discovered by reflection — any method named cmd_<name> on the Commands class becomes /name (with underscores replaced by hyphens).
Fuzzy matching (line 300): matching_commands() finds the longest prefix match. /m matches /model if unambiguous.
Tab completion for commands (lines 148-184): The AutoCompleter detects when the buffer starts with / and yields command name completions. For command-specific arguments (e.g., /model gpt-5.2-codex completes model names), each command has a completions_<name>() method.
AutoCompleter
Section titled “AutoCompleter”The AutoCompleter class (io.py, lines 91-227) provides context-aware completions from multiple sources:
- Project file names: Tokenized from disk, cached on first use.
- Symbol names: Extracted via Pygments lexer tokenization of in-context files.
- Command names: The
get_commands()list. - Command-specific arguments: Delegated to per-command completers.
Minimum input threshold: 3 characters before fuzzy matching activates (line 212). Completion style is MULTI_COLUMN — multiple candidates displayed in columns.
Limitations
Section titled “Limitations”Aider’s input handling is entirely constrained by prompt_toolkit’s capabilities. There’s no custom paste detection, no leader key concept, no popup beyond the completion menu, and no TUI-style modal dialogs. This is intentional — Aider targets a simple CLI experience, not a full TUI.
Codex Implementation
Section titled “Codex Implementation”Reference: references/codex/codex-rs/tui/src/ | Commit: 4ab44e2c5
Codex builds a full ratatui-based TUI with custom input handling on top of crossterm. The input pipeline spans seven files: terminal setup, event streaming, key binding definitions, a multiline textarea editor, slash command routing, paste burst detection, and clipboard image support.
Terminal Setup and Event Capture
Section titled “Terminal Setup and Event Capture”tui/src/tui.rs (lines 15-82) initializes the terminal with keyboard enhancement:
PushKeyboardEnhancementFlags( DISAMBIGUATE_ESCAPE_CODES | REPORT_EVENT_TYPES | REPORT_ALTERNATE_KEYS)This enables the Kitty keyboard protocol where supported, giving reliable modifier detection on arrow keys and Enter. Bracketed paste is enabled via EnableBracketedPaste.
Event Stream Architecture
Section titled “Event Stream Architecture”tui/src/tui/event_stream.rs (572 lines) defines the core pipeline:
pub enum TuiEvent { Key(KeyEvent), Paste(String), Draw,}The EventBroker manages a shared crossterm EventStream. It supports pause/resume — when the user launches an external editor or the agent spawns a subprocess, the event stream is dropped (pause_events()). When the process exits, a new stream is created (resume_events()). This prevents stdin contention.
Event mapping (lines 238-260):
| Crossterm Event | TuiEvent | Notes |
|---|---|---|
Event::Key(key) | TuiEvent::Key(key) | Checks for suspend key on Unix |
Event::Paste(s) | TuiEvent::Paste(s) | Bracketed paste content |
Event::Resize | TuiEvent::Draw | Triggers redraw |
Event::FocusGained | Updates terminal focus flag | May trigger palette requery |
Event::FocusLost | Updates terminal focus flag | — |
| Other (mouse, etc.) | Filtered out | — |
Key Binding System
Section titled “Key Binding System”tui/src/key_hint.rs (113 lines) defines the KeyBinding type:
struct KeyBinding(KeyCode, KeyModifiers);Helper constructors: plain(), alt(), shift(), ctrl(), ctrl_alt(). The is_press() method checks if a binding matches a KeyEvent, ignoring Release and Repeat events.
Platform-aware display: macOS shows ⌥ + for Alt, other platforms show alt +. An has_ctrl_or_alt() helper detects AltGr on Windows (where ALT|CONTROL together indicate a local character, not a shortcut).
Composer Input State Machine
Section titled “Composer Input State Machine”tui/src/bottom_pane/chat_composer.rs routes key events through a popup-aware dispatcher:
pub fn handle_key_event(&mut self, key_event: KeyEvent) -> (InputResult, bool) { if !self.input_enabled { return (InputResult::None, false); } let result = match &mut self.active_popup { ActivePopup::Command(_) => self.handle_key_event_with_slash_popup(key_event), ActivePopup::File(_) => self.handle_key_event_with_file_popup(key_event), ActivePopup::Skill(_) => self.handle_key_event_with_skill_popup(key_event), ActivePopup::None => self.handle_key_event_without_popup(key_event), }; self.sync_popups(); result}At most one popup is active at a time. After each key event, sync_popups() checks if the buffer state warrants showing, hiding, or switching popups.
Input result types:
pub enum InputResult { Submitted { text: String, text_elements: Vec<TextElement> }, Queued { text: String, text_elements: Vec<TextElement> }, Command(SlashCommand), CommandWithArgs(SlashCommand, String, Vec<TextElement>), None,}Queued is for messages submitted while a task is running — they wait until the current turn completes.
Top-level composer shortcuts:
| Key | Action |
|---|---|
Enter | Submit message |
Shift+Enter | Insert newline |
Tab | Queue message (during task) or submit (idle) |
Ctrl+C | Clear input + show exit hint |
? | Toggle shortcut overlay |
Esc | Dismiss popup |
Double-press quit: Ctrl+C twice within QUIT_SHORTCUT_TIMEOUT exits the application.
TextArea Editor
Section titled “TextArea Editor”tui/src/bottom_pane/textarea.rs (700+ lines) implements a full multiline editor with Emacs-style keybindings.
Cursor movement:
| Key | Action |
|---|---|
| Arrow keys | Character/line movement |
Ctrl+B/F/P/N | Left/right/up/down (Emacs) |
Alt+Left/Right or Ctrl+Left/Right | Word navigation |
Home / Ctrl+A | Beginning of line |
End / Ctrl+E | End of line |
Editing operations:
| Key | Action |
|---|---|
Backspace / Ctrl+H | Delete backward |
Delete / Ctrl+D | Delete forward |
Alt+Backspace / Ctrl+W | Delete word backward |
Alt+Delete | Delete word forward |
Ctrl+U | Kill to beginning of line (saved to kill buffer) |
Ctrl+K | Kill to end of line (saved to kill buffer) |
Ctrl+Y | Yank from kill buffer |
The kill buffer implements Emacs-style cut/yank semantics. Ctrl+K and Ctrl+U save to the kill buffer; Ctrl+Y pastes it back. This is separate from the system clipboard.
Control code fallbacks: Some terminals send C0 control characters without the CONTROL modifier flag. The textarea handles \u{0002} (^B), \u{0006} (^F), \u{0010} (^P), \u{000E} (^N) as direct movement commands.
Text elements: The textarea tracks annotations (file references, mentions) as byte ranges within the buffer. Editing operations that modify text update these ranges accordingly.
Slash Command System
Section titled “Slash Command System”tui/src/slash_command.rs (177 lines) defines 50+ built-in commands as a SlashCommand enum:
pub enum SlashCommand { Model, Approvals, Permissions, Review, Plan, Diff, New, Resume, Fork, Quit, Logout, // ...}Methods on the enum:
supports_inline_args(): Returnstruefor commands like/review,/plan,/renamethat accept arguments after the command name.available_during_task(): Gates which commands work while the agent is mid-turn.
Command popup (tui/src/bottom_pane/command_popup.rs): Triggered by typing / in the composer.
| Key | Action |
|---|---|
Up / Ctrl+P | Navigate up |
Down / Ctrl+N | Navigate down |
Tab | Auto-complete command name |
Enter | Select command |
Esc | Dismiss |
1, 2, 3 | Numeric shortcut selection |
Paste Burst Detection
Section titled “Paste Burst Detection”tui/src/bottom_pane/paste_burst.rs (572 lines) solves the problem of detecting pastes in terminals that don’t support bracketed paste.
The problem: On Windows terminals, pasted text arrives as a rapid sequence of individual KeyCode::Char events — identical to fast typing. Without detection, a pasted ? would toggle the shortcut overlay, a pasted newline would submit the prompt, and a pasted / would open the command popup.
The solution: A PasteBurst state machine with platform-specific timing thresholds:
| Platform | Char interval | Active timeout | Min chars |
|---|---|---|---|
| Non-Windows | 8ms | 8ms | 3 |
| Windows | 30ms | 60ms | 3 |
If 3+ characters arrive within the char interval window, the state machine transitions to “burst mode” and treats all subsequent rapid characters as paste content.
Edge cases handled:
pending_first_char: The first ASCII character is held briefly to check if a burst follows, preventing flicker from transient mode toggles.retro_chars: Characters already inserted before burst detection retroactively get pulled into the paste buffer.- Non-ASCII and IME input: No hold applied (to avoid dropped-input feeling with CJK input methods).
Clipboard Integration
Section titled “Clipboard Integration”tui/src/clipboard_paste.rs (550 lines) handles image paste from the system clipboard via the arboard crate:
paste_image_as_png(): Reads image data from clipboard, encodes to PNG with dimension capture.- Supports file paths (e.g., from macOS Finder).
- WSL fallback: Calls
powershell.exeorpwshto read Windows clipboard when running under WSL. - Handles Windows path normalization for UNC and
C:\paths.
Suspend/Resume (Unix)
Section titled “Suspend/Resume (Unix)”tui/src/tui/job_control.rs handles Ctrl+Z:
- Detect
SUSPEND_KEYin the event stream. - Drop alt-screen, disable raw mode.
- Send
SIGSTOPto self. - On resume: re-enable raw mode, restore alt-screen.
- Flush stdin via
tcflush(STDIN_FILENO, TCIFLUSH)to clear buffered input.
On Windows, FlushConsoleInputBuffer serves the same purpose after external editor launches.
OpenCode Implementation
Section titled “OpenCode Implementation”Reference: references/opencode/packages/opencode/src/, references/opentui/ | Commit: 7ed449974 (OpenCode), f4712b9 (OpenTUI)
OpenCode’s TUI is built on OpenTUI (a Zig-based terminal rendering engine with a TypeScript binding layer). Input flows from raw terminal bytes through OpenTUI’s key parser, through a priority-based event dispatch system, and into OpenCode’s React-like component tree where keybindings are matched against a configurable schema.
OpenTUI Key Parsing Layer
Section titled “OpenTUI Key Parsing Layer”opentui/packages/core/src/lib/KeyHandler.ts (lines 1-140) captures raw terminal input:
class InternalKeyHandler { processInput(data: string): void { // Parse ANSI sequences via parseKeypress() // Emit standardized KeyEvent objects }}KeyEvent properties:
name: Key name ("escape","return","a")ctrl,meta,shift,super: Modifier flagssequence: Raw ANSI sequencesource:"raw"or"kitty"(keyboard protocol)repeated: Boolean for key repeat eventspreventDefault()/stopPropagation(): Event control methods
Terminal capability negotiation (opentui/packages/core/src/zig/terminal.zig, lines 78-85):
OpenTUI supports the Kitty keyboard protocol with progressive enhancement:
- Bit 0: Disambiguate escape codes
- Bit 1: Report event types (press/repeat/release)
- Bit 2: Report alternate keys
- Bit 3: Report all keys as escape codes
- Bit 4: Report associated text
It also manages mouse tracking (5 levels: none, basic, drag, motion, pixels), bracketed paste, focus tracking, and ModifyOtherKeys.
Event Dispatch Priority
Section titled “Event Dispatch Priority”KeyHandler.ts (lines 152-200) dispatches events in priority order:
- Global handlers (registered via
useKeyboard()) execute first. - If not
stopPropagation()’d, renderable/component handlers execute. - Each handler can call
preventDefault()to block default behavior.
This allows app-level shortcuts (Ctrl+C for copy/exit) to take precedence over component-level input handling.
Keybinding Configuration
Section titled “Keybinding Configuration”OpenCode’s keybinding system is fully user-configurable. The schema is defined in config/config.ts (lines 763-915) with 100+ bindings:
// Selection of defaults:leader: "ctrl+x" // Leader key prefixapp_exit: "ctrl+c,ctrl+d,<leader>q" // Multiple bindings per actioneditor_open: "<leader>e" // Leader key sequencescommand_list: "ctrl+p" // Command paletteinput_submit: "return" // Submit promptinput_newline: "shift+return,ctrl+return,alt+return,ctrl+j"input_paste: "ctrl+v"input_clear: "ctrl+c"Leader key system (cli/cmd/tui/context/keybind.tsx, lines 28-65): The leader key (Ctrl+X by default) enters a two-key sequence mode with a 2-second timeout. While active, the next keypress completes the binding (e.g., Ctrl+X then e triggers “open editor”). Focus is temporarily blurred during leader mode and restored on timeout or completion.
Keybind Matching
Section titled “Keybind Matching”util/keybind.ts provides the core matching logic:
Keybind.parse(key: string): Info[]: Parses config strings like"ctrl+x,alt+y"into arrays ofInfoobjects. Handles the<leader>syntax.Keybind.match(a: Info, b: Info): boolean: Deep equality onname,ctrl,meta,shift,super,leaderfields.Keybind.toString(info: Info): Produces display strings like"ctrl+shift+a".Keybind.fromParsedKey(key: ParsedKey, leader?): Converts OpenTUI’sParsedKeyto OpenCode’sInfotype.
Prompt Component Input Flow
Section titled “Prompt Component Input Flow”The main prompt component (cli/cmd/tui/component/prompt/index.tsx, 1154 lines) handles the highest-level input routing.
onKeyDown handler (line 832):
- Clipboard paste (line 841): Intercepts
ctrl+v, checks for images before falling back to text paste. - Clear input (line 854): Matches
input_clearkeybind, clears text and extmarks. - Exit app (line 864): Matches
app_exitwhen prompt is empty. - Shell mode toggle (line 872):
!at position 0 enters shell mode. - Shell mode exit (line 878): Backspace at position 0 or Escape exits shell mode.
- History navigation (line 888):
history_previousat cursor offset 0 cycles backward;history_nextat cursor end cycles forward. - Autocomplete delegation (line 885): Passes event to autocomplete when visible.
onPaste handler (line 912): Handles bracketed paste with file detection:
- If the pasted text is a file path and the file is an image: encodes as base64 and attaches.
- If the pasted text is 3+ lines or >150 chars: creates a summarized paste reference (collapsed in UI).
- Normalizes CRLF line endings.
onSubmit handler (line 526): Routes by mode:
"exit","quit",":q"→ exit application.- Shell mode →
sdk.client.session.shell(). - Starts with
/and matches registered command →sdk.client.session.command(). - Otherwise →
sdk.client.session.prompt()with expanded parts.
Autocomplete System
Section titled “Autocomplete System”cli/cmd/tui/component/prompt/autocomplete.tsx (668 lines) implements trigger-based autocomplete.
Trigger characters:
@— Agent and file references. Must have whitespace or line start before it./— Slash commands. Must be at position 0 with no spaces between/and cursor.
Navigation within autocomplete popup:
| Key | Action |
|---|---|
Up / Ctrl+P | Move selection up (wraps) |
Down / Ctrl+N | Move selection down (wraps) |
Enter | Select current item |
Tab | Expand directory (for @) or select file |
Escape | Dismiss autocomplete |
Sources:
- Files: Full-text file search via SDK.
- Agents: Available subagents for
@mention. - Slash commands: Registered commands from config and SDK.
Command Palette
Section titled “Command Palette”cli/cmd/tui/component/dialog-command.tsx (149 lines) implements a Ctrl+P command palette:
export type CommandOption = DialogSelectOption<string> & { keybind?: keyof KeybindsConfig, suggested?: boolean, slash?: Slash, hidden?: boolean, enabled?: boolean,}The palette listens globally for the command_list keybind, iterates registered commands, and executes option.onSelect() on match. It also checks all registered command keybinds on every keypress (lines 61-72) — so commands are accessible both via the palette and via their direct keybind.
Textarea Keybinding Mapping
Section titled “Textarea Keybinding Mapping”cli/cmd/tui/component/textarea-keybindings.ts maps OpenCode’s config keybind names to OpenTUI’s textarea action names:
const TEXTAREA_ACTIONS = [ "submit", "newline", "move-left", "move-right", "move-up", "move-down", "select-left", "select-right", "select-up", "select-down", "line-home", "line-end", "buffer-home", "buffer-end", "delete-line", "delete-to-line-end", "delete-to-line-start", "backspace", "delete", "undo", "redo", "word-forward", "word-backward", "delete-word-forward", "delete-word-backward", // ... 30+ actions total]The mapTextareaKeybindings() function converts config key names like input_move_left to OpenTUI action names like "move-left", producing KeyBinding[] arrays for the textarea component.
Global Keyboard Handlers
Section titled “Global Keyboard Handlers”App-level handlers in cli/cmd/tui/app.tsx (lines 214-241):
- Selection copy (Ctrl+C): If text is selected in the output area, copies to clipboard and clears selection instead of triggering exit.
- Escape: Clears selection without copying.
- Other keys: Clear selection and pass through.
- Error screen exit (lines 775-779): Ctrl+C in error state triggers cleanup and exit.
Stash System
Section titled “Stash System”OpenCode has a prompt stash — save the current input, do something else, then restore:
prompt.stash: Save current prompt text.prompt.stash.pop: Restore last stashed prompt.prompt.stash.list: Show all stashed prompts.
Pitfalls & Hard Lessons
Section titled “Pitfalls & Hard Lessons”Paste Burst Timing Is Platform-Dependent
Section titled “Paste Burst Timing Is Platform-Dependent”Codex’s paste burst detector uses 8ms thresholds on Unix and 30ms on Windows. These numbers come from empirical testing — Windows terminal emulators deliver pasted characters more slowly than Unix ones. Too tight a threshold misses pastes; too loose interferes with fast typing. There’s no universal correct value.
Ctrl+C Overloading
Section titled “Ctrl+C Overloading”Every reference implementation overloads Ctrl+C: it means “copy” when text is selected (OpenCode), “clear input” when the prompt has text (Codex, OpenCode), “exit” when the prompt is empty (all three), and “interrupt agent” during a running turn (OpenCode). Getting the priority order wrong creates surprising behavior.
AltGr on Windows
Section titled “AltGr on Windows”Windows sends ALT|CONTROL together for AltGr characters (common in European keyboard layouts). A naive “is Alt or Ctrl pressed?” check incorrectly treats AltGr+@ (typed on a German keyboard) as a keyboard shortcut. Codex’s has_ctrl_or_alt() helper explicitly checks for the AltGr combination and ignores it.
External Editor Handoff
Section titled “External Editor Handoff”When spawning $EDITOR, the TUI must: (1) drop the alt screen, (2) disable raw mode, (3) release stdin, (4) wait for the editor to exit, (5) flush any leaked stdin bytes (tcflush on Unix, FlushConsoleInputBuffer on Windows), (6) re-enable raw mode, (7) restore the alt screen. Missing step 5 causes phantom key events after the editor closes.
Leader Key Timeout vs Focus Management
Section titled “Leader Key Timeout vs Focus Management”OpenCode’s leader key mode temporarily blurs focus from the input field. If the timeout fires while the user is mid-sequence, focus must be restored to exactly where it was — not to a default widget. Codex avoids this complexity by not implementing leader keys.
Slash Command Ambiguity
Section titled “Slash Command Ambiguity”When the user types /m, should the command popup show or should it wait for more input? Aider uses longest-prefix matching (accepts /m if only /model matches). Codex shows a popup immediately on / and lets the user navigate. OpenCode waits for the autocomplete trigger. Each approach has tradeoffs: eager matching is faster for experienced users, popup-based is more discoverable.
Non-ASCII Input and IME
Section titled “Non-ASCII Input and IME”CJK input methods (IME) produce multi-byte characters through a composition sequence. Paste burst detection must not hold characters during IME composition — Codex explicitly skips the hold for non-ASCII characters to avoid the “dropped character” feeling.
OpenOxide Blueprint
Section titled “OpenOxide Blueprint”Event Pipeline Architecture
Section titled “Event Pipeline Architecture”Use crossterm for terminal event capture with Kitty keyboard enhancement. Define a three-stage pipeline:
// Stage 1: Raw terminal eventsenum RawEvent { Key(crossterm::event::KeyEvent), Paste(String), Resize(u16, u16), FocusChange(bool),}
// Stage 2: Semantic events (after paste burst detection)enum InputEvent { KeyPress(KeyBinding), Paste(String), Resize(u16, u16),}
// Stage 3: Application actionsenum Action { Submit(String), Command(SlashCommand, Option<String>), InsertChar(char), CursorMove(Direction), // ...}The paste burst detector sits between stages 1 and 2, coalescing rapid key events into Paste events on terminals without bracketed paste support.
Keybinding System
Section titled “Keybinding System”pub struct KeyMap { bindings: HashMap<ActionId, Vec<KeySequence>>,}
pub enum KeySequence { Single(KeyBinding), Leader(KeyBinding), // Requires leader prefix}
impl KeyMap { pub fn from_config(config: &Config) -> Self; pub fn matches(&self, event: &KeyEvent, leader_active: bool) -> Option<ActionId>;}Store bindings in a HashMap<ActionId, Vec<KeySequence>> for O(1) lookup. Support multi-key leader sequences with a timeout-based state machine. Make all bindings user-configurable via TOML config, with Emacs defaults and an optional Vi mode.
Crates
Section titled “Crates”openoxide-tui-input:KeyMap,KeyBinding,PasteBurstDetector,LeaderState. Pure logic, no rendering dependency.openoxide-tui-textarea: Multiline text editor widget. Emacs and Vi keybinding presets. Kill buffer, word navigation, undo/redo.openoxide-tui-commands:SlashCommandenum, command popup widget, autocomplete trigger logic.openoxide-tui: Integrates all input components into the ratatui application. Manages theEventBrokerfor pause/resume, clipboard integration viaarboard, external editor handoff.
Key Design Decisions
Section titled “Key Design Decisions”- Emacs default, Vi optional: Follow Codex’s approach — Emacs keybindings as default with a
--viflag for Vi mode. Don’t build a custom Vi engine; map Vi-style normal/insert mode concepts to the same underlying action system. - Configurable keybindings: Follow OpenCode’s approach — full keybinding configuration in a
[keybinds]TOML section. Parse"ctrl+x,alt+y"syntax. Support leader key sequences. - Platform-aware paste detection: Follow Codex’s
PasteBurststate machine with platform-specific thresholds. Always enable bracketed paste, but fall back to burst detection when the terminal doesn’t support it. - Slash commands as an enum: Follow Codex — define all commands as variants of a
SlashCommandenum. This gives exhaustive match checking and prevents typos. Usestrumfor string conversion and iteration. - Autocomplete triggers: Follow OpenCode’s
@for files and/for commands. Trigger on character, not on Tab. Show a popup immediately with filtered results. - Clipboard via arboard: Follow Codex — use the
arboardcrate for cross-platform clipboard access. Handle WSL via PowerShell fallback. Support image paste to PNG.