Documents: strong text keeps a heading's weight; sizes are tested
CI / platforms (ubuntu-24.04) (push) Waiting to run
CI / site (push) Successful in 32s
CI / platforms (windows-latest) (push) Successful in 4m33s
CI / crate (push) Failing after 6m26s

Headings and strong text share one bold weight, so strong text in a
heading no longer draws lighter. A heading's line height is relative
to its own size, and tests check every level's size against the body,
a larger body and a zoomed document.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-15 14:55:39 -04:00
1 parent acbbe1dbe5
commit 90bd37b550
2 files changed
+155 -77

No files matched your search

+64 -59
View File
@@ -2,8 +2,8 @@ use std::{path::Path, rc::Rc, sync::Arc};
use gpui::{
AnyElement, App, ElementId, Entity, FontStyle, FontWeight, HighlightStyle, InteractiveText,
IntoElement, ParentElement, RenderOnce, SharedString, StrikethroughStyle, Styled, StyledText,
UnderlineStyle, Window, div, prelude::*,
IntoElement, ParentElement, Pixels, RenderOnce, SharedString, StrikethroughStyle, Styled,
StyledText, UnderlineStyle, Window, div, prelude::*, relative,
};
use super::{
@@ -16,7 +16,7 @@ use crate::{
editor::{code_colors, code_colors_with},
navigation::{Anchor, Sections},
primitives::FocusRing as _,
theme::{ActiveTheme, Radius, TextSize},
theme::{ActiveTheme, Palette, Radius, TextSize},
typography::{Latex, failed},
};
@@ -101,6 +101,64 @@ pub(crate) struct Hooks<'a> {
pub cell: Option<&'a OpenCell>,
}
/// Headings and strong text share one weight.
const HEAVY: FontWeight = FontWeight::BOLD;
/// How one inline style draws.
fn highlight(style: &Style, colors: &Palette, rule: Pixels) -> HighlightStyle {
match style {
Style::Strong => HighlightStyle {
font_weight: Some(HEAVY),
..HighlightStyle::default()
},
Style::Emphasis => HighlightStyle {
font_style: Some(FontStyle::Italic),
..HighlightStyle::default()
},
Style::Strike => HighlightStyle {
strikethrough: Some(StrikethroughStyle {
thickness: rule,
color: Some(colors.fg_muted),
}),
..HighlightStyle::default()
},
Style::Code | Style::Math => HighlightStyle {
background_color: Some(colors.sunken),
color: Some(if *style == Style::Math {
colors.syntax.function
} else {
colors.fg
}),
..HighlightStyle::default()
},
Style::Link => HighlightStyle {
color: Some(colors.link),
underline: Some(UnderlineStyle {
thickness: rule,
color: Some(colors.link.opacity(0.4)),
wavy: false,
}),
..HighlightStyle::default()
},
Style::Footnote => HighlightStyle {
color: Some(colors.fg_subtle),
..HighlightStyle::default()
},
Style::Underline => HighlightStyle {
underline: Some(UnderlineStyle {
thickness: rule,
color: None,
wavy: false,
}),
..HighlightStyle::default()
},
Style::Mark => HighlightStyle {
background_color: Some(colors.warning_subtle),
..HighlightStyle::default()
},
}
}
/// Draws nodes under one owner's ids and hooks.
struct Draw<'a> {
id: &'a ElementId,
@@ -135,60 +193,7 @@ impl Draw<'_> {
let styles: Vec<_> = inline
.styles
.iter()
.map(|(range, style)| {
let highlight = match style {
Style::Strong => HighlightStyle {
font_weight: Some(FontWeight::SEMIBOLD),
..HighlightStyle::default()
},
Style::Emphasis => HighlightStyle {
font_style: Some(FontStyle::Italic),
..HighlightStyle::default()
},
Style::Strike => HighlightStyle {
strikethrough: Some(StrikethroughStyle {
thickness: rule,
color: Some(colors.fg_muted),
}),
..HighlightStyle::default()
},
Style::Code | Style::Math => HighlightStyle {
background_color: Some(colors.sunken),
color: Some(if *style == Style::Math {
colors.syntax.function
} else {
colors.fg
}),
..HighlightStyle::default()
},
Style::Link => HighlightStyle {
color: Some(colors.link),
underline: Some(UnderlineStyle {
thickness: rule,
color: Some(colors.link.opacity(0.4)),
wavy: false,
}),
..HighlightStyle::default()
},
Style::Footnote => HighlightStyle {
color: Some(colors.fg_subtle),
..HighlightStyle::default()
},
Style::Underline => HighlightStyle {
underline: Some(UnderlineStyle {
thickness: rule,
color: None,
wavy: false,
}),
..HighlightStyle::default()
},
Style::Mark => HighlightStyle {
background_color: Some(colors.warning_subtle),
..HighlightStyle::default()
},
};
(range.clone(), highlight)
})
.map(|(range, style)| (range.clone(), highlight(style, colors, rule)))
.collect();
let styled =
StyledText::new(inline.text.clone()).with_highlights(crate::editor::stack(styles));
@@ -244,8 +249,8 @@ impl Draw<'_> {
.debug_selector(move || format!("markdown-heading-{ordinal}"))
.pt_2()
.text_size(size)
.line_height(size * 1.3)
.font_weight(FontWeight::BOLD)
.line_height(relative(1.3))
.font_weight(HEAVY)
.text_color(if *level == 6 {
colors.fg_muted
} else {
+91 -18
View File
@@ -1,7 +1,7 @@
use gpui::{Context, IntoElement, Render, TestAppContext, Window, div, prelude::*, px};
use gpui::{Context, IntoElement, Render, TestAppContext, Window, div, prelude::*, px, rems};
use super::MarkdownRenderer;
use crate::theme::Theme;
use super::{HEAVY, MarkdownRenderer, Style, highlight};
use crate::theme::{ActiveTheme, TextSize, Theme};
/// `source` drawn in a 400-point column.
struct Page(&'static str);
@@ -52,10 +52,8 @@ fn alerts_lists_of_headings_and_front_matter_draw(cx: &mut TestAppContext) {
assert!(two.top() > one.top(), "{one:?} {two:?}");
}
#[gpui::test]
fn each_heading_level_stands_above_the_next(cx: &mut TestAppContext) {
cx.update(Theme::init);
let source = "# a
/// One heading of every level.
const HEADINGS: &str = "# a
## a
@@ -64,17 +62,92 @@ fn each_heading_level_stands_above_the_next(cx: &mut TestAppContext) {
#### a
##### a
###### a
";
let (_, cx) = cx.add_window_view(|_, _| Page(source));
/// `source` in a 400-point column, body at `size`.
struct Sized(&'static str, TextSize);
impl Render for Sized {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div()
.w(px(400.0))
.child(MarkdownRenderer::new("page", self.0).text_size(self.1))
}
}
#[gpui::test]
fn headings_step_up_from_the_body_by_typoras_ratios(cx: &mut TestAppContext) {
cx.update(Theme::init);
for body in [TextSize::Base, TextSize::Lg] {
let (_, cx) = cx.add_window_view(move |_, _| Sized(HEADINGS, body));
cx.run_until_parked();
let (base, pad) = cx.update(|window, cx| {
let rem = window.rem_size();
(
cx.theme().text_size(body).to_pixels(rem),
rems(0.5).to_pixels(rem),
)
});
for (ordinal, step) in [2.25, 1.75, 1.5, 1.25, 1.0, 1.0].into_iter().enumerate() {
let selector = Box::leak(format!("markdown-heading-{ordinal}").into_boxed_str());
let high = cx.debug_bounds(selector).expect("drawn").size.height;
let off = high - pad - base * step * 1.3;
assert!(
off.abs() < px(0.5),
"{body:?} level {}: {high:?}",
ordinal + 1
);
}
}
}
#[gpui::test]
fn strong_text_in_a_heading_keeps_its_weight(cx: &mut TestAppContext) {
cx.update(Theme::init);
let (_, cx) = cx.add_window_view(|_, _| {
Page(
"### Plain **Strong** plain
",
)
});
cx.run_until_parked();
let high = |ordinal: usize| {
let selector = Box::leak(format!("markdown-heading-{ordinal}").into_boxed_str());
cx.debug_bounds(selector).expect("drawn").size.height
};
let heights: Vec<_> = (0..5).map(high).collect();
// Level 5 is body size; each above is taller.
assert!(
heights.windows(2).all(|pair| pair[0] > pair[1]),
"{heights:?}"
);
assert!(cx.debug_bounds("markdown-heading-0").is_some(), "drawn");
let strong = cx.update(|_, cx| {
let theme = cx.theme();
highlight(&Style::Strong, &theme.colors, theme.underline_thickness()).font_weight
});
assert_eq!(strong, Some(HEAVY), "strong never lightens a heading");
}
#[gpui::test]
fn a_zoomed_document_scales_its_headings(cx: &mut TestAppContext) {
use crate::documents::{DocumentLook, MarkdownDocument, MarkdownMode};
cx.update(crate::init_for_tests);
let (document, cx) = cx.add_window_view(|window, cx| {
MarkdownDocument::new("### a\n", MarkdownMode::Preview, window, cx)
});
document.update(cx, |document, cx| {
let look = DocumentLook {
scale: 1.5,
..DocumentLook::default()
};
document.set_look(look, cx);
});
cx.run_until_parked();
let (base, pad) = cx.update(|window, cx| {
let rem = window.rem_size();
(
cx.theme().text_size(TextSize::Base).to_pixels(rem),
rems(0.5).to_pixels(rem),
)
});
let high = cx
.debug_bounds("markdown-heading-0")
.expect("drawn")
.size
.height;
let off = high - pad - base * 1.5 * 1.5 * 1.3;
assert!(off.abs() < px(0.5), "{high:?}");
}