//! Public wrapper around the internal ChatComposer for simple, reusable text input. //! //! This exposes a minimal interface suitable for other crates (e.g., //! codex-cloud-tasks) to reuse the mature composer behavior: multi-line input, //! paste heuristics, Enter-to-submit, and Shift+Enter for newline. use crossterm::event::KeyEvent; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::widgets::WidgetRef; use std::time::Duration; use crate::app_event::AppEvent; use crate::app_event_sender::AppEventSender; use crate::bottom_pane::ChatComposer; use crate::bottom_pane::InputResult; /// Action returned from feeding a key event into the ComposerInput. pub enum ComposerAction { /// The user submitted the current text (typically via Enter). Contains the submitted text. Submitted(String), /// No submission occurred; UI may need to redraw if `needs_redraw()` returned true. None, } /// A minimal, public wrapper for the internal `ChatComposer` that behaves as a /// reusable text input field with submit semantics. pub struct ComposerInput { inner: ChatComposer, _tx: tokio::sync::mpsc::UnboundedSender, rx: tokio::sync::mpsc::UnboundedReceiver, } impl ComposerInput { /// Create a new composer input with a neutral placeholder. pub fn new() -> Self { let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); let sender = AppEventSender::new(tx.clone()); // `enhanced_keys_supported=true` enables Shift+Enter newline hint/behavior. let inner = ChatComposer::new(true, sender, true, "Compose new task".to_string(), false); Self { inner, _tx: tx, rx } } /// Returns true if the input is empty. pub fn is_empty(&self) -> bool { self.inner.is_empty() } /// Clear the input text. pub fn clear(&mut self) { self.inner.set_text_content(String::new()); } /// Feed a key event into the composer and return a high-level action. pub fn input(&mut self, key: KeyEvent) -> ComposerAction { let action = match self.inner.handle_key_event(key).0 { InputResult::Submitted(text) => ComposerAction::Submitted(text), _ => ComposerAction::None, }; self.drain_app_events(); action } pub fn handle_paste(&mut self, pasted: String) -> bool { let handled = self.inner.handle_paste(pasted); self.drain_app_events(); handled } /// Override the footer hint items displayed under the composer. /// Each tuple is rendered as "