From 5954132e76631e63c86840ed4f450d25e0613143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 15 Oct 2026 12:25:50 -0400 Subject: [PATCH] Documents: headings step up from the body, all bold Sizes follow Typora's steps over the body (2.25, 1.75, 1.5, 1.25, 1, 1) so levels 3 and 4 stand out; level 6 is muted; line height fits the size. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/documents/render.rs | 39 +++++++++++++++++++++-------------- src/documents/render/tests.rs | 27 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/documents/render.rs b/src/documents/render.rs index fc731ce..f4a59e7 100644 --- a/src/documents/render.rs +++ b/src/documents/render.rs @@ -109,6 +109,8 @@ struct Draw<'a> { sections: &'a Option>, headings: std::cell::Cell, code: &'a Option, + /// The body's size; headings step up from it. + body: TextSize, /// Text size against the theme's. scale: f32, /// Lists open around the node drawn. @@ -227,24 +229,29 @@ impl Draw<'_> { .child(self.inline(inline, cx)) .into_any_element(), Node::Heading(level, inline) => { - let size = match level { - 1 => TextSize::Xxl, - 2 => TextSize::Xl, - 3 => TextSize::Lg, - _ => TextSize::Md, + // Typora's steps over the body; every level bold. + let step = match level { + 1 => 2.25, + 2 => 1.75, + 3 => 1.5, + 4 => 1.25, + _ => 1.0, }; - let heading = div() - .pt_2() - .text_size(theme.text_size(size) * self.scale) - .font_weight(if *level <= 2 { - FontWeight::SEMIBOLD - } else { - FontWeight::MEDIUM - }) - .text_color(colors.fg) - .child(self.inline(inline, cx)); + let size = theme.text_size(self.body) * step * self.scale; let ordinal = self.headings.get(); self.headings.set(ordinal + 1); + let heading = div() + .debug_selector(move || format!("markdown-heading-{ordinal}")) + .pt_2() + .text_size(size) + .line_height(size * 1.3) + .font_weight(FontWeight::BOLD) + .text_color(if *level == 6 { + colors.fg_muted + } else { + colors.fg + }) + .child(self.inline(inline, cx)); match self.sections { Some(sections) => Anchor::new(sections, anchor(ordinal)) .child(heading) @@ -392,6 +399,7 @@ pub(crate) fn draw_nodes( sections: &None, headings: std::cell::Cell::new(0), code: &None, + body: TextSize::Base, scale, lists: std::cell::Cell::new(0), table: std::cell::Cell::new(false), @@ -417,6 +425,7 @@ impl RenderOnce for MarkdownRenderer { sections: &self.sections, headings: std::cell::Cell::new(0), code: &self.code, + body: self.size, scale: 1.0, lists: std::cell::Cell::new(0), table: std::cell::Cell::new(false), diff --git a/src/documents/render/tests.rs b/src/documents/render/tests.rs index 23246f6..1d9daf3 100644 --- a/src/documents/render/tests.rs +++ b/src/documents/render/tests.rs @@ -51,3 +51,30 @@ 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 + +## a + +### a + +#### a + +##### a +"; + let (_, cx) = cx.add_window_view(|_, _| Page(source)); + 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 the body's size; each above it is taller. + assert!( + heights.windows(2).all(|pair| pair[0] > pair[1]), + "{heights:?}" + ); +}