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-core = { path = "../core" }
|
||||||
codex-common = { path = "../common", features = ["cli", "elapsed"] }
|
codex-common = { path = "../common", features = ["cli", "elapsed"] }
|
||||||
color-eyre = "0.6.3"
|
color-eyre = "0.6.3"
|
||||||
crossterm = "0.28.1"
|
crossterm = { version = "0.28.1", features = ["bracketed-paste"] }
|
||||||
mcp-types = { path = "../mcp-types" }
|
mcp-types = { path = "../mcp-types" }
|
||||||
ratatui = { version = "0.29.0", features = [
|
ratatui = { version = "0.29.0", features = [
|
||||||
"unstable-widget-ref",
|
"unstable-widget-ref",
|
||||||
|
|||||||
@@ -47,29 +47,52 @@ impl App<'_> {
|
|||||||
let app_event_tx = app_event_tx.clone();
|
let app_event_tx = app_event_tx.clone();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
while let Ok(event) = crossterm::event::read() {
|
while let Ok(event) = crossterm::event::read() {
|
||||||
let app_event = match event {
|
match event {
|
||||||
crossterm::event::Event::Key(key_event) => AppEvent::KeyEvent(key_event),
|
crossterm::event::Event::Key(key_event) => {
|
||||||
crossterm::event::Event::Resize(_, _) => AppEvent::Redraw,
|
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 {
|
crossterm::event::Event::Mouse(MouseEvent {
|
||||||
kind: MouseEventKind::ScrollUp,
|
kind: MouseEventKind::ScrollUp,
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
scroll_event_helper.scroll_up();
|
scroll_event_helper.scroll_up();
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
crossterm::event::Event::Mouse(MouseEvent {
|
crossterm::event::Event::Mouse(MouseEvent {
|
||||||
kind: MouseEventKind::ScrollDown,
|
kind: MouseEventKind::ScrollDown,
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
scroll_event_helper.scroll_down();
|
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::stdout;
|
||||||
use std::io::{self};
|
use std::io::{self};
|
||||||
|
|
||||||
|
use crossterm::event::DisableBracketedPaste;
|
||||||
use crossterm::event::DisableMouseCapture;
|
use crossterm::event::DisableMouseCapture;
|
||||||
|
use crossterm::event::EnableBracketedPaste;
|
||||||
use crossterm::event::EnableMouseCapture;
|
use crossterm::event::EnableMouseCapture;
|
||||||
use ratatui::Terminal;
|
use ratatui::Terminal;
|
||||||
use ratatui::backend::CrosstermBackend;
|
use ratatui::backend::CrosstermBackend;
|
||||||
@@ -19,6 +21,7 @@ pub type Tui = Terminal<CrosstermBackend<Stdout>>;
|
|||||||
pub fn init() -> io::Result<Tui> {
|
pub fn init() -> io::Result<Tui> {
|
||||||
execute!(stdout(), EnterAlternateScreen)?;
|
execute!(stdout(), EnterAlternateScreen)?;
|
||||||
execute!(stdout(), EnableMouseCapture)?;
|
execute!(stdout(), EnableMouseCapture)?;
|
||||||
|
execute!(stdout(), EnableBracketedPaste)?;
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
set_panic_hook();
|
set_panic_hook();
|
||||||
Terminal::new(CrosstermBackend::new(stdout()))
|
Terminal::new(CrosstermBackend::new(stdout()))
|
||||||
@@ -35,6 +38,7 @@ fn set_panic_hook() {
|
|||||||
/// Restore the terminal to its original state
|
/// Restore the terminal to its original state
|
||||||
pub fn restore() -> io::Result<()> {
|
pub fn restore() -> io::Result<()> {
|
||||||
execute!(stdout(), DisableMouseCapture)?;
|
execute!(stdout(), DisableMouseCapture)?;
|
||||||
|
execute!(stdout(), DisableBracketedPaste)?;
|
||||||
execute!(stdout(), LeaveAlternateScreen)?;
|
execute!(stdout(), LeaveAlternateScreen)?;
|
||||||
disable_raw_mode()?;
|
disable_raw_mode()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user