ESC Key Logic Flow

TunaCode TUI - Component Relationships and Event Flow

Architecture Overview

The ESC key handling in TunaCode follows a priority cascade pattern with layered interception. Events flow through the Textual framework's event system, with modal screens having the highest priority, followed by the application-level handler.

Entry Point / Trigger
Decision Node
Action / Handler
Terminal State

Primary ESC Event Flow

User Presses ESC Modal Screen Active? YES Modal Handler on_key() or action_cancel() screen.dismiss() NO action_cancel_request() app.py:343-356 Request Running? YES task.cancel() RETURN NO Shell Running? YES shell.cancel() RETURN NO Editor Has Content? YES editor.clear_input() editor.py:110 NO No Action PRIORITY 1 PRIORITY 2 PRIORITY 3

Component Relationships

TunaApp (Main Application)

src/tunacode/ui/app.py

Central coordinator for all ESC handling. Defines global binding and priority cascade.

  • Line 65: ESC binding with priority=True
  • Lines 343-356: action_cancel_request()
  • Owns references to: Editor, ShellRunner, request task

Editor Widget

src/tunacode/ui/widgets/editor.py

Input widget for user text. Provides clear_input() method called by app.

  • Lines 110-113: clear_input()
  • Line 54-55: has_paste_buffer property
  • No direct ESC handling - delegated to app

ShellRunner

src/tunacode/ui/shell_runner.py

Manages shell command execution. Provides cancellation interface.

  • Lines 119-137: cancel() method
  • Line 111: "Esc to cancel" notification
  • Terminates subprocess on cancel

Modal Screens

src/tunacode/ui/screens/*.py

Pushed onto screen stack. Each handles its own ESC to dismiss.

  • ModelPickerScreen - two-phase ESC
  • ProviderPickerScreen - two-phase ESC
  • SessionPickerScreen - direct dismiss
  • ThemePickerScreen - revert + dismiss

Modal Two-Phase ESC Pattern

Model and Provider picker screens implement a two-phase ESC behavior for better UX:

Implementation Location

# src/tunacode/ui/screens/model_picker.py:160-164 elif event.key == "escape" and self._filter_query: filter_input.value = "" self._filter_query = "" self._rebuild_options() event.stop() # Prevents propagation to action_cancel binding

Complete File Reference

File Lines Component ESC Behavior
ui/app.py 65 Binding Global ESC binding with priority=True
ui/app.py 343-356 Handler Priority cascade: request > shell > editor
ui/widgets/editor.py 110-113 Method clear_input() - clears value and paste buffer
ui/shell_runner.py 119-137 Method cancel() - terminates shell subprocess
ui/screens/model_picker.py 94, 160-164 ProviderPicker Two-phase: clear filter, then dismiss
ui/screens/model_picker.py 212, 289-293 ModelPicker Two-phase: clear filter, then dismiss
ui/screens/session_picker.py 55, 179-181 SessionPicker Direct dismiss with None
ui/screens/theme_picker.py 48, 87-90 ThemePicker Revert theme, dismiss with None
ui/screens/update_confirm.py 46, 68-69 UpdateConfirm Dismiss with False
ui/screens/setup.py 29, 100-102 SetupScreen Skip setup, dismiss with False

Textual Event Propagation

Screen Stack (Modals) Application Layer Widget Layer (Editor, etc.) ESC Event Bubbles up Modal intercepts first (if present) App binding (priority=True) No ESC handling in Editor widget priority=True Ensures app ESC runs before lower-priority widget bindings

Key Takeaways

Priority Cascade Pattern

The action_cancel_request() method uses early returns to implement a clear priority order. First matching condition wins.

Modal Screen Isolation

Modal screens handle their own ESC independently. They intercept the event before it reaches the app layer via event.stop().

No Hidden Chat State

The ChatContainer is always visible. ESC never hides the chat - it only cancels operations or clears input.

Two-Phase UX

Pickers with filters require two ESC presses: first clears filter, second dismisses. This prevents accidental closure.

Generated: 2026-02-02 | TunaCode TUI ESC Key Flow Documentation