render local file links from target paths (#13857)

Co-authored-by: Josh McKinney <joshka@openai.com>
This commit is contained in:
pash-openaiandJosh McKinney authored and Michael Bolin committed 2026-03-11 12:33:09 -07:00
1 parent c8446d7cf3
commit da74da6684
9 files changed
+619 -160

No files matched your search

+128 -40
View File
@@ -3,12 +3,18 @@ use ratatui::style::Stylize;
use ratatui::text::Line;
use ratatui::text::Span;
use ratatui::text::Text;
use std::path::Path;
use crate::markdown_render::COLON_LOCATION_SUFFIX_RE;
use crate::markdown_render::HASH_LOCATION_SUFFIX_RE;
use crate::markdown_render::render_markdown_text;
use crate::markdown_render::render_markdown_text_with_width_and_cwd;
use insta::assert_snapshot;
fn render_markdown_text_for_cwd(input: &str, cwd: &Path) -> Text<'static> {
render_markdown_text_with_width_and_cwd(input, None, Some(cwd))
}
#[test]
fn empty() {
assert_eq!(render_markdown_text(""), Text::default());
@@ -661,8 +667,9 @@ fn load_location_suffix_regexes() {
#[test]
fn file_link_hides_destination() {
let text = render_markdown_text(
let text = render_markdown_text_for_cwd(
"[codex-rs/tui/src/markdown_render.rs](/Users/example/code/codex/codex-rs/tui/src/markdown_render.rs)",
Path::new("/Users/example/code/codex"),
);
let expected = Text::from(Line::from_iter(["codex-rs/tui/src/markdown_render.rs".cyan()]));
assert_eq!(text, expected);
@@ -670,97 +677,101 @@ fn file_link_hides_destination() {
#[test]
fn file_link_appends_line_number_when_label_lacks_it() {
let text = render_markdown_text(
let text = render_markdown_text_for_cwd(
"[markdown_render.rs](/Users/example/code/codex/codex-rs/tui/src/markdown_render.rs:74)",
Path::new("/Users/example/code/codex"),
);
let expected = Text::from(Line::from_iter([
"markdown_render.rs".cyan(),
":74".cyan(),
]));
let expected = Text::from(Line::from_iter(["codex-rs/tui/src/markdown_render.rs:74".cyan()]));
assert_eq!(text, expected);
}
#[test]
fn file_link_uses_label_for_line_number() {
let text = render_markdown_text(
"[markdown_render.rs:74](/Users/example/code/codex/codex-rs/tui/src/markdown_render.rs:74)",
fn file_link_keeps_absolute_paths_outside_cwd() {
let text = render_markdown_text_for_cwd(
"[README.md:74](/Users/example/code/codex/README.md:74)",
Path::new("/Users/example/code/codex/codex-rs/tui"),
);
let expected = Text::from(Line::from_iter(["markdown_render.rs:74".cyan()]));
let expected = Text::from(Line::from_iter(["/Users/example/code/codex/README.md:74".cyan()]));
assert_eq!(text, expected);
}
#[test]
fn file_link_appends_hash_anchor_when_label_lacks_it() {
let text = render_markdown_text(
let text = render_markdown_text_for_cwd(
"[markdown_render.rs](file:///Users/example/code/codex/codex-rs/tui/src/markdown_render.rs#L74C3)",
Path::new("/Users/example/code/codex"),
);
let expected = Text::from(Line::from_iter([
"markdown_render.rs".cyan(),
":74:3".cyan(),
]));
let expected =
Text::from(Line::from_iter(["codex-rs/tui/src/markdown_render.rs:74:3".cyan()]));
assert_eq!(text, expected);
}
#[test]
fn file_link_uses_label_for_hash_anchor() {
let text = render_markdown_text(
fn file_link_uses_target_path_for_hash_anchor() {
let text = render_markdown_text_for_cwd(
"[markdown_render.rs#L74C3](file:///Users/example/code/codex/codex-rs/tui/src/markdown_render.rs#L74C3)",
Path::new("/Users/example/code/codex"),
);
let expected = Text::from(Line::from_iter(["markdown_render.rs#L74C3".cyan()]));
let expected =
Text::from(Line::from_iter(["codex-rs/tui/src/markdown_render.rs:74:3".cyan()]));
assert_eq!(text, expected);
}
#[test]
fn file_link_appends_range_when_label_lacks_it() {
let text = render_markdown_text(
let text = render_markdown_text_for_cwd(
"[markdown_render.rs](/Users/example/code/codex/codex-rs/tui/src/markdown_render.rs:74:3-76:9)",
Path::new("/Users/example/code/codex"),
);
let expected = Text::from(Line::from_iter([
"markdown_render.rs".cyan(),
":74:3-76:9".cyan(),
]));
let expected =
Text::from(Line::from_iter(["codex-rs/tui/src/markdown_render.rs:74:3-76:9".cyan()]));
assert_eq!(text, expected);
}
#[test]
fn file_link_uses_label_for_range() {
let text = render_markdown_text(
fn file_link_uses_target_path_for_range() {
let text = render_markdown_text_for_cwd(
"[markdown_render.rs:74:3-76:9](/Users/example/code/codex/codex-rs/tui/src/markdown_render.rs:74:3-76:9)",
Path::new("/Users/example/code/codex"),
);
let expected = Text::from(Line::from_iter(["markdown_render.rs:74:3-76:9".cyan()]));
let expected =
Text::from(Line::from_iter(["codex-rs/tui/src/markdown_render.rs:74:3-76:9".cyan()]));
assert_eq!(text, expected);
}
#[test]
fn file_link_appends_hash_range_when_label_lacks_it() {
let text = render_markdown_text(
let text = render_markdown_text_for_cwd(
"[markdown_render.rs](file:///Users/example/code/codex/codex-rs/tui/src/markdown_render.rs#L74C3-L76C9)",
Path::new("/Users/example/code/codex"),
);
let expected = Text::from(Line::from_iter([
"markdown_render.rs".cyan(),
":74:3-76:9".cyan(),
]));
let expected =
Text::from(Line::from_iter(["codex-rs/tui/src/markdown_render.rs:74:3-76:9".cyan()]));
assert_eq!(text, expected);
}
#[test]
fn multiline_file_link_label_after_styled_prefix_does_not_panic() {
let text = render_markdown_text(
let text = render_markdown_text_for_cwd(
"**bold** plain [foo\nbar](file:///Users/example/code/codex/codex-rs/tui/src/markdown_render.rs#L74C3)",
Path::new("/Users/example/code/codex"),
);
let expected = Text::from_iter([
Line::from_iter(["bold".bold(), " plain ".into(), "foo".cyan()]),
Line::from_iter(["bar".cyan(), ":74:3".cyan()]),
]);
let expected = Text::from(Line::from_iter([
"bold".bold(),
" plain ".into(),
"codex-rs/tui/src/markdown_render.rs:74:3".cyan(),
]));
assert_eq!(text, expected);
}
#[test]
fn file_link_uses_label_for_hash_range() {
let text = render_markdown_text(
fn file_link_uses_target_path_for_hash_range() {
let text = render_markdown_text_for_cwd(
"[markdown_render.rs#L74C3-L76C9](file:///Users/example/code/codex/codex-rs/tui/src/markdown_render.rs#L74C3-L76C9)",
Path::new("/Users/example/code/codex"),
);
let expected = Text::from(Line::from_iter(["markdown_render.rs#L74C3-L76C9".cyan()]));
let expected =
Text::from(Line::from_iter(["codex-rs/tui/src/markdown_render.rs:74:3-76:9".cyan()]));
assert_eq!(text, expected);
}
@@ -778,8 +789,9 @@ fn url_link_shows_destination() {
#[test]
fn markdown_render_file_link_snapshot() {
let text = render_markdown_text(
let text = render_markdown_text_for_cwd(
"See [markdown_render.rs:74](/Users/example/code/codex/codex-rs/tui/src/markdown_render.rs:74).",
Path::new("/Users/example/code/codex"),
);
let rendered = text
.lines
@@ -796,6 +808,82 @@ fn markdown_render_file_link_snapshot() {
assert_snapshot!(rendered);
}
#[test]
fn unordered_list_local_file_link_stays_inline_with_following_text() {
let text = render_markdown_text_with_width_and_cwd(
"- [binary](/Users/example/code/codex/codex-rs/README.md:93): core is the agent/business logic, tui is the terminal UI, exec is the headless automation surface, and cli is the top-level multitool binary.",
Some(72),
Some(Path::new("/Users/example/code/codex")),
);
let rendered = text
.lines
.iter()
.map(|line| {
line.spans
.iter()
.map(|span| span.content.as_ref())
.collect::<String>()
})
.collect::<Vec<_>>();
assert_eq!(
rendered,
vec![
"- codex-rs/README.md:93: core is the agent/business logic, tui is the",
" terminal UI, exec is the headless automation surface, and cli is the",
" top-level multitool binary.",
]
);
}
#[test]
fn unordered_list_local_file_link_soft_break_before_colon_stays_inline() {
let text = render_markdown_text_with_width_and_cwd(
"- [binary](/Users/example/code/codex/codex-rs/README.md:93)\n : core is the agent/business logic.",
Some(72),
Some(Path::new("/Users/example/code/codex")),
);
let rendered = text
.lines
.iter()
.map(|line| {
line.spans
.iter()
.map(|span| span.content.as_ref())
.collect::<String>()
})
.collect::<Vec<_>>();
assert_eq!(
rendered,
vec!["- codex-rs/README.md:93: core is the agent/business logic.",]
);
}
#[test]
fn consecutive_unordered_list_local_file_links_do_not_detach_paths() {
let text = render_markdown_text_with_width_and_cwd(
"- [binary](/Users/example/code/codex/codex-rs/README.md:93)\n : cli is the top-level multitool binary.\n- [expectations](/Users/example/code/codex/codex-rs/core/README.md:1)\n : codex-core owns the real runtime behavior.",
Some(72),
Some(Path::new("/Users/example/code/codex")),
);
let rendered = text
.lines
.iter()
.map(|line| {
line.spans
.iter()
.map(|span| span.content.as_ref())
.collect::<String>()
})
.collect::<Vec<_>>();
assert_eq!(
rendered,
vec![
"- codex-rs/README.md:93: cli is the top-level multitool binary.",
"- codex-rs/core/README.md:1: codex-core owns the real runtime behavior.",
]
);
}
#[test]
fn code_block_known_lang_has_syntax_colors() {
let text = render_markdown_text("```rust\nfn main() {}\n```\n");