diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 5cc4fe2ee5..bc2d73fdcf 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -2854,6 +2854,7 @@ dependencies = [ "unicode-segmentation", "unicode-width 0.2.1", "url", + "urlencoding", "uuid", "vt100", "webbrowser", diff --git a/codex-rs/tui/Cargo.toml b/codex-rs/tui/Cargo.toml index 696ab503b3..8c68de0da6 100644 --- a/codex-rs/tui/Cargo.toml +++ b/codex-rs/tui/Cargo.toml @@ -105,6 +105,7 @@ two-face = { version = "0.5", default-features = false, features = ["syntect-def unicode-segmentation = { workspace = true } unicode-width = { workspace = true } url = { workspace = true } +urlencoding = { workspace = true } webbrowser = { workspace = true } uuid = { workspace = true } diff --git a/codex-rs/tui/src/markdown_render.rs b/codex-rs/tui/src/markdown_render.rs index fb6eb6695e..ae680f5db3 100644 --- a/codex-rs/tui/src/markdown_render.rs +++ b/codex-rs/tui/src/markdown_render.rs @@ -789,7 +789,9 @@ fn parse_local_link_target(dest_url: &str) -> Option<(String, Option)> { location_suffix = Some(suffix); } - Some((expand_local_link_path(path_text), location_suffix)) + let decoded_path_text = + urlencoding::decode(path_text).unwrap_or(std::borrow::Cow::Borrowed(path_text)); + Some((expand_local_link_path(&decoded_path_text), location_suffix)) } /// Normalize a hash fragment like `L12` or `L12C3-L14C9` into the display suffix we render. diff --git a/codex-rs/tui/src/markdown_render_tests.rs b/codex-rs/tui/src/markdown_render_tests.rs index 9594cd4840..850d343853 100644 --- a/codex-rs/tui/src/markdown_render_tests.rs +++ b/codex-rs/tui/src/markdown_render_tests.rs @@ -676,6 +676,18 @@ fn file_link_hides_destination() { assert_eq!(text, expected); } +#[test] +fn file_link_decodes_percent_encoded_bare_path_destination() { + let text = render_markdown_text_for_cwd( + "[report](/Users/example/code/codex/Example%20Folder/R%C3%A9sum%C3%A9/report.md)", + Path::new("/Users/example/code/codex"), + ); + let expected = Text::from(Line::from_iter([ + "Example Folder/Résumé/report.md".cyan(), + ])); + assert_eq!(text, expected); +} + #[test] fn file_link_appends_line_number_when_label_lacks_it() { let text = render_markdown_text_for_cwd(