1use crate::{QuoteKind, Typography, html::Html, render};
4use gpui::Hsla;
5use theme::{HighlightKind, Metrics, Theme};
6use ui::color::to_hex;
7
8const ALERTS: [QuoteKind; 5] = [
9 QuoteKind::Note,
10 QuoteKind::Tip,
11 QuoteKind::Important,
12 QuoteKind::Warning,
13 QuoteKind::Caution,
14];
15
16impl Html {
17 pub fn stylesheet(light: &Theme, dark: &Theme, typography: &Typography) -> String {
22 let mut css = String::new();
23 css.push_str(".md {\n");
24 colors(&mut css, light);
25 css.push_str("}\n@media (prefers-color-scheme: dark) {\n .md {\n");
26 colors(&mut css, dark);
27 css.push_str(" }\n}\n");
28
29 let body = format!("\"{}\", system-ui, sans-serif", light.font_body);
30 let mono = format!("\"{}\", ui-monospace, monospace", light.font_mono);
31 rule(
32 &mut css,
33 ".md",
34 &[
35 ("color", "var(--md-text)".into()),
36 ("font-family", body),
37 ("font-size", px(typography.body.size())),
38 ("line-height", typography.body.leading.to_string()),
39 ("font-weight", weight(typography.body)),
40 ],
41 );
42 for level in 1..=6 {
43 let metrics = typography.heading(level);
44 rule(
45 &mut css,
46 &format!(".md h{level}"),
47 &[
48 ("font-size", px(metrics.size())),
49 ("line-height", metrics.leading.to_string()),
50 ("font-weight", weight(metrics)),
51 ],
52 );
53 }
54 rule(&mut css, ".md a", &[("color", "var(--md-accent)".into())]);
55 rule(
56 &mut css,
57 ".md code, .md pre",
58 &[
59 ("font-family", mono),
60 ("font-size", px(typography.code.size())),
61 ("line-height", typography.code.leading.to_string()),
62 ("color", "var(--md-code-text)".into()),
63 ],
64 );
65 rule(
66 &mut css,
67 ".md :not(pre) > code",
68 &[
69 ("background", "var(--md-code-wash)".into()),
70 ("border-radius", "4px".into()),
71 ("padding", "0 0.25em".into()),
72 ],
73 );
74 rule(
75 &mut css,
76 ".md pre",
77 &[
78 ("background", "var(--md-code-wash)".into()),
79 ("border-radius", "8px".into()),
80 ("padding", "12px 16px".into()),
81 ("overflow-x", "auto".into()),
82 ],
83 );
84 rule(
85 &mut css,
86 ".md blockquote",
87 &[
88 ("margin-left", "0".into()),
89 ("padding-left", "1em".into()),
90 ("border-left", "3px solid var(--md-border)".into()),
91 ("color", "var(--md-text-muted)".into()),
92 ],
93 );
94 for alert in ALERTS {
95 let name = alert.label().to_lowercase();
96 rule(
97 &mut css,
98 &format!(".md .md-alert-{name}"),
99 &[("border-left-color", format!("var(--md-alert-{name})"))],
100 );
101 rule(
102 &mut css,
103 &format!(".md .md-alert-{name} > .md-alert-title"),
104 &[
105 ("color", format!("var(--md-alert-{name})")),
106 ("font-weight", "600".into()),
107 ],
108 );
109 }
110 rule(
111 &mut css,
112 ".md hr",
113 &[
114 ("border", "none".into()),
115 ("border-top", "1px solid var(--md-border)".into()),
116 ],
117 );
118 rule(
119 &mut css,
120 ".md table",
121 &[("border-collapse", "collapse".into())],
122 );
123 rule(
124 &mut css,
125 ".md th, .md td",
126 &[
127 ("border", "1px solid var(--md-border)".into()),
128 ("padding", "4px 10px".into()),
129 ],
130 );
131 rule(
132 &mut css,
133 ".md .md-tasks",
134 &[("list-style", "none".into()), ("padding-left", "0".into())],
135 );
136 rule(
137 &mut css,
138 ".md .md-indent",
139 &[("margin-left", "1.5em".into())],
140 );
141 rule(
142 &mut css,
143 ".md .md-image img",
144 &[("max-width", "100%".into())],
145 );
146 rule(
147 &mut css,
148 ".md figcaption",
149 &[
150 ("color", "var(--md-text-muted)".into()),
151 ("font-size", px(typography.caption.size())),
152 ("line-height", typography.caption.leading.to_string()),
153 ],
154 );
155 rule(
156 &mut css,
157 ".md .md-bookmark",
158 &[
159 ("display", "block".into()),
160 ("padding", "12px 16px".into()),
161 ("border", "1px solid var(--md-border)".into()),
162 ("border-radius", "8px".into()),
163 ("font-size", px(typography.card.size())),
164 ],
165 );
166 for kind in HighlightKind::ALL {
167 let name = kind.name();
168 rule(
169 &mut css,
170 &format!(".md .hl-{name}"),
171 &[("color", format!("var(--md-hl-{name})"))],
172 );
173 }
174 css
175 }
176}
177
178fn colors(css: &mut String, theme: &Theme) {
179 let mut var = |name: &str, color: Hsla| {
180 css.push_str(&format!(" --md-{name}: {};\n", to_hex(color, true)));
181 };
182 var("text", theme.text);
183 var("text-muted", theme.text_muted);
184 var("border", theme.border);
185 var("accent", theme.accent);
186 var("code-text", theme.code_text);
187 var("code-wash", theme.code_wash);
188 for alert in ALERTS {
189 var(
190 &format!("alert-{}", alert.label().to_lowercase()),
191 render::alert_color(alert, theme),
192 );
193 }
194 for kind in HighlightKind::ALL {
195 var(&format!("hl-{}", kind.name()), theme.syntax.color(kind));
196 }
197}
198
199fn rule(css: &mut String, selector: &str, declarations: &[(&str, String)]) {
200 css.push_str(selector);
201 css.push_str(" {\n");
202 for (property, value) in declarations {
203 css.push_str(&format!(" {property}: {value};\n"));
204 }
205 css.push_str("}\n");
206}
207
208fn px(size: f32) -> String {
209 format!("{}px", (size * 100.0).round() / 100.0)
210}
211
212fn weight(metrics: Metrics) -> String {
213 format!("{}", metrics.weight.0)
214}