fix(tui): pasted U+2028 and U+2029 become line breaks

str::lines() splits on neither separator, so a paste carrying them counted
as one line and the invisible characters broke the textarea's width model.
Bare \r, U+2028 and U+2029 now normalize to \n on every paste route (the
prompt, the inline paste key and the previous-message editor); dropped
file paths keep their bytes verbatim because a separator in a filename is
payload.

Ported from upstream 1.0.18 (grok-build prompt_widget normalize_line_breaks).

Claude-Session: https://claude.ai/code/session_01RUqV8HVGXcAETyYbZ2tPQ4
This commit is contained in:
雷电芽衣 committed 2026-09-13 10:16:22 -04:00
1 parent d8609267a1
commit 901203c2b2
5 files changed
+73 -21

No files matched your search

@@ -734,7 +734,8 @@ impl AgentView {
Event::Mouse(mouse) => self.handle_inline_edit_mouse(mouse),
Event::Paste(text) => {
if let Some(ref mut edit) = self.inline_edit {
edit.textarea.insert_str(text);
edit.textarea
.insert_str(&crate::views::prompt_widget::normalize_line_breaks(text));
}
InputOutcome::Changed
}
@@ -344,7 +344,10 @@ impl AgentView {
self.prompt.textarea.begin_undo_group();
group_open = true;
}
if matches!(self.prompt.handle_paste(&to_insert), PromptEvent::Edited) {
if matches!(
self.prompt.insert_dropped_path(&to_insert),
PromptEvent::Edited
) {
inserted_non_image = true;
}
}
@@ -290,6 +290,27 @@ mod tests {
agent
}
/// The previous-message editor is its own paste route; separators normalize there too.
#[test]
fn inline_edit_paste_normalizes_line_separators() {
let mut agent = agent_with_prompt();
assert!(agent.enter_inline_edit(0));
let registry = crate::actions::ActionRegistry::defaults();
agent.handle_input(
&crossterm::event::Event::Paste("a\u{2028}b\u{2029}c".into()),
&registry,
);
let text = agent
.inline_edit
.as_ref()
.expect("editing state")
.textarea
.text()
.to_owned();
assert!(text.contains("a\nb\nc"), "{text:?}");
assert!(!text.contains('\u{2028}'), "{text:?}");
}
/// Entering edit mode on a plain user prompt captures the original text,
/// resolves the rewind target, and selects the entry.
#[test]