Files
codex/prs/bolinfest/PR-1683.md
2025-09-02 15:17:45 -07:00

14 KiB

PR #1683: fix: crash on resize

Description

Without this, resizing the terminal prints "Error: The cursor position could not be read within a normal duration" and quits the app.

Full Diff

diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs
index 4e2133a6be..e7097e6af0 100644
--- a/codex-rs/tui/src/app.rs
+++ b/codex-rs/tui/src/app.rs
@@ -88,39 +88,51 @@ impl App<'_> {
         {
             let app_event_tx = app_event_tx.clone();
             std::thread::spawn(move || {
-                while let Ok(event) = crossterm::event::read() {
-                    match event {
-                        crossterm::event::Event::Key(key_event) => {
-                            app_event_tx.send(AppEvent::KeyEvent(key_event));
-                        }
-                        crossterm::event::Event::Resize(_, _) => {
-                            app_event_tx.send(AppEvent::RequestRedraw);
-                        }
-                        crossterm::event::Event::Mouse(MouseEvent {
-                            kind: MouseEventKind::ScrollUp,
-                            ..
-                        }) => {
-                            scroll_event_helper.scroll_up();
-                        }
-                        crossterm::event::Event::Mouse(MouseEvent {
-                            kind: MouseEventKind::ScrollDown,
-                            ..
-                        }) => {
-                            scroll_event_helper.scroll_down();
-                        }
-                        crossterm::event::Event::Paste(pasted) => {
-                            // Many terminals convert newlines to \r when
-                            // pasting, e.g. [iTerm2][]. But [tui-textarea
-                            // expects \n][tui-textarea]. This seems like a bug
-                            // in tui-textarea IMO, but work around it for now.
-                            // [tui-textarea]: https://github.com/rhysd/tui-textarea/blob/4d18622eeac13b309e0ff6a55a46ac6706da68cf/src/textarea.rs#L782-L783
-                            // [iTerm2]: https://github.com/gnachman/iTerm2/blob/5d0c0d9f68523cbd0494dad5422998964a2ecd8d/sources/iTermPasteHelper.m#L206-L216
-                            let pasted = pasted.replace("\r", "\n");
-                            app_event_tx.send(AppEvent::Paste(pasted));
-                        }
-                        _ => {
-                            // Ignore any other events.
+                loop {
+                    // This timeout is necessary to avoid holding the event lock
+                    // that crossterm::event::read() acquires. In particular,
+                    // reading the cursor position (crossterm::cursor::position())
+                    // needs to acquire the event lock, and so will fail if it
+                    // can't acquire it within 2 sec. Resizing the terminal
+                    // crashes the app if the cursor position can't be read.
+                    if let Ok(true) = crossterm::event::poll(Duration::from_millis(100)) {
+                        if let Ok(event) = crossterm::event::read() {
+                            match event {
+                                crossterm::event::Event::Key(key_event) => {
+                                    app_event_tx.send(AppEvent::KeyEvent(key_event));
+                                }
+                                crossterm::event::Event::Resize(_, _) => {
+                                    app_event_tx.send(AppEvent::RequestRedraw);
+                                }
+                                crossterm::event::Event::Mouse(MouseEvent {
+                                    kind: MouseEventKind::ScrollUp,
+                                    ..
+                                }) => {
+                                    scroll_event_helper.scroll_up();
+                                }
+                                crossterm::event::Event::Mouse(MouseEvent {
+                                    kind: MouseEventKind::ScrollDown,
+                                    ..
+                                }) => {
+                                    scroll_event_helper.scroll_down();
+                                }
+                                crossterm::event::Event::Paste(pasted) => {
+                                    // Many terminals convert newlines to \r when
+                                    // pasting, e.g. [iTerm2][]. But [tui-textarea
+                                    // expects \n][tui-textarea]. This seems like a bug
+                                    // in tui-textarea IMO, but work around it for now.
+                                    // [tui-textarea]: https://github.com/rhysd/tui-textarea/blob/4d18622eeac13b309e0ff6a55a46ac6706da68cf/src/textarea.rs#L782-L783
+                                    // [iTerm2]: https://github.com/gnachman/iTerm2/blob/5d0c0d9f68523cbd0494dad5422998964a2ecd8d/sources/iTermPasteHelper.m#L206-L216
+                                    let pasted = pasted.replace("\r", "\n");
+                                    app_event_tx.send(AppEvent::Paste(pasted));
+                                }
+                                _ => {
+                                    // Ignore any other events.
+                                }
+                            }
                         }
+                    } else {
+                        // Timeout expired, no `Event` is available
                     }
                 }
             });

Review Comments

codex-rs/tui/src/app.rs

@@ -88,39 +88,51 @@ impl App<'_> {
         {
             let app_event_tx = app_event_tx.clone();
             std::thread::spawn(move || {
-                while let Ok(event) = crossterm::event::read() {
-                    match event {
-                        crossterm::event::Event::Key(key_event) => {
-                            app_event_tx.send(AppEvent::KeyEvent(key_event));
-                        }
-                        crossterm::event::Event::Resize(_, _) => {
-                            app_event_tx.send(AppEvent::RequestRedraw);
-                        }
-                        crossterm::event::Event::Mouse(MouseEvent {
-                            kind: MouseEventKind::ScrollUp,
-                            ..
-                        }) => {
-                            scroll_event_helper.scroll_up();
-                        }
-                        crossterm::event::Event::Mouse(MouseEvent {
-                            kind: MouseEventKind::ScrollDown,
-                            ..
-                        }) => {
-                            scroll_event_helper.scroll_down();
-                        }
-                        crossterm::event::Event::Paste(pasted) => {
-                            // Many terminals convert newlines to \r when
-                            // pasting, e.g. [iTerm2][]. But [tui-textarea
-                            // expects \n][tui-textarea]. This seems like a bug
-                            // in tui-textarea IMO, but work around it for now.
-                            // [tui-textarea]: https://github.com/rhysd/tui-textarea/blob/4d18622eeac13b309e0ff6a55a46ac6706da68cf/src/textarea.rs#L782-L783
-                            // [iTerm2]: https://github.com/gnachman/iTerm2/blob/5d0c0d9f68523cbd0494dad5422998964a2ecd8d/sources/iTermPasteHelper.m#L206-L216
-                            let pasted = pasted.replace("\r", "\n");
-                            app_event_tx.send(AppEvent::Paste(pasted));
-                        }
-                        _ => {
-                            // Ignore any other events.
+                loop {
+                    // This timeout is necessary to avoid holding the event lock
+                    // that crossterm::event::read() acquires. In particular,
+                    // reading the cursor position (crossterm::cursor::position())
+                    // needs to acquire the event lock, and so will fail if it
+                    // can't acquire it within 2 sec. Resizing the terminal
+                    // crashes the app if the cursor position can't be read.
+                    if let Ok(true) = crossterm::event::poll(Duration::from_millis(100)) {

This is...surprising. Maybe link to 9836f0760d/examples/apps/inline/src/main.rs (L98-L118) to cite an example from Ratatui itself?

@@ -88,39 +88,51 @@ impl App<'_> {
         {
             let app_event_tx = app_event_tx.clone();
             std::thread::spawn(move || {
-                while let Ok(event) = crossterm::event::read() {
-                    match event {
-                        crossterm::event::Event::Key(key_event) => {
-                            app_event_tx.send(AppEvent::KeyEvent(key_event));
-                        }
-                        crossterm::event::Event::Resize(_, _) => {
-                            app_event_tx.send(AppEvent::RequestRedraw);
-                        }
-                        crossterm::event::Event::Mouse(MouseEvent {
-                            kind: MouseEventKind::ScrollUp,
-                            ..
-                        }) => {
-                            scroll_event_helper.scroll_up();
-                        }
-                        crossterm::event::Event::Mouse(MouseEvent {
-                            kind: MouseEventKind::ScrollDown,
-                            ..
-                        }) => {
-                            scroll_event_helper.scroll_down();
-                        }
-                        crossterm::event::Event::Paste(pasted) => {
-                            // Many terminals convert newlines to \r when
-                            // pasting, e.g. [iTerm2][]. But [tui-textarea
-                            // expects \n][tui-textarea]. This seems like a bug
-                            // in tui-textarea IMO, but work around it for now.
-                            // [tui-textarea]: https://github.com/rhysd/tui-textarea/blob/4d18622eeac13b309e0ff6a55a46ac6706da68cf/src/textarea.rs#L782-L783
-                            // [iTerm2]: https://github.com/gnachman/iTerm2/blob/5d0c0d9f68523cbd0494dad5422998964a2ecd8d/sources/iTermPasteHelper.m#L206-L216
-                            let pasted = pasted.replace("\r", "\n");
-                            app_event_tx.send(AppEvent::Paste(pasted));
-                        }
-                        _ => {
-                            // Ignore any other events.
+                loop {
+                    // This timeout is necessary to avoid holding the event lock
+                    // that crossterm::event::read() acquires. In particular,
+                    // reading the cursor position (crossterm::cursor::position())
+                    // needs to acquire the event lock, and so will fail if it
+                    // can't acquire it within 2 sec. Resizing the terminal

2 sec or 100ms?

@@ -88,39 +88,51 @@ impl App<'_> {
         {
             let app_event_tx = app_event_tx.clone();
             std::thread::spawn(move || {
-                while let Ok(event) = crossterm::event::read() {
-                    match event {
-                        crossterm::event::Event::Key(key_event) => {
-                            app_event_tx.send(AppEvent::KeyEvent(key_event));
-                        }
-                        crossterm::event::Event::Resize(_, _) => {
-                            app_event_tx.send(AppEvent::RequestRedraw);
-                        }
-                        crossterm::event::Event::Mouse(MouseEvent {
-                            kind: MouseEventKind::ScrollUp,
-                            ..
-                        }) => {
-                            scroll_event_helper.scroll_up();
-                        }
-                        crossterm::event::Event::Mouse(MouseEvent {
-                            kind: MouseEventKind::ScrollDown,
-                            ..
-                        }) => {
-                            scroll_event_helper.scroll_down();
-                        }
-                        crossterm::event::Event::Paste(pasted) => {
-                            // Many terminals convert newlines to \r when
-                            // pasting, e.g. [iTerm2][]. But [tui-textarea
-                            // expects \n][tui-textarea]. This seems like a bug
-                            // in tui-textarea IMO, but work around it for now.
-                            // [tui-textarea]: https://github.com/rhysd/tui-textarea/blob/4d18622eeac13b309e0ff6a55a46ac6706da68cf/src/textarea.rs#L782-L783
-                            // [iTerm2]: https://github.com/gnachman/iTerm2/blob/5d0c0d9f68523cbd0494dad5422998964a2ecd8d/sources/iTermPasteHelper.m#L206-L216
-                            let pasted = pasted.replace("\r", "\n");
-                            app_event_tx.send(AppEvent::Paste(pasted));
-                        }
-                        _ => {
-                            // Ignore any other events.
+                loop {
+                    // This timeout is necessary to avoid holding the event lock
+                    // that crossterm::event::read() acquires. In particular,
+                    // reading the cursor position (crossterm::cursor::position())
+                    // needs to acquire the event lock, and so will fail if it
+                    // can't acquire it within 2 sec. Resizing the terminal
+                    // crashes the app if the cursor position can't be read.
+                    if let Ok(true) = crossterm::event::poll(Duration::from_millis(100)) {

Also, it looks like that example is doing something slightly different: should we be modeling it more closely?