Fix multiline paste in /goal edit (#26047)

Fixes #26025.

## Why
`/goal edit` opens `CustomPromptView`, which did not use the paste-burst
handling that protects the main composer when terminals deliver paste as
rapid key events. On Windows terminals, the first pasted newline could
be treated as Enter-to-submit, truncating the goal edit and leaving the
rest of the paste behind.

## What
This reuses `PasteBurst` in `CustomPromptView` as a lightweight
Enter-suppression detector for paste-like key streams. Characters still
insert directly, explicit paste still goes through the view paste path,
and ordinary text entry still submits on Enter.
This commit is contained in:
Eric Traut authored and GitHub committed 2026-06-03 09:36:50 -07:00
1 parent 8030c36970
commit a2a9e767f7
4 files changed
+160 -24

No files matched your search

+16 -4
View File
@@ -10,7 +10,7 @@
//! paste once enough chars have arrived.
//!
//! This module provides the `PasteBurst` state machine. `ChatComposer` feeds it only "plain"
//! character events (no Ctrl/Alt) and uses its decisions to either:
//! character events (no Ctrl/Alt) and uses the full buffering decisions to either:
//!
//! - briefly hold a first ASCII char (flicker suppression),
//! - buffer a burst as a single pasted string, or
@@ -32,6 +32,10 @@
//! [`PasteBurst::flush_before_modified_input`] to avoid leaving buffered text "stuck", and then
//! [`PasteBurst::clear_window_after_non_char`] so subsequent typing does not get grouped into a
//! previous burst.
//! - Direct-insert callers can skip buffering, use
//! [`PasteBurst::direct_insert_newline_should_insert`] in their Enter handler, and call
//! [`PasteBurst::extend_window`] when Enter or [`PasteBurst::on_plain_char_no_hold`] reports a
//! burst-like stream.
//!
//! # State Variables
//!
@@ -106,10 +110,10 @@
//! - [`PasteBurst::on_plain_char_no_hold`] never holds (used for IME/non-ASCII paths), since
//! holding a non-ASCII character can feel like dropped input.
//!
//! # Contract With `ChatComposer`
//! # Contract With Callers
//!
//! `PasteBurst` does not mutate the UI text buffer on its own. The caller (`ChatComposer`) must
//! interpret decisions and apply the corresponding UI edits:
//! `PasteBurst` does not mutate the UI text buffer on its own. Callers must interpret decisions
//! and apply the corresponding UI edits. `ChatComposer` uses the full buffering contract:
//!
//! - For each plain ASCII `KeyCode::Char`, call [`PasteBurst::on_plain_char`].
//! - [`CharDecision::RetainFirstChar`]: do **not** insert the char into the textarea yet.
@@ -333,6 +337,14 @@ impl PasteBurst {
self.is_active() || in_burst_window
}
/// Decide if Enter should insert a newline for callers that insert chars immediately.
pub fn direct_insert_newline_should_insert(&self, now: Instant) -> bool {
self.newline_should_insert_instead_of_submit(now)
|| self
.last_plain_char_time
.is_some_and(|t| now.duration_since(t) <= PASTE_BURST_CHAR_INTERVAL)
}
/// Keep the burst window alive.
pub fn extend_window(&mut self, now: Instant) {
self.burst_window_until = Some(now + PASTE_ENTER_SUPPRESS_WINDOW);