feat: Allow pasting newlines (#866)
Noticed that when pasting multi-line blocks, each newline was treated like a new submission. Update tui to handle Paste directly and map newlines to shift+enter. # Test Copied this into clipboard: ``` Do nothing. Explain this repo to me. ``` Pasted in and saw multi-line input. Hitting Enter then submitted the full block.
This commit is contained in:
@@ -21,7 +21,7 @@ codex-ansi-escape = { path = "../ansi-escape" }
|
||||
codex-core = { path = "../core" }
|
||||
codex-common = { path = "../common", features = ["cli", "elapsed"] }
|
||||
color-eyre = "0.6.3"
|
||||
crossterm = "0.28.1"
|
||||
crossterm = { version = "0.28.1", features = ["bracketed-paste"] }
|
||||
mcp-types = { path = "../mcp-types" }
|
||||
ratatui = { version = "0.29.0", features = [
|
||||
"unstable-widget-ref",
|
||||
|
||||
@@ -47,29 +47,52 @@ impl App<'_> {
|
||||
let app_event_tx = app_event_tx.clone();
|
||||
std::thread::spawn(move || {
|
||||
while let Ok(event) = crossterm::event::read() {
|
||||
let app_event = match event {
|
||||
crossterm::event::Event::Key(key_event) => AppEvent::KeyEvent(key_event),
|
||||
crossterm::event::Event::Resize(_, _) => AppEvent::Redraw,
|
||||
match event {
|
||||
crossterm::event::Event::Key(key_event) => {
|
||||
if let Err(e) = app_event_tx.send(AppEvent::KeyEvent(key_event)) {
|
||||
tracing::error!("failed to send key event: {e}");
|
||||
}
|
||||
}
|
||||
crossterm::event::Event::Resize(_, _) => {
|
||||
if let Err(e) = app_event_tx.send(AppEvent::Redraw) {
|
||||
tracing::error!("failed to send resize event: {e}");
|
||||
}
|
||||
}
|
||||
crossterm::event::Event::Mouse(MouseEvent {
|
||||
kind: MouseEventKind::ScrollUp,
|
||||
..
|
||||
}) => {
|
||||
scroll_event_helper.scroll_up();
|
||||
continue;
|
||||
}
|
||||
crossterm::event::Event::Mouse(MouseEvent {
|
||||
kind: MouseEventKind::ScrollDown,
|
||||
..
|
||||
}) => {
|
||||
scroll_event_helper.scroll_down();
|
||||
continue;
|
||||
}
|
||||
crossterm::event::Event::Paste(pasted) => {
|
||||
use crossterm::event::KeyModifiers;
|
||||
|
||||
for ch in pasted.chars() {
|
||||
let key_event = match ch {
|
||||
'\n' | '\r' => {
|
||||
// Represent newline as <Shift+Enter> so that the bottom
|
||||
// pane treats it as a literal newline instead of a submit
|
||||
// action (submission is only triggered on Enter *without*
|
||||
// any modifiers).
|
||||
KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT)
|
||||
}
|
||||
_ => KeyEvent::new(KeyCode::Char(ch), KeyModifiers::empty()),
|
||||
};
|
||||
if let Err(e) = app_event_tx.send(AppEvent::KeyEvent(key_event)) {
|
||||
tracing::error!("failed to send pasted key event: {e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
continue;
|
||||
// Ignore any other events.
|
||||
}
|
||||
};
|
||||
if let Err(e) = app_event_tx.send(app_event) {
|
||||
tracing::error!("failed to send event: {e}");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,7 +2,9 @@ use std::io::Stdout;
|
||||
use std::io::stdout;
|
||||
use std::io::{self};
|
||||
|
||||
use crossterm::event::DisableBracketedPaste;
|
||||
use crossterm::event::DisableMouseCapture;
|
||||
use crossterm::event::EnableBracketedPaste;
|
||||
use crossterm::event::EnableMouseCapture;
|
||||
use ratatui::Terminal;
|
||||
use ratatui::backend::CrosstermBackend;
|
||||
@@ -19,6 +21,7 @@ pub type Tui = Terminal<CrosstermBackend<Stdout>>;
|
||||
pub fn init() -> io::Result<Tui> {
|
||||
execute!(stdout(), EnterAlternateScreen)?;
|
||||
execute!(stdout(), EnableMouseCapture)?;
|
||||
execute!(stdout(), EnableBracketedPaste)?;
|
||||
enable_raw_mode()?;
|
||||
set_panic_hook();
|
||||
Terminal::new(CrosstermBackend::new(stdout()))
|
||||
@@ -35,6 +38,7 @@ fn set_panic_hook() {
|
||||
/// Restore the terminal to its original state
|
||||
pub fn restore() -> io::Result<()> {
|
||||
execute!(stdout(), DisableMouseCapture)?;
|
||||
execute!(stdout(), DisableBracketedPaste)?;
|
||||
execute!(stdout(), LeaveAlternateScreen)?;
|
||||
disable_raw_mode()?;
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user