T02 typography: text styles, keys, formatters, fit, links, math, motion text

Headings to overlines, Code, Kbd and KbdCombo on gpui keystrokes,
Highlight, MiddleEllipsis and EllipsisTooltip, links, eight formatted
values on jiff with a typographic minus, CopyableText, SelectableText,
Emoji, a TeX subset, rolling AnimatedNumber, Typewriter, tonal
GradientText, ShimmerText. Gallery probes now hug their targets.
Codex gpt-6-astra (max): PASS in round 2.
This commit is contained in:
ZacharyZhang-NY committed 2026-09-25 01:12:52 -04:00
1 parent a13343c2a5
commit b35530c80d
30 files changed
+3125 -58

No files matched your search

+226
View File
@@ -0,0 +1,226 @@
use std::ops::Range;
use gpui::{
AnyElement, App, FontWeight, HighlightStyle, IntoElement, Keystroke, ParentElement, RenderOnce,
SharedString, Styled, StyledText, Window, div,
};
use regex::RegexBuilder;
use smallvec::SmallVec;
use super::keys::{Platform, keystroke_labels};
use crate::theme::{ActiveTheme, Radius, TextSize};
/// Monospace fragment on a quiet fill.
#[derive(IntoElement)]
pub struct Code {
text: SharedString,
}
impl Code {
pub fn new(text: impl Into<SharedString>) -> Self {
Self { text: text.into() }
}
}
impl RenderOnce for Code {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
div()
.px_1()
.rounded(theme.radius(Radius::Sm))
.bg(theme.colors.sunken)
.border_1()
.border_color(theme.colors.border)
.font_family(theme.mono_family.clone())
.text_size(theme.text_size(TextSize::Sm))
.text_color(theme.colors.fg)
.child(self.text)
}
}
fn cap(label: String, cx: &App) -> impl IntoElement + use<> {
let theme = cx.theme();
div()
.min_w(theme.text_size(TextSize::Xs) * 1.8)
.px_1()
.flex()
.justify_center()
.rounded(theme.radius(Radius::Sm))
.border_1()
.border_b_2()
.border_color(theme.colors.border_strong)
.bg(theme.colors.surface)
.text_size(theme.text_size(TextSize::Xs))
.font_weight(FontWeight::MEDIUM)
.text_color(theme.colors.fg_muted)
.child(label)
}
fn parse(source: &str) -> Keystroke {
Keystroke::parse(source).unwrap_or_else(|error| panic!("bad keystroke {source:?}: {error}"))
}
/// One key, labeled for this platform. Takes gpui key syntax.
#[derive(IntoElement)]
pub struct Kbd {
stroke: Keystroke,
}
impl Kbd {
/// `"secondary-s"` reads ⌘S on macOS and Ctrl S elsewhere.
pub fn new(source: &str) -> Self {
Self {
stroke: parse(source),
}
}
}
impl RenderOnce for Kbd {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let labels = keystroke_labels(&self.stroke, Platform::current());
div()
.flex()
.gap_0p5()
.children(labels.into_iter().map(|label| cap(label, cx)))
}
}
/// A chord sequence such as `"cmd-k cmd-s"`.
#[derive(IntoElement)]
pub struct KbdCombo {
strokes: SmallVec<[Keystroke; 2]>,
}
impl KbdCombo {
pub fn new(source: &str) -> Self {
Self {
strokes: source.split_whitespace().map(parse).collect(),
}
}
}
impl RenderOnce for KbdCombo {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let platform = Platform::current();
let subtle = cx.theme().colors.fg_subtle;
let spelled = platform != Platform::Mac;
div()
.flex()
.items_center()
.gap_2()
.children(self.strokes.iter().map(|stroke| {
let labels = keystroke_labels(stroke, platform);
let last = labels.len() - 1;
div().flex().items_center().gap_0p5().children(
labels.into_iter().enumerate().flat_map(|(ix, label)| {
let mut parts = vec![cap(label, cx).into_any_element()];
if spelled && ix < last {
parts.push(div().text_color(subtle).child("+").into_any_element());
}
parts
}),
)
}))
}
}
/// Quoted passage behind a rule.
#[derive(IntoElement)]
pub struct Blockquote {
children: SmallVec<[AnyElement; 2]>,
}
impl Blockquote {
pub fn new() -> Self {
Self {
children: SmallVec::new(),
}
}
}
impl Default for Blockquote {
fn default() -> Self {
Self::new()
}
}
impl ParentElement for Blockquote {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements);
}
}
impl RenderOnce for Blockquote {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
div()
.pl_4()
.border_l_2()
.border_color(theme.colors.border_strong)
.text_color(theme.colors.fg_muted)
.text_size(theme.text_size(TextSize::Md))
.children(self.children)
}
}
/// Text with marked ranges, as for search matches.
#[derive(IntoElement)]
pub struct Highlight {
text: SharedString,
ranges: Vec<Range<usize>>,
}
impl Highlight {
/// Byte ranges on char boundaries.
pub fn new(text: impl Into<SharedString>, ranges: Vec<Range<usize>>) -> Self {
Self {
text: text.into(),
ranges,
}
}
/// Marks every case-insensitive occurrence of `query`.
pub fn matching(text: impl Into<SharedString>, query: &str) -> Self {
let text = text.into();
let ranges = if query.is_empty() {
Vec::new()
} else {
RegexBuilder::new(&regex::escape(query))
.case_insensitive(true)
.build()
.expect("an escaped literal is a valid regex")
.find_iter(&text)
.map(|found| found.range())
.collect()
};
Self { text, ranges }
}
}
impl RenderOnce for Highlight {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let colors = &cx.theme().colors;
let mark = HighlightStyle {
background_color: Some(colors.warning_subtle),
color: Some(colors.fg),
..Default::default()
};
StyledText::new(self.text)
.with_highlights(self.ranges.into_iter().map(|range| (range, mark)))
}
}
#[cfg(test)]
mod tests {
use super::{Highlight, Range};
#[test]
fn matching_finds_every_case_insensitive_hit() {
let found = Highlight::matching("Ely, ely, ELY and élan", "ely");
assert_eq!(found.ranges, [0..3, 5..8, 10..13]);
let none = Highlight::matching("abc", "");
assert!(none.ranges.is_empty());
let literal = Highlight::matching("a.b ab", ".");
assert_eq!(literal.ranges, vec![Range { start: 1, end: 2 }]);
}
}