Documents: an open heading keeps its weight and color; front matter keeps its panel
CI / platforms (ubuntu-24.04) (push) Waiting to run
CI / site (push) Successful in 31s
CI / platforms (windows-latest) (push) Successful in 4m42s
CI / crate (push) Failing after 12m51s

The field's highlighter gave headings and bold semibold over the drawn
bold, and its plain color hid a sixth-level heading's muted color. It
now uses the drawn weight, and runs inside a sixth-level heading stay
muted. Front matter opens in its mono, muted panel. The open text box
is built by one function, so tests read its background directly.

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-16 14:27:26 -04:00
1 parent bff7ebcd64
commit 1ff993dcb1
5 files changed
+182 -44

No files matched your search

+1 -1
View File
@@ -90,7 +90,7 @@ impl MarkdownDocument {
block.size.height -= self.room;
}
let view = self.list.viewport_bounds();
// A new layout, a resize or the ends' room moves it.
// New layout, resize or end room moves it.
let heights = [block.size.height, view.size.height, self.room]
.map(|height| f32::from(height).round() as u64);
let moved = (ix, self.edits, self.typed, heights);
+4 -2
View File
@@ -210,8 +210,10 @@ fn the_source_colors_markdown(cx: &mut TestAppContext) {
document.editor().read(cx).line_colors("a **b**", cx)
});
assert!(
colors.iter().any(|(range, style)| *range == (2..7)
&& style.font_weight == Some(gpui::FontWeight::SEMIBOLD)),
colors
.iter()
.any(|(range, style)| *range == (2..7)
&& style.font_weight == Some(gpui::FontWeight::BOLD)),
"{colors:?}"
);
}
+118 -3
View File
@@ -1,9 +1,17 @@
//! An open block keeps its place and size (R7.5).
use gpui::{Bounds, Modifiers, Pixels, TestAppContext, VisualTestContext, rems};
use gpui::{
Bounds, Fill, Modifiers, Pixels, StyleRefinement, Styled, TestAppContext, VisualTestContext,
rems,
};
use super::{open, press};
use crate::documents::MarkdownMode;
use crate::documents::{TargetBlock, document::visual::open_text};
use crate::{
documents::{MarkdownMode, markdown_highlights, render::HEAVY},
forms::Highlight,
theme::ActiveTheme,
};
/// Block 0's bounds closed, then open.
fn closed_then_open<'a>(
@@ -50,7 +58,17 @@ fn an_open_heading_keeps_its_size(cx: &mut TestAppContext) {
#[gpui::test]
fn an_open_code_block_keeps_its_panel(cx: &mut TestAppContext) {
let (closed, open, cx) = closed_then_open("```\nlet x = 1;\n```\n", cx);
keeps_its_panel("```\nlet x = 1;\n```\n", cx);
}
#[gpui::test]
fn open_front_matter_keeps_its_panel(cx: &mut TestAppContext) {
keeps_its_panel("---\nkey: value\n---\n\nText.\n", cx);
}
/// Block 0 opens in place, its panel at the closed inset.
fn keeps_its_panel(source: &str, cx: &mut TestAppContext) {
let (closed, open, cx) = closed_then_open(source, cx);
assert!(
near(closed.top(), open.top()) && near(closed.left(), open.left()),
"{closed:?} {open:?}"
@@ -71,3 +89,100 @@ fn an_open_code_block_keeps_its_panel(cx: &mut TestAppContext) {
"{panel:?} {closed:?}"
);
}
/// The highlight on the run that holds `word`.
fn run(source: &str, word: &str, cx: &mut TestAppContext) -> Highlight {
cx.update(crate::init_for_tests);
let at = source.find(word).expect("the word");
cx.update(|cx| {
markdown_highlights(source, cx)
.into_iter()
.rfind(|(range, _)| range.start <= at && at < range.end)
.expect("a run")
.1
})
}
#[gpui::test]
fn an_open_heading_keeps_its_weight(cx: &mut TestAppContext) {
for source in [
"# Heavy words
",
"a **Heavy** b
",
] {
assert_eq!(run(source, "Heavy", cx).weight, Some(HEAVY), "{source}");
}
}
#[gpui::test]
fn an_open_sixth_heading_stays_muted(cx: &mut TestAppContext) {
let muted = cx.update(|cx| {
crate::init_for_tests(cx);
cx.theme().colors.fg_muted
});
for word in ["Quiet", "Loud"] {
let source = "###### Quiet **Loud**
";
assert_eq!(run(source, word, cx).color, muted, "{word}");
}
let fg = cx.update(|cx| cx.theme().colors.fg);
assert_eq!(
run(
"##### Plain
",
"Plain",
cx
)
.color,
fg
);
}
/// The open text box's style for `kind`.
fn styled(kind: TargetBlock, cx: &mut TestAppContext) -> StyleRefinement {
cx.update(crate::init_for_tests);
cx.update(|cx| {
open_text(Some((kind, true)), 1.0, cx.theme())
.style()
.clone()
})
}
#[gpui::test]
fn open_prose_has_no_panel(cx: &mut TestAppContext) {
for kind in [
TargetBlock::Paragraph,
TargetBlock::Heading(2),
TargetBlock::List,
TargetBlock::Quote,
] {
assert_eq!(styled(kind, cx).background, None, "{kind:?}");
}
}
#[gpui::test]
fn open_code_math_and_front_matter_keep_their_panel(cx: &mut TestAppContext) {
let (sunken, muted, mono) = cx.update(|cx| {
crate::init_for_tests(cx);
let theme = cx.theme();
(
theme.colors.sunken,
theme.colors.fg_muted,
theme.mono_family.clone(),
)
});
for kind in [
TargetBlock::Code,
TargetBlock::Math,
TargetBlock::FrontMatter,
] {
assert_eq!(
styled(kind, cx).background,
Some(Fill::from(sunken)),
"{kind:?}"
);
}
let front = styled(TargetBlock::FrontMatter, cx).text;
assert_eq!((front.color, front.font_family), (Some(muted), Some(mono)));
}
+42 -33
View File
@@ -1,5 +1,5 @@
use gpui::{
AnyElement, App, ClickEvent, Context, ExternalPaths, FontWeight, InteractiveElement,
AnyElement, App, ClickEvent, Context, Div, ExternalPaths, FontWeight, InteractiveElement,
IntoElement, KeyBinding, MouseButton, MouseDownEvent, ParentElement, Render,
StatefulInteractiveElement, Styled, StyledText, Window, actions, div, list,
prelude::FluentBuilder as _, relative,
@@ -13,7 +13,7 @@ use crate::{
},
forms::{self, Down, Up},
primitives::FocusRing as _,
theme::{ActiveTheme, Radius, TextSize},
theme::{ActiveTheme, Radius, TextSize, Theme},
};
actions!(markdown_document, [Undo, Redo, Indent, Outdent, Paste]);
@@ -23,6 +23,44 @@ const CONTEXT: &str = "MarkdownDocument";
/// How far a faded block recedes.
const FADED: f32 = 0.35;
/// The open block's text box: its closed font and panel.
pub(super) fn open_text(kind: Option<(TargetBlock, bool)>, scale: f32, theme: &Theme) -> Div {
let colors = &theme.colors;
let body = theme.text_size(TextSize::Base) * scale;
let mono = theme.text_size(TextSize::Sm) * scale;
let text = div()
.debug_selector(|| "markdown-open-text".to_string())
.text_size(body)
.line_height(body * 1.6)
.text_color(colors.fg);
match kind {
Some((TargetBlock::Heading(level), true)) => text
.pt_2()
.text_size(body * heading_step(level))
.line_height(relative(1.3))
.font_weight(HEAVY)
.when(level == 6, |text| text.text_color(colors.fg_muted)),
// Code, math and front matter keep their panel.
Some((kind @ (TargetBlock::Code | TargetBlock::Math | TargetBlock::FrontMatter), true)) => {
text.p_3()
.rounded(theme.radius(Radius::Md))
.bg(colors.sunken)
.when(kind != TargetBlock::Math, |text| {
text.font_family(theme.mono_family.clone()).text_size(mono)
})
.when(kind == TargetBlock::FrontMatter, |text| {
text.text_color(colors.fg_muted)
})
}
// Written as is: mono and muted.
Some((_, false)) => text
.font_family(theme.mono_family.clone())
.line_height(gpui::phi())
.text_color(colors.fg_muted),
_ => text,
}
}
/// Modes with a reader's block to keep.
fn editing_mode(mode: MarkdownMode) -> bool {
mode == MarkdownMode::Visual
@@ -163,37 +201,8 @@ impl MarkdownDocument {
cx: &mut Context<Self>,
) -> AnyElement {
let point = Self::point_in_open(cx);
let theme = cx.theme();
let colors = theme.colors.clone();
let body = theme.text_size(TextSize::Base) * self.look.scale;
let text = div()
.debug_selector(|| "markdown-open-text".to_string())
.text_size(body)
.line_height(body * 1.6)
.text_color(colors.fg);
let text = match block.map(|block| (block.kind, block.nodes.is_some())) {
Some((TargetBlock::Heading(level), true)) => text
.pt_2()
.text_size(body * heading_step(level))
.line_height(relative(1.3))
.font_weight(HEAVY)
.when(level == 6, |text| text.text_color(colors.fg_muted)),
// Code and math keep their panel.
Some((kind @ (TargetBlock::Code | TargetBlock::Math), true)) => text
.p_3()
.rounded(theme.radius(Radius::Md))
.bg(colors.sunken)
.when(kind == TargetBlock::Code, |text| {
text.font_family(theme.mono_family.clone())
.text_size(theme.text_size(TextSize::Sm) * self.look.scale)
}),
// Written as is: mono and muted.
Some((_, false)) => text
.font_family(theme.mono_family.clone())
.line_height(gpui::phi())
.text_color(colors.fg_muted),
_ => text,
};
let kind = block.map(|block| (block.kind, block.nodes.is_some()));
let text = open_text(kind, self.look.scale, cx.theme());
div()
.debug_selector(|| "markdown-open".to_string())
.on_mouse_down(MouseButton::Right, point)
+17 -5
View File
@@ -1,10 +1,10 @@
use std::ops::Range;
use gpui::{App, FontWeight};
use gpui::App;
use markdown::{Constructs, ParseOptions, mdast};
use unicode_segmentation::UnicodeSegmentation;
use crate::{forms::Highlight, theme::ActiveTheme};
use crate::{documents::render::HEAVY, forms::Highlight, theme::ActiveTheme};
/// GitHub's flavor, and math between dollars.
pub(crate) fn options() -> ParseOptions {
@@ -141,10 +141,22 @@ pub fn marks(text: &str) -> Vec<(Range<usize>, Mark)> {
/// Markdown styled as typed; its syntax recedes.
pub fn markdown_highlights(text: &str, cx: &App) -> Vec<(Range<usize>, Highlight)> {
let colors = &cx.theme().colors;
let plain = Highlight::new(colors.fg);
marks(text)
let marks = marks(text);
// A sixth-level heading reads muted, as drawn.
let muted: Vec<Range<usize>> = marks
.iter()
.filter(|(_, mark)| *mark == Mark::Heading(6))
.map(|(range, _)| range.clone())
.collect();
marks
.into_iter()
.map(|(range, mark)| {
let within =
|outer: &Range<usize>| outer.start <= range.start && range.end <= outer.end;
let plain = Highlight::new(match muted.iter().any(within) {
true => colors.fg_muted,
false => colors.fg,
});
let range = match mark {
Mark::Strike => {
let span = &text[range.clone()];
@@ -155,7 +167,7 @@ pub fn markdown_highlights(text: &str, cx: &App) -> Vec<(Range<usize>, Highlight
};
let highlight = match mark {
Mark::Strong | Mark::Heading(_) => Highlight {
weight: Some(FontWeight::SEMIBOLD),
weight: Some(HEAVY),
..plain
},
Mark::Emphasis => Highlight {