Skip to content

Commit 31bd020

Browse files
charliemarsh-oaicopyberry
authored andcommitted
Make URLs clickable in user verification prompts
## What changed Render user verification titles and request details with `HyperlinkText` so URLs carry terminal hyperlinks, including in the waiting-for-verification view. ## Testing Extend the narrow-prompt regression test to verify that wrapped URLs retain their full hyperlink targets before and during verification. Strip OSC 8 annotations when comparing visible text in snapshots. GitOrigin-RevId: 9b2fba963c028eaa2b177220b0487364b44d04bb
1 parent e44b9bb commit 31bd020

2 files changed

Lines changed: 38 additions & 4 deletions

File tree

‎codex-rs/tui/src/bottom_pane/user_verification.rs‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,9 @@ fn user_verification_options(keymap: &ApprovalKeymap) -> Vec<UserVerificationOpt
261261

262262
pub(crate) fn prompt_header(request: &UserVerificationRequest) -> Box<dyn Renderable> {
263263
let mut header = ColumnRenderable::new();
264-
header.push(Paragraph::new(request.title.clone().bold()).wrap(Wrap { trim: false }));
264+
header.push(crate::terminal_hyperlinks::HyperlinkText::new(vec![
265+
request.title.clone().bold().into(),
266+
]));
265267
header.push(Line::from(""));
266268
header.push(request_details(request));
267269
Box::new(header)
@@ -281,7 +283,7 @@ fn request_details(request: &UserVerificationRequest) -> Box<dyn Renderable> {
281283
Line::from(""),
282284
Line::from(request.description.clone()),
283285
]);
284-
Box::new(Paragraph::new(lines).wrap(Wrap { trim: false }))
286+
Box::new(crate::terminal_hyperlinks::HyperlinkText::new(lines))
285287
}
286288

287289
fn waiting_view(request: &UserVerificationRequest, keymap: &ListKeymap) -> Box<dyn Renderable> {

‎codex-rs/tui/src/bottom_pane/user_verification_tests.rs‎

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ fn render_view_lines(view: &UserVerificationView, width: u16, height: u16) -> St
1111
(0..buf.area.height)
1212
.map(|row| {
1313
(0..buf.area.width)
14-
.map(|col| buf[(col, row)].symbol().to_string())
14+
.map(|col| crate::terminal_hyperlinks::strip_osc8(buf[(col, row)].symbol()))
1515
.collect::<String>()
1616
.trim_end()
1717
.to_string()
@@ -157,7 +157,7 @@ fn full_screen_request_is_available_before_and_during_verification() {
157157

158158
#[test]
159159
fn long_url_tokens_remain_visible_in_a_narrow_verification_prompt() {
160-
let (view, _) = make_view_for_request(UserVerificationRequest {
160+
let (mut view, _) = make_view_for_request(UserVerificationRequest {
161161
title: "Approve https://example.com/production/release/candidate?".to_string(),
162162
description: "Deploy https://example.com/production/critical-release-candidate".to_string(),
163163
thread_label: None,
@@ -168,6 +168,38 @@ fn long_url_tokens_remain_visible_in_a_narrow_verification_prompt() {
168168
"user_verification_long_urls",
169169
render_view_lines(&view, /*width*/ 30, view.desired_height(/*width*/ 30))
170170
);
171+
let urls = [
172+
"https://example.com/production/release/candidate?",
173+
"https://example.com/production/critical-release-candidate",
174+
];
175+
for waiting in [false, true] {
176+
if waiting {
177+
view.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
178+
}
179+
let area = Rect::new(0, 0, 30, view.desired_height(/*width*/ 30));
180+
let mut buf = Buffer::empty(area);
181+
view.render(area, &mut buf);
182+
let linked = buf
183+
.content
184+
.iter()
185+
.filter(|cell| cell.symbol().contains("\x1b]8;;"))
186+
.map(|cell| {
187+
assert!(
188+
urls.iter()
189+
.any(|url| cell.symbol().starts_with(&format!("\x1b]8;;{url}\x07")))
190+
);
191+
crate::terminal_hyperlinks::strip_osc8(cell.symbol())
192+
})
193+
.collect::<String>();
194+
assert_eq!(
195+
linked,
196+
if waiting {
197+
urls[1].to_owned()
198+
} else {
199+
urls.concat()
200+
}
201+
);
202+
}
171203
}
172204

173205
#[tokio::test]

0 commit comments

Comments
 (0)