Documents: an open heading keeps its weight and color; front matter keeps its panel
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:
1 parent
bff7ebcd64
commit
1ff993dcb1
5 files changed
+182
-44
No files matched your search
@@ -90,7 +90,7 @@ impl MarkdownDocument {
|
|||||||
block.size.height -= self.room;
|
block.size.height -= self.room;
|
||||||
}
|
}
|
||||||
let view = self.list.viewport_bounds();
|
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]
|
let heights = [block.size.height, view.size.height, self.room]
|
||||||
.map(|height| f32::from(height).round() as u64);
|
.map(|height| f32::from(height).round() as u64);
|
||||||
let moved = (ix, self.edits, self.typed, heights);
|
let moved = (ix, self.edits, self.typed, heights);
|
||||||
|
|||||||
@@ -210,8 +210,10 @@ fn the_source_colors_markdown(cx: &mut TestAppContext) {
|
|||||||
document.editor().read(cx).line_colors("a **b**", cx)
|
document.editor().read(cx).line_colors("a **b**", cx)
|
||||||
});
|
});
|
||||||
assert!(
|
assert!(
|
||||||
colors.iter().any(|(range, style)| *range == (2..7)
|
colors
|
||||||
&& style.font_weight == Some(gpui::FontWeight::SEMIBOLD)),
|
.iter()
|
||||||
|
.any(|(range, style)| *range == (2..7)
|
||||||
|
&& style.font_weight == Some(gpui::FontWeight::BOLD)),
|
||||||
"{colors:?}"
|
"{colors:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
//! An open block keeps its place and size (R7.5).
|
//! 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 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.
|
/// Block 0's bounds closed, then open.
|
||||||
fn closed_then_open<'a>(
|
fn closed_then_open<'a>(
|
||||||
@@ -50,7 +58,17 @@ fn an_open_heading_keeps_its_size(cx: &mut TestAppContext) {
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn an_open_code_block_keeps_its_panel(cx: &mut TestAppContext) {
|
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!(
|
assert!(
|
||||||
near(closed.top(), open.top()) && near(closed.left(), open.left()),
|
near(closed.top(), open.top()) && near(closed.left(), open.left()),
|
||||||
"{closed:?} {open:?}"
|
"{closed:?} {open:?}"
|
||||||
@@ -71,3 +89,100 @@ fn an_open_code_block_keeps_its_panel(cx: &mut TestAppContext) {
|
|||||||
"{panel:?} {closed:?}"
|
"{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)));
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, ClickEvent, Context, ExternalPaths, FontWeight, InteractiveElement,
|
AnyElement, App, ClickEvent, Context, Div, ExternalPaths, FontWeight, InteractiveElement,
|
||||||
IntoElement, KeyBinding, MouseButton, MouseDownEvent, ParentElement, Render,
|
IntoElement, KeyBinding, MouseButton, MouseDownEvent, ParentElement, Render,
|
||||||
StatefulInteractiveElement, Styled, StyledText, Window, actions, div, list,
|
StatefulInteractiveElement, Styled, StyledText, Window, actions, div, list,
|
||||||
prelude::FluentBuilder as _, relative,
|
prelude::FluentBuilder as _, relative,
|
||||||
@@ -13,7 +13,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
forms::{self, Down, Up},
|
forms::{self, Down, Up},
|
||||||
primitives::FocusRing as _,
|
primitives::FocusRing as _,
|
||||||
theme::{ActiveTheme, Radius, TextSize},
|
theme::{ActiveTheme, Radius, TextSize, Theme},
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(markdown_document, [Undo, Redo, Indent, Outdent, Paste]);
|
actions!(markdown_document, [Undo, Redo, Indent, Outdent, Paste]);
|
||||||
@@ -23,6 +23,44 @@ const CONTEXT: &str = "MarkdownDocument";
|
|||||||
/// How far a faded block recedes.
|
/// How far a faded block recedes.
|
||||||
const FADED: f32 = 0.35;
|
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.
|
/// Modes with a reader's block to keep.
|
||||||
fn editing_mode(mode: MarkdownMode) -> bool {
|
fn editing_mode(mode: MarkdownMode) -> bool {
|
||||||
mode == MarkdownMode::Visual
|
mode == MarkdownMode::Visual
|
||||||
@@ -163,37 +201,8 @@ impl MarkdownDocument {
|
|||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let point = Self::point_in_open(cx);
|
let point = Self::point_in_open(cx);
|
||||||
let theme = cx.theme();
|
let kind = block.map(|block| (block.kind, block.nodes.is_some()));
|
||||||
let colors = theme.colors.clone();
|
let text = open_text(kind, self.look.scale, cx.theme());
|
||||||
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,
|
|
||||||
};
|
|
||||||
div()
|
div()
|
||||||
.debug_selector(|| "markdown-open".to_string())
|
.debug_selector(|| "markdown-open".to_string())
|
||||||
.on_mouse_down(MouseButton::Right, point)
|
.on_mouse_down(MouseButton::Right, point)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use gpui::{App, FontWeight};
|
use gpui::App;
|
||||||
use markdown::{Constructs, ParseOptions, mdast};
|
use markdown::{Constructs, ParseOptions, mdast};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
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.
|
/// GitHub's flavor, and math between dollars.
|
||||||
pub(crate) fn options() -> ParseOptions {
|
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.
|
/// Markdown styled as typed; its syntax recedes.
|
||||||
pub fn markdown_highlights(text: &str, cx: &App) -> Vec<(Range<usize>, Highlight)> {
|
pub fn markdown_highlights(text: &str, cx: &App) -> Vec<(Range<usize>, Highlight)> {
|
||||||
let colors = &cx.theme().colors;
|
let colors = &cx.theme().colors;
|
||||||
let plain = Highlight::new(colors.fg);
|
let marks = marks(text);
|
||||||
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()
|
.into_iter()
|
||||||
.map(|(range, mark)| {
|
.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 {
|
let range = match mark {
|
||||||
Mark::Strike => {
|
Mark::Strike => {
|
||||||
let span = &text[range.clone()];
|
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 {
|
let highlight = match mark {
|
||||||
Mark::Strong | Mark::Heading(_) => Highlight {
|
Mark::Strong | Mark::Heading(_) => Highlight {
|
||||||
weight: Some(FontWeight::SEMIBOLD),
|
weight: Some(HEAVY),
|
||||||
..plain
|
..plain
|
||||||
},
|
},
|
||||||
Mark::Emphasis => Highlight {
|
Mark::Emphasis => Highlight {
|
||||||
|
|||||||
Reference in new issue
Block a user