fix(tui): route bracketed paste to provider picker key entry instead of composer (#342)
Add handle_paste(text) -> ViewAction method to the ModalView trait with a default no-op. ProviderPickerView overrides it in KeyEntry stage to sanitize and append pasted text to api_key_input (rejecting whitespace in the same way as the Char handler). Wire into the Event::Paste handler in ui.rs: before falling through to app.insert_paste_text(), check view_stack.handle_paste(). If the top modal consumes the paste, skip the composer entirely. If a modal is open but does NOT consume the paste, also skip the composer — any modal that receives paste while focused should handle it, not leak into the chat input.
This commit is contained in:
@@ -246,6 +246,16 @@ impl ModalView for ProviderPickerView {
|
||||
self
|
||||
}
|
||||
|
||||
fn handle_paste(&mut self, text: &str) -> ViewAction {
|
||||
if self.stage == Stage::KeyEntry {
|
||||
let sanitized: String = text.chars().filter(|c| !c.is_whitespace()).collect();
|
||||
if !sanitized.is_empty() {
|
||||
self.api_key_input.push_str(&sanitized);
|
||||
}
|
||||
}
|
||||
ViewAction::None
|
||||
}
|
||||
|
||||
fn handle_key(&mut self, key: KeyEvent) -> ViewAction {
|
||||
match self.stage {
|
||||
Stage::List => match key.code {
|
||||
|
||||
@@ -1240,6 +1240,10 @@ async fn run_event_loop(
|
||||
sync_api_key_validation_status(app, false);
|
||||
} else if app.is_history_search_active() {
|
||||
app.history_search_insert_str(text);
|
||||
} else if app.view_stack.handle_paste(text) {
|
||||
// Modal consumed the paste (e.g. provider picker key entry)
|
||||
} else if !app.view_stack.is_empty() {
|
||||
// A non-consumed modal is open — don't leak paste into composer
|
||||
} else {
|
||||
// Paste into main input
|
||||
app.insert_paste_text(text);
|
||||
|
||||
@@ -173,6 +173,9 @@ pub enum ViewAction {
|
||||
pub trait ModalView: std::any::Any {
|
||||
fn kind(&self) -> ModalKind;
|
||||
fn handle_key(&mut self, key: KeyEvent) -> ViewAction;
|
||||
fn handle_paste(&mut self, _text: &str) -> ViewAction {
|
||||
ViewAction::None
|
||||
}
|
||||
fn handle_mouse(&mut self, _mouse: MouseEvent) -> ViewAction {
|
||||
ViewAction::None
|
||||
}
|
||||
@@ -245,6 +248,13 @@ impl ViewStack {
|
||||
self.apply_action(action)
|
||||
}
|
||||
|
||||
pub fn handle_paste(&mut self, text: &str) -> bool {
|
||||
self.views
|
||||
.last_mut()
|
||||
.map(|view| matches!(view.handle_paste(text), ViewAction::None))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn handle_mouse(&mut self, mouse: MouseEvent) -> Vec<ViewEvent> {
|
||||
let action = self
|
||||
.views
|
||||
|
||||
Reference in New Issue
Block a user