Let the terminal handle links in expanded questions (#57285)

## Why

Mouse capture prevents the terminal from handling link-opening gestures in expanded questions.

## What changed

Disable mouse capture while questions are expanded and the TUI owns the screen, unless an interactive modal is active. Restore the default capture policy when questions collapse.

## Testing

Add a regression test covering expansion, collapse, non-owned screens, and link visibility with warnings open.

GitOrigin-RevId: 1fd37feb5c9cd374b492218eeef8c80316bb1cf9
This commit is contained in:
Ian MacLeod
2026-10-26 22:04:03 +00:00
committed by copyberry
parent a3eabbbbf9
commit f9c4c3c9a9
3 changed files with 38 additions and 1 deletions
+1 -1
View File
@@ -1296,7 +1296,7 @@ See the Codex keymap documentation for supported actions and examples."
if !tui.is_owned_screen() && tui.is_alt_screen_active() {
app.close_transcript_overlay(tui);
} else if let Err(err) =
tui.set_mouse_capture(crate::tui::MouseCapture::Inherit)
tui.set_mouse_capture(app.chat_widget.mouse_capture(tui.is_owned_screen()))
{
break Err(err.into());
}
+16
View File
@@ -3,9 +3,25 @@
use super::*;
use crate::bottom_pane::QuestionSubmission;
use crate::tui::MouseCapture;
use codex_protocol::items::AsyncUserInputQuestion;
impl ChatWidget {
pub(crate) fn mouse_capture(&self, owned_screen: bool) -> MouseCapture {
if owned_screen
&& !self.bottom_pane.has_active_modal()
&& self
.bottom_pane
.questions
.as_ref()
.is_some_and(|q| q.expanded)
{
MouseCapture::Disabled
} else {
MouseCapture::Inherit
}
}
pub(super) fn take_question_drafts(&mut self) -> Option<Vec<String>> {
if matches!(
self.pending_notification,
@@ -668,6 +668,27 @@ fn open_questions(chat: &mut ChatWidget, options: Option<Vec<String>>) {
render_bottom_popup(chat, /*width*/ 80);
}
#[tokio::test]
async fn expanded_question_yields_mouse_to_terminal_for_links() {
use crate::tui::MouseCapture::Disabled;
use crate::tui::MouseCapture::Inherit;
let (mut chat, _rx, _ops) = make_chatwidget_manual(/*model_override*/ None).await;
let url = "https://example.com/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/r2/overview";
chat.add_async_questions("message", &[question(url, /*options*/ None)]);
assert_eq!(chat.mouse_capture(/*owned_screen*/ true), Inherit);
chat.handle_key_event(KeyEvent::new(KeyCode::Up, KeyModifiers::ALT));
assert_eq!(chat.mouse_capture(/*owned_screen*/ true), Disabled);
assert_eq!(chat.mouse_capture(/*owned_screen*/ false), Inherit);
chat.handle_key_event(KeyEvent::new(KeyCode::Down, KeyModifiers::ALT));
assert_eq!(chat.mouse_capture(/*owned_screen*/ true), Inherit);
chat.handle_key_event(KeyEvent::new(KeyCode::Up, KeyModifiers::ALT));
chat.open_warnings(&[]);
assert!(render_bottom_popup(&chat, /*width*/ 100).contains(url));
assert_eq!(chat.mouse_capture(/*owned_screen*/ true), Disabled);
}
#[tokio::test]
async fn question_queue_key_does_not_steer_the_running_turn() {
let (mut chat, _rx, mut ops) = make_chatwidget_manual(/*model_override*/ None).await;