Apply syntax theme colors to Markdown prose in the TUI (#54217)

## What changed

Use the active syntax theme's markup foregrounds for headings, emphasis, strong text, list markers, links, and blockquotes. Preserve existing bold, italic, and underline modifiers, and retain previous styles when a scope provides no foreground.

## Testing

Update rendering snapshots and streaming blockquote assertions for themed colors. Exercise existing fallback expectations with an empty syntax theme.

GitOrigin-RevId: b00aa4af30f73f538ff5f888a0f9b4edbdc15893
This commit is contained in:
Eric Traut
2026-10-15 07:05:58 +00:00
committed by copyberry
parent dffa0deb69
commit e89b7c442c
18 changed files with 155 additions and 87 deletions
@@ -188,11 +188,11 @@ async fn list_spacing_completion_preserves_the_scrolled_reader() -> Result<()> {
let mut after = Buffer::empty(area);
app.transcript_view
.render(area, &mut after, &app.transcript_cells);
assert_eq!(after, before);
assert_eq!(buffer_text(&after), buffer_text(&before));
app.transcript_view.jump_to_latest();
app.transcript_view
.render(area, &mut after, &app.transcript_cells);
assert_eq!(after, before);
assert_eq!(buffer_text(&after), buffer_text(&before));
Ok(())
}
@@ -346,7 +346,9 @@ fn finalized_agent_cell_replays_visualization_link() {
.expect("visualization URL span");
assert_eq!(
url_span.style,
Style::new().fg(crate::style::accent_color()).underlined()
crate::render::highlight::foreground_style_for_scopes(&["markup.underline.link"])
.unwrap()
.underlined()
);
let destinations = lines
.iter()
+42 -16
View File
@@ -38,6 +38,8 @@
//! body rows, or even 3-char-wide columns cannot fit, body rows render as
//! key/value records.
//!
//! Markdown prose uses foregrounds from the active syntax theme's markup scopes,
//! retaining its usual modifiers and falling back when a scope has no foreground.
//! Inline code and local file paths share the active syntax theme's raw-markup foreground.
use crate::markdown_text_merge::DecodedTextMerge;
@@ -133,13 +135,20 @@ impl Default for MarkdownStyles {
impl MarkdownStyles {
fn for_theme(theme: &syntect::highlighting::Theme) -> Self {
let foreground = |scopes: &[&str]| {
foreground_style_for_scopes_with_theme(theme, scopes).unwrap_or_default()
};
Self {
h1: Style::new().bold().underlined(),
h2: Style::new().bold(),
h3: Style::new().bold().italic(),
h4: Style::new().italic(),
h5: Style::new().italic(),
h6: Style::new().italic(),
h1: foreground(&["markup.heading.1.markdown", "markup.heading.markdown"])
.bold()
.underlined(),
h2: foreground(&["markup.heading.2.markdown", "markup.heading.markdown"]).bold(),
h3: foreground(&["markup.heading.3.markdown", "markup.heading.markdown"])
.bold()
.italic(),
h4: foreground(&["markup.heading.4.markdown", "markup.heading.markdown"]).italic(),
h5: foreground(&["markup.heading.5.markdown", "markup.heading.markdown"]).italic(),
h6: foreground(&["markup.heading.6.markdown", "markup.heading.markdown"]).italic(),
code: foreground_style_for_scopes_with_theme(
theme,
&[
@@ -148,13 +157,23 @@ impl MarkdownStyles {
],
)
.unwrap_or_else(|| Style::new().fg(accent_color())),
emphasis: Style::new().italic(),
strong: Style::new().bold(),
emphasis: foreground(&["markup.italic.markdown"]).italic(),
strong: foreground(&["markup.bold.markdown"]).bold(),
strikethrough: Style::new().crossed_out(),
ordered_list_marker: Style::new().light_blue(),
unordered_list_marker: Style::new(),
link: Style::new().fg(accent_color()).underlined(),
blockquote: Style::new().green(),
ordered_list_marker: Style::new()
.light_blue()
.patch(foreground(&["markup.list.numbered.markdown"])),
unordered_list_marker: foreground(&[
"markup.list.unnumbered.markdown",
"markup.list.bullet.markdown",
]),
link: Style::new()
.fg(accent_color())
.patch(foreground(&["markup.underline.link.markdown"]))
.underlined(),
blockquote: Style::new()
.green()
.patch(foreground(&["markup.quote.markdown"])),
}
}
}
@@ -2575,7 +2594,6 @@ mod markdown_render_tests {
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use ratatui::style::Stylize;
use ratatui::text::Text;
fn lines_to_strings(text: &Text<'_>) -> Vec<String> {
@@ -2660,9 +2678,17 @@ mod tests {
assert_eq!(
source.styled_range(0..source.text.len()),
Line::from(vec![
"block quote with ".green(),
"content".green().bold(),
" that should wrap nicely".green(),
Span::styled("block quote with ", MarkdownStyles::default().blockquote),
Span::styled(
"content",
MarkdownStyles::default()
.blockquote
.patch(MarkdownStyles::default().strong)
),
Span::styled(
" that should wrap nicely",
MarkdownStyles::default().blockquote
),
])
);
let lines: Vec<_> = rendered.iter().map(|line| line.line.to_string()).collect();
@@ -3,4 +3,4 @@ source: tui/src/markdown_render/web_links_tests.rs
assertion_line: 37
expression: text
---
Text::from(Line::from_iter([Span::from("界 ("), Span::from("https://example.com/a").fg(Color::Reset).underlined(), Span::from("); "), Span::from("https://example.com/b").fg(Color::Reset).underlined(), Span::from("! "), Span::from("https://example.com/c").fg(Color::Reset).bold().underlined()]))
Text::from(Line::from_iter([Span::from("界 ("), Span::from("https://example.com/a").fg(Color::Rgb(137, 180, 250)).underlined(), Span::from("); "), Span::from("https://example.com/b").fg(Color::Rgb(137, 180, 250)).underlined(), Span::from("! "), Span::from("https://example.com/c").fg(Color::Rgb(137, 180, 250)).bold().underlined()]))
@@ -11,10 +11,10 @@ Text::from_iter([
Line::from(Span::from("━━━━━━━━━━━━━━━━━━━━").dim()),
Line::from_iter([
Span::from(" "),
Span::from("https://").fg(Color::Reset).underlined(),
Span::from("https://").fg(Color::Rgb(137, 180, 250)).underlined(),
]),
Line::from_iter([
Span::from(" "),
Span::from("example.com/docs").fg(Color::Reset).underlined(),
Span::from("example.com/docs").fg(Color::Rgb(137, 180, 250)).underlined(),
]),
])
@@ -6,11 +6,11 @@ expression: presentations
Text::from_iter([
Line::from_iter([
Span::from("plain "),
Span::from("plain").fg(Color::Reset).underlined(),
Span::from("plain").fg(Color::Rgb(137, 180, 250)).underlined(),
Span::from(" "),
Span::from("code").fg(Color::Rgb(166, 227, 161)),
Span::from(" "),
Span::from("code").fg(Color::Reset).underlined(),
Span::from("code").fg(Color::Rgb(137, 180, 250)).underlined(),
]),
Line::default(),
Line::from_iter([
@@ -20,23 +20,23 @@ expression: presentations
Line::from(Span::from("━━━━━━━━━━").dim()),
Line::from_iter([
Span::from(" "),
Span::from("docs").fg(Color::Reset).underlined(),
Span::from("docs").fg(Color::Rgb(137, 180, 250)).underlined(),
]),
]),
Text::from_iter([
Line::from_iter([
Span::from("plain "),
Span::from("plain").fg(Color::Reset).underlined(),
Span::from("plain").fg(Color::Rgb(137, 180, 250)).underlined(),
Span::from(" ("),
Span::from("https://example.com").fg(Color::Reset).underlined(),
Span::from("https://example.com").fg(Color::Rgb(137, 180, 250)).underlined(),
Span::from(")"),
Span::from(" "),
Span::from("code").fg(Color::Rgb(166, 227, 161)),
]),
Line::from_iter([
Span::from("code").fg(Color::Reset).underlined(),
Span::from("code").fg(Color::Rgb(137, 180, 250)).underlined(),
Span::from(" ("),
Span::from("https://example.com").fg(Color::Reset).underlined(),
Span::from("https://example.com").fg(Color::Rgb(137, 180, 250)).underlined(),
Span::from(")"),
]),
Line::default(),
@@ -47,9 +47,9 @@ expression: presentations
Line::from(Span::from("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━").dim()),
Line::from_iter([
Span::from(" "),
Span::from("docs").fg(Color::Reset).underlined(),
Span::from("docs").fg(Color::Rgb(137, 180, 250)).underlined(),
Span::from(" ("),
Span::from("https://example.com/docs").fg(Color::Reset).underlined(),
Span::from("https://example.com/docs").fg(Color::Rgb(137, 180, 250)).underlined(),
Span::from(")"),
]),
]),
@@ -1,4 +1,5 @@
use super::WebLinkDisplay;
use crate::markdown_render::MarkdownStyles;
use crate::markdown_render::render_markdown_lines_with_width_cwd_and_hidden_link_destinations;
use crate::markdown_render::render_streaming_markdown_lines_with_width_and_cwd;
use crate::style::accent_color;
@@ -13,6 +14,7 @@ use pretty_assertions::assert_eq;
use ratatui::style::Style;
use ratatui::style::Stylize;
use ratatui::text::Line;
use ratatui::text::Span;
use ratatui::text::Text;
use std::collections::BTreeSet;
@@ -27,14 +29,11 @@ fn bare_urls_are_styled_without_coloring_surrounding_prose() {
text,
Text::from(Line::from(vec![
"界 (".into(),
"https://example.com/a".fg(accent_color()).underlined(),
Span::styled("https://example.com/a", MarkdownStyles::default().link),
"); ".into(),
"https://example.com/b".fg(accent_color()).underlined(),
Span::styled("https://example.com/b", MarkdownStyles::default().link),
"! ".into(),
"https://example.com/c"
.bold()
.fg(accent_color())
.underlined(),
Span::styled("https://example.com/c", MarkdownStyles::default().link).bold(),
])),
);
assert_debug_snapshot!(text);
@@ -103,27 +102,27 @@ fn supporting_terminals_render_only_the_styled_label_and_keep_its_target() {
for (markdown, label) in [
(
"[label](https://example.com)",
"label".fg(accent_color()).underlined(),
Span::styled("label", MarkdownStyles::default().link),
),
(
"[`label`](https://example.com)",
"label".fg(accent_color()).underlined(),
Span::styled("label", MarkdownStyles::default().link),
),
(
"[**label**](https://example.com)",
"label".fg(accent_color()).bold().underlined(),
Span::styled("label", MarkdownStyles::default().link).bold(),
),
(
"[*label*](https://example.com)",
"label".fg(accent_color()).italic().underlined(),
Span::styled("label", MarkdownStyles::default().link).italic(),
),
(
"[<b>](https://example.com)",
"<b>".fg(accent_color()).underlined(),
Span::styled("<b>", MarkdownStyles::default().link),
),
(
"[https://example.com](https://example.com)",
"https://example.com".fg(accent_color()).underlined(),
Span::styled("https://example.com", MarkdownStyles::default().link),
),
] {
let label_width = label.width();
+28
View File
@@ -1,4 +1,5 @@
use super::MarkdownStyles;
use crate::render::highlight::set_syntax_theme;
use crate::style::accent_color;
use pretty_assertions::assert_eq;
use ratatui::style::Modifier;
@@ -146,6 +147,7 @@ fn paragraph_multiple() {
#[test]
fn headings() {
set_syntax_theme(syntect::highlighting::Theme::default());
let md = "# Heading 1\n## Heading 2\n### Heading 3\n#### Heading 4\n##### Heading 5\n###### Heading 6\n";
let text = render_markdown_text(md);
let expected = Text::from_iter([
@@ -166,6 +168,7 @@ fn headings() {
#[test]
fn blockquote_single() {
set_syntax_theme(syntect::highlighting::Theme::default());
let text = render_markdown_text("> Blockquote");
let expected = Text::from(Line::from_iter(["> ", "Blockquote"]).green());
assert_eq!(text, expected);
@@ -196,6 +199,7 @@ fn blockquote_soft_break() {
#[test]
fn blockquote_multiple_with_break() {
set_syntax_theme(syntect::highlighting::Theme::default());
let text = render_markdown_text("> Blockquote 1\n\n> Blockquote 2\n");
let expected = Text::from_iter([
Line::from_iter(["> ", "Blockquote 1"]).green(),
@@ -207,6 +211,7 @@ fn blockquote_multiple_with_break() {
#[test]
fn blockquote_three_paragraphs_short_lines() {
set_syntax_theme(syntect::highlighting::Theme::default());
let md = "> one\n>\n> two\n>\n> three\n";
let text = render_markdown_text(md);
let expected = Text::from_iter([
@@ -221,6 +226,7 @@ fn blockquote_three_paragraphs_short_lines() {
#[test]
fn blockquote_nested_two_levels() {
set_syntax_theme(syntect::highlighting::Theme::default());
let md = "> Level 1\n>> Level 2\n";
let text = render_markdown_text(md);
let expected = Text::from_iter([
@@ -233,6 +239,7 @@ fn blockquote_nested_two_levels() {
#[test]
fn blockquote_with_list_items() {
set_syntax_theme(syntect::highlighting::Theme::default());
let md = "> - item 1\n> - item 2\n";
let text = render_markdown_text(md);
let expected = Text::from_iter([
@@ -244,6 +251,7 @@ fn blockquote_with_list_items() {
#[test]
fn blockquote_with_ordered_list() {
set_syntax_theme(syntect::highlighting::Theme::default());
let md = "> 1. first\n> 2. second\n";
let text = render_markdown_text(md);
let expected = Text::from_iter([
@@ -265,6 +273,7 @@ fn blockquote_with_ordered_list() {
#[test]
fn blockquote_list_then_nested_blockquote() {
set_syntax_theme(syntect::highlighting::Theme::default());
let md = "> - parent\n> > child\n";
let text = render_markdown_text(md);
let expected = Text::from_iter([
@@ -470,6 +479,7 @@ fn blockquote_with_heading_and_paragraph() {
#[test]
fn blockquote_heading_inherits_heading_style() {
set_syntax_theme(syntect::highlighting::Theme::default());
let text = render_markdown_text("> # test header\n> in blockquote\n");
assert_eq!(
text.lines,
@@ -565,6 +575,7 @@ fn nested_blockquote_with_inline_and_fenced_code() {
#[test]
fn list_unordered_single() {
set_syntax_theme(syntect::highlighting::Theme::default());
let text = render_markdown_text("- List item 1\n");
let expected = Text::from_iter([Line::from_iter(["• ", "List item 1"])]);
assert_eq!(text, expected);
@@ -572,6 +583,7 @@ fn list_unordered_single() {
#[test]
fn list_unordered_multiple() {
set_syntax_theme(syntect::highlighting::Theme::default());
let text = render_markdown_text("- List item 1\n- List item 2\n");
let expected = Text::from_iter([
Line::from_iter(["• ", "List item 1"]),
@@ -599,6 +611,7 @@ fn empty_list_items_keep_their_markers() {
#[test]
fn list_nested() {
set_syntax_theme(syntect::highlighting::Theme::default());
let text = render_markdown_text("- List item 1\n - Nested list item 1\n");
let expected = Text::from_iter([
Line::from_iter(["• ", "List item 1"]),
@@ -627,6 +640,7 @@ fn list_ordered_custom_start() {
#[test]
fn nested_unordered_in_ordered() {
set_syntax_theme(syntect::highlighting::Theme::default());
let md = "1. Outer\n - Inner A\n - Inner B\n2. Next\n";
let text = render_markdown_text(md);
let expected = Text::from_iter([
@@ -641,6 +655,7 @@ fn nested_unordered_in_ordered() {
#[test]
fn nested_ordered_in_unordered() {
set_syntax_theme(syntect::highlighting::Theme::default());
let md = "- Outer\n 1. One\n 2. Two\n- Last\n";
let text = render_markdown_text(md);
let expected = Text::from_iter([
@@ -669,6 +684,7 @@ fn loose_list_item_multiple_paragraphs() {
#[test]
fn tight_item_with_soft_break() {
set_syntax_theme(syntect::highlighting::Theme::default());
let md = "- item line1\n item line2\n";
let text = render_markdown_text(md);
let expected = Text::from_iter([
@@ -680,6 +696,7 @@ fn tight_item_with_soft_break() {
#[test]
fn deeply_nested_mixed_three_levels() {
set_syntax_theme(syntect::highlighting::Theme::default());
let md = "1. A\n - B\n 1. C\n2. D\n";
let text = render_markdown_text(md);
let expected = Text::from_iter([
@@ -802,6 +819,7 @@ fn inline_code_and_file_paths_follow_syntax_theme() {
#[test]
fn strong() {
set_syntax_theme(syntect::highlighting::Theme::default());
assert_eq!(
render_markdown_text("**Strong**"),
Text::from(Line::from("Strong".bold()))
@@ -810,6 +828,7 @@ fn strong() {
#[test]
fn emphasis() {
set_syntax_theme(syntect::highlighting::Theme::default());
assert_eq!(
render_markdown_text("*Emphasis*"),
Text::from(Line::from("Emphasis".italic()))
@@ -826,6 +845,7 @@ fn strikethrough() {
#[test]
fn strong_emphasis() {
set_syntax_theme(syntect::highlighting::Theme::default());
let text = render_markdown_text("**Strong *emphasis***");
let expected = Text::from(Line::from_iter([
"Strong ".bold(),
@@ -836,6 +856,7 @@ fn strong_emphasis() {
#[test]
fn link() {
set_syntax_theme(syntect::highlighting::Theme::default());
let text = render_markdown_text("[Link](https://example.com)");
let expected = Text::from(Line::from_iter([
"Link".fg(accent_color()).underlined(),
@@ -848,6 +869,7 @@ fn link() {
#[test]
fn web_link_labels_use_link_style_and_preserve_inline_formatting() {
set_syntax_theme(syntect::highlighting::Theme::default());
for (label, expected_label) in [
("plain", "plain".fg(accent_color()).underlined()),
("`code`", "code".fg(accent_color()).underlined()),
@@ -875,6 +897,7 @@ fn web_link_labels_use_link_style_and_preserve_inline_formatting() {
#[test]
fn web_link_labels_keep_link_style_in_wrapped_prose_and_tables() {
set_syntax_theme(syntect::highlighting::Theme::default());
let links =
"[plain](https://example.com) [`code`](https://example.com) [<b>](https://example.com)";
for markdown in [
@@ -1210,6 +1233,7 @@ fn file_link_appends_hash_range_when_label_lacks_it() {
#[test]
fn multiline_file_link_label_after_styled_prefix_does_not_panic() {
set_syntax_theme(syntect::highlighting::Theme::default());
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"),
@@ -1250,6 +1274,7 @@ fn file_link_uses_target_path_for_hash_range() {
#[test]
fn url_link_shows_destination() {
set_syntax_theme(syntect::highlighting::Theme::default());
let text = render_markdown_text("[docs](https://example.com/docs)");
let expected = Text::from(Line::from_iter([
"docs".fg(accent_color()).underlined(),
@@ -1739,6 +1764,7 @@ fn ordered_item_with_code_block_and_nested_bullet() {
#[test]
fn nested_five_levels_mixed_lists() {
set_syntax_theme(syntect::highlighting::Theme::default());
let md = "1. First\n - Second level\n 1. Third level (ordered)\n - Fourth level (bullet)\n - Fifth level to test indent consistency\n";
let text = render_markdown_text(md);
let expected = Text::from_iter([
@@ -1787,6 +1813,7 @@ fn html_in_tight_ordered_item_soft_breaks_with_space() {
#[test]
fn html_continuation_paragraph_in_unordered_item_indented() {
set_syntax_theme(syntect::highlighting::Theme::default());
let md = "- Item\n\n <em>continued</em>\n";
let text = render_markdown_text(md);
let expected = Text::from_iter([
@@ -1836,6 +1863,7 @@ fn ordered_item_continuation_paragraph_is_indented() {
#[test]
fn nested_item_continuation_paragraph_is_indented() {
set_syntax_theme(syntect::highlighting::Theme::default());
let md = "1. A\n - B\n\n Continuation for B\n2. C\n";
let text = render_markdown_text(md);
let expected = Text::from_iter([
+24 -11
View File
@@ -225,6 +225,7 @@ pub(crate) fn simulate_stream_markdown_for_tests(
#[cfg(test)]
mod tests {
use super::*;
use crate::render::highlight::foreground_style_for_scopes;
use ratatui::style::Color;
#[tokio::test]
@@ -247,20 +248,20 @@ mod tests {
}
#[tokio::test]
async fn e2e_stream_blockquote_simple_is_green() {
async fn e2e_stream_blockquote_simple_follows_theme() {
let out = super::simulate_stream_markdown_for_tests(&["> Hello\n"], /*finalize*/ true);
assert_eq!(out.len(), 1);
let l = &out[0];
assert_eq!(
l.style.fg,
Some(Color::Green),
"expected blockquote line fg green, got {:?}",
foreground_style_for_scopes(&["markup.quote"]).unwrap().fg,
"expected themed blockquote foreground, got {:?}",
l.style.fg
);
}
#[tokio::test]
async fn e2e_stream_blockquote_nested_is_green() {
async fn e2e_stream_blockquote_nested_follows_theme() {
let out = super::simulate_stream_markdown_for_tests(
&["> Level 1\n>> Level 2\n"],
/*finalize*/ true,
@@ -281,19 +282,31 @@ mod tests {
})
.collect();
assert_eq!(non_blank.len(), 2);
assert_eq!(non_blank[0].style.fg, Some(Color::Green));
assert_eq!(non_blank[1].style.fg, Some(Color::Green));
assert_eq!(
non_blank[0].style.fg,
foreground_style_for_scopes(&["markup.quote"]).unwrap().fg
);
assert_eq!(
non_blank[1].style.fg,
foreground_style_for_scopes(&["markup.quote"]).unwrap().fg
);
}
#[tokio::test]
async fn e2e_stream_blockquote_with_list_items_is_green() {
async fn e2e_stream_blockquote_with_list_items_follows_theme() {
let out = super::simulate_stream_markdown_for_tests(
&["> - item 1\n> - item 2\n"],
/*finalize*/ true,
);
assert_eq!(out.len(), 2);
assert_eq!(out[0].style.fg, Some(Color::Green));
assert_eq!(out[1].style.fg, Some(Color::Green));
assert_eq!(
out[0].style.fg,
foreground_style_for_scopes(&["markup.quote"]).unwrap().fg
);
assert_eq!(
out[1].style.fg,
foreground_style_for_scopes(&["markup.quote"]).unwrap().fg
);
}
#[tokio::test]
@@ -328,7 +341,7 @@ mod tests {
}
#[tokio::test]
async fn e2e_stream_blockquote_wrap_preserves_green_style() {
async fn e2e_stream_blockquote_wrap_preserves_theme_style() {
let long = "> This is a very long quoted line that should wrap across multiple columns to verify style preservation.";
let out = super::simulate_stream_markdown_for_tests(&[long, "\n"], /*finalize*/ true);
// Wrap to a narrow width to force multiple output lines.
@@ -356,7 +369,7 @@ mod tests {
for (i, l) in non_blank.iter().enumerate() {
assert_eq!(
l.spans[0].style.fg,
Some(Color::Green),
foreground_style_for_scopes(&["markup.quote"]).unwrap().fg,
"wrapped line {} should preserve green style, got {:?}",
i,
l.spans[0].style.fg
@@ -3,4 +3,4 @@ source: tui/src/markdown_render_tests.rs
assertion_line: 608
expression: text
---
Text::from(Line::from_iter([Span::from("1. ").light_blue(), Span::from("plain "), Span::from("plain").fg(Color::Reset).underlined(), Span::from(" ("), Span::from("https://example.com").fg(Color::Reset).underlined(), Span::from(")"), Span::from(" "), Span::from("code").fg(Color::Rgb(166, 227, 161)), Span::from(" "), Span::from("code").fg(Color::Reset).underlined(), Span::from(" ("), Span::from("https://example.com").fg(Color::Reset).underlined(), Span::from(")")]))
Text::from(Line::from_iter([Span::from("1. ").light_blue(), Span::from("plain "), Span::from("plain").fg(Color::Rgb(137, 180, 250)).underlined(), Span::from(" ("), Span::from("https://example.com").fg(Color::Rgb(137, 180, 250)).underlined(), Span::from(")"), Span::from(" "), Span::from("code").fg(Color::Rgb(166, 227, 161)), Span::from(" "), Span::from("code").fg(Color::Rgb(137, 180, 250)).underlined(), Span::from(" ("), Span::from("https://example.com").fg(Color::Rgb(137, 180, 250)).underlined(), Span::from(")")]))
@@ -2,4 +2,4 @@
source: tui/src/markdown_render_tests.rs
expression: text
---
Text::from(Line::from_iter([Span::from("plain "), Span::from("plain").fg(Color::Reset).underlined(), Span::from(" ("), Span::from("https://example.com").fg(Color::Reset).underlined(), Span::from(")"), Span::from(" "), Span::from("code").fg(Color::Rgb(166, 227, 161)), Span::from(" "), Span::from("code").fg(Color::Reset).underlined(), Span::from(" ("), Span::from("https://example.com").fg(Color::Reset).underlined(), Span::from(")")]))
Text::from(Line::from_iter([Span::from("plain "), Span::from("plain").fg(Color::Rgb(137, 180, 250)).underlined(), Span::from(" ("), Span::from("https://example.com").fg(Color::Rgb(137, 180, 250)).underlined(), Span::from(")"), Span::from(" "), Span::from("code").fg(Color::Rgb(166, 227, 161)), Span::from(" "), Span::from("code").fg(Color::Rgb(137, 180, 250)).underlined(), Span::from(" ("), Span::from("https://example.com").fg(Color::Rgb(137, 180, 250)).underlined(), Span::from(")")]))
@@ -2,7 +2,7 @@
source: tui/src/tooltips.rs
expression: rendered
---
Line::from_iter([Span::from("Tip:").bold(), Span::from(" Use the "), Span::from("desktop app").bold(), Span::from(". Install it")]) []
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" Use the "), Span::from("desktop app").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(". Install it")]) []
Line::from("from") []
Line::from(Span::from("https://learn.chatgpt.com/docs/linux/linux-app").fg(Color::Reset).underlined()) [TerminalHyperlink { columns: 0..46, destination: "https://learn.chatgpt.com/docs/linux/linux-app", destination_kind: Web }]
Line::from(Span::from("https://learn.chatgpt.com/docs/linux/linux-app").fg(Color::Rgb(137, 180, 250)).underlined()) [TerminalHyperlink { columns: 0..46, destination: "https://learn.chatgpt.com/docs/linux/linux-app", destination_kind: Web }]
Line::from_iter([Span::from("and run "), Span::from("chatgpt").fg(Color::Rgb(166, 227, 161)), Span::from(".")]) []
@@ -2,6 +2,6 @@
source: tui/src/tooltips.rs
expression: rendered
---
Line::from_iter([Span::from("Tip:").bold(), Span::from(" Use the "), Span::from("desktop app").bold(), Span::from(". Run "), Span::from("codex app").fg(Color::Rgb(166, 227, 161))]) []
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" Use the "), Span::from("desktop app").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(". Run "), Span::from("codex app").fg(Color::Rgb(166, 227, 161))]) []
Line::from("to open it. It installs automatically if") []
Line::from("needed.") []
@@ -2,7 +2,7 @@
source: tui/src/tooltips.rs
expression: rendered
---
Line::from_iter([Span::from("Tip:").bold(), Span::from(" Use the "), Span::from("desktop app").bold(), Span::from(". Install it")]) []
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" Use the "), Span::from("desktop app").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(". Install it")]) []
Line::from("from") []
Line::from(Span::from("https://chatgpt.com/codex?app-landing-page=true").fg(Color::Reset).underlined()) [TerminalHyperlink { columns: 0..47, destination: "https://chatgpt.com/codex?app-landing-page=true", destination_kind: Web }]
Line::from(Span::from("https://chatgpt.com/codex?app-landing-page=true").fg(Color::Rgb(137, 180, 250)).underlined()) [TerminalHyperlink { columns: 0..47, destination: "https://chatgpt.com/codex?app-landing-page=true", destination_kind: Web }]
Line::from_iter([Span::from("and run "), Span::from("codex app").fg(Color::Rgb(166, 227, 161)), Span::from(".")]) []
@@ -6,8 +6,8 @@ expression: rendered_cases
[
HyperlinkLine {
line: Line::from_iter([
Span::from("# ").bold().underlined(),
Span::from("Output").bold().underlined(),
Span::from("# ").fg(Color::Rgb(243, 139, 168)).bold().underlined(),
Span::from("Output").fg(Color::Rgb(243, 139, 168)).bold().underlined(),
]),
hyperlinks: [],
},
@@ -17,7 +17,7 @@ expression: rendered_cases
},
HyperlinkLine {
line: Line::from_iter([
Span::from("• "),
Span::from("• ").fg(Color::Rgb(148, 226, 213)),
Span::from("Quarterly Report.xlsx").fg(Color::Rgb(166, 227, 161)),
]),
hyperlinks: [],
@@ -34,8 +34,8 @@ expression: rendered_cases
[
HyperlinkLine {
line: Line::from_iter([
Span::from("# ").bold().underlined(),
Span::from("Output").bold().underlined(),
Span::from("# ").fg(Color::Rgb(243, 139, 168)).bold().underlined(),
Span::from("Output").fg(Color::Rgb(243, 139, 168)).bold().underlined(),
]),
hyperlinks: [],
},
@@ -45,7 +45,7 @@ expression: rendered_cases
},
HyperlinkLine {
line: Line::from_iter([
Span::from("• "),
Span::from("• ").fg(Color::Rgb(148, 226, 213)),
Span::from("Résumé *final* ✨.xlsx").fg(Color::Rgb(166, 227, 161)),
]),
hyperlinks: [],
@@ -6,8 +6,8 @@ expression: render.lines
[
HyperlinkLine {
line: Line::from_iter([
Span::from("# ").bold().underlined(),
Span::from("Heading").bold().underlined(),
Span::from("# ").fg(Color::Rgb(243, 139, 168)).bold().underlined(),
Span::from("Heading").fg(Color::Rgb(243, 139, 168)).bold().underlined(),
]),
hyperlinks: [],
},
@@ -18,7 +18,7 @@ expression: render.lines
HyperlinkLine {
line: Line::from_iter([
Span::from("First paragraph with a "),
Span::from("link").fg(Color::Rgb(99, 168, 248)).underlined(),
Span::from("link").fg(Color::Rgb(137, 180, 250)).underlined(),
]),
hyperlinks: [
TerminalHyperlink {
@@ -31,7 +31,7 @@ expression: render.lines
HyperlinkLine {
line: Line::from_iter([
Span::from("("),
Span::from("https://example.com").fg(Color::Rgb(99, 168, 248)).underlined(),
Span::from("https://example.com").fg(Color::Rgb(137, 180, 250)).underlined(),
Span::from(")"),
Span::from("."),
]),
@@ -73,7 +73,7 @@ expression: render.lines
line: Line::from_iter([
Span::from("> "),
Span::from("Quoted paragraph"),
]).green(),
]).fg(Color::Rgb(245, 194, 231)),
hyperlinks: [],
},
HyperlinkLine {
@@ -12,7 +12,7 @@ expression: render.lines
hyperlinks: [],
},
HyperlinkLine {
line: Line::from(Span::from("Visualization unavailable on this device.").italic()),
line: Line::from(Span::from("Visualization unavailable on this device.").fg(Color::Rgb(243, 139, 168)).italic()),
hyperlinks: [],
},
]
@@ -3,50 +3,50 @@ source: tui/src/tooltips/keybinding_tests.rs
expression: "lines.iter().map(|line| format!(\"{line:?}\")).collect::<Vec<_>>().join(\"\\n\")"
---
Line::from("Default")
Line::from_iter([Span::from("Tip:").bold(), Span::from(" Press "), Span::from("tab").fg(Color::Rgb(166, 227, 161)), Span::from(" to queue a message when a")])
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" Press "), Span::from("tab").fg(Color::Rgb(166, 227, 161)), Span::from(" to queue a message when a")])
Line::from("task is running; otherwise it sends")
Line::from_iter([Span::from("immediately (except "), Span::from("!").fg(Color::Rgb(166, 227, 161)), Span::from(").")])
Line::default()
Line::from_iter([Span::from("Tip:").bold(), Span::from(" Use "), Span::from("/copy").bold(), Span::from(" or press "), Span::from("⌃o").fg(Color::Rgb(166, 227, 161)), Span::from(" to copy the")])
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" Use "), Span::from("/copy").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" or press "), Span::from("⌃o").fg(Color::Rgb(166, 227, 161)), Span::from(" to copy the")])
Line::from("latest agent response as Markdown.")
Line::default()
Line::from_iter([Span::from("Tip:").bold(), Span::from(" Press "), Span::from("⌃t").fg(Color::Rgb(166, 227, 161)), Span::from(" to open the full")])
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" Press "), Span::from("⌃t").fg(Color::Rgb(166, 227, 161)), Span::from(" to open the full")])
Line::from("transcript.")
Line::default()
Line::from_iter([Span::from("Tip:").bold(), Span::from(" Press "), Span::from("f3").fg(Color::Rgb(166, 227, 161)), Span::from(" to search this")])
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" Press "), Span::from("f3").fg(Color::Rgb(166, 227, 161)), Span::from(" to search this")])
Line::from("conversation.")
Line::default()
Line::from_iter([Span::from("Tip:").bold(), Span::from(" Press "), Span::from("⌃g").fg(Color::Rgb(166, 227, 161)), Span::from(" to edit your current draft")])
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" Press "), Span::from("⌃g").fg(Color::Rgb(166, 227, 161)), Span::from(" to edit your current draft")])
Line::from("in an external editor.")
Line::default()
Line::from_iter([Span::from("Tip:").bold(), Span::from(" Press "), Span::from("⌃r").fg(Color::Rgb(166, 227, 161)), Span::from(" to search previously")])
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" Press "), Span::from("⌃r").fg(Color::Rgb(166, 227, 161)), Span::from(" to search previously")])
Line::from("entered prompts.")
Line::default()
Line::from_iter([Span::from("Tip:").bold(), Span::from(" For models with adjustable")])
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" For models with adjustable")])
Line::from_iter([Span::from("reasoning, press "), Span::from("⌥.").fg(Color::Rgb(166, 227, 161)), Span::from(" to increase")])
Line::from_iter([Span::from("reasoning effort or "), Span::from("⌥,").fg(Color::Rgb(166, 227, 161)), Span::from(" to decrease it.")])
Line::default()
Line::from("Remapped")
Line::from_iter([Span::from("Tip:").bold(), Span::from(" Press "), Span::from("⌃x q").fg(Color::Rgb(166, 227, 161)), Span::from(" to queue a message when")])
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" Press "), Span::from("⌃x q").fg(Color::Rgb(166, 227, 161)), Span::from(" to queue a message when")])
Line::from("a task is running; otherwise it sends")
Line::from_iter([Span::from("immediately (except "), Span::from("!").fg(Color::Rgb(166, 227, 161)), Span::from(").")])
Line::default()
Line::from_iter([Span::from("Tip:").bold(), Span::from(" Use "), Span::from("/copy").bold(), Span::from(" or press "), Span::from("⌃x `").fg(Color::Rgb(166, 227, 161)), Span::from(" to copy the")])
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" Use "), Span::from("/copy").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" or press "), Span::from("⌃x `").fg(Color::Rgb(166, 227, 161)), Span::from(" to copy the")])
Line::from("latest agent response as Markdown.")
Line::default()
Line::from_iter([Span::from("Tip:").bold(), Span::from(" Press "), Span::from("⌃x t").fg(Color::Rgb(166, 227, 161)), Span::from(" to open the full")])
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" Press "), Span::from("⌃x t").fg(Color::Rgb(166, 227, 161)), Span::from(" to open the full")])
Line::from("transcript.")
Line::default()
Line::from_iter([Span::from("Tip:").bold(), Span::from(" Press "), Span::from("⌃x f").fg(Color::Rgb(166, 227, 161)), Span::from(" to search this")])
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" Press "), Span::from("⌃x f").fg(Color::Rgb(166, 227, 161)), Span::from(" to search this")])
Line::from("conversation.")
Line::default()
Line::from_iter([Span::from("Tip:").bold(), Span::from(" Press "), Span::from("⌃x e").fg(Color::Rgb(166, 227, 161)), Span::from(" to edit your current")])
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" Press "), Span::from("⌃x e").fg(Color::Rgb(166, 227, 161)), Span::from(" to edit your current")])
Line::from("draft in an external editor.")
Line::default()
Line::from_iter([Span::from("Tip:").bold(), Span::from(" Press "), Span::from("⌃x r").fg(Color::Rgb(166, 227, 161)), Span::from(" to search previously")])
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" Press "), Span::from("⌃x r").fg(Color::Rgb(166, 227, 161)), Span::from(" to search previously")])
Line::from("entered prompts.")
Line::default()
Line::from_iter([Span::from("Tip:").bold(), Span::from(" For models with adjustable")])
Line::from_iter([Span::from("Tip:").fg(Color::Rgb(243, 139, 168)).bold(), Span::from(" For models with adjustable")])
Line::from_iter([Span::from("reasoning, press "), Span::from("⌃x +").fg(Color::Rgb(166, 227, 161)), Span::from(" to increase")])
Line::from_iter([Span::from("reasoning effort or "), Span::from("⌃x -").fg(Color::Rgb(166, 227, 161)), Span::from(" to decrease it.")])
Line::default()