1use std::{ops::Range, sync::Arc};
2
3use gpui::{
4 div, px, rems, AnyElement, DefiniteLength, Div, ElementId, Hsla, ParentElement, SharedString,
5 Styled, StyledText, WindowContext,
6};
7use language::LanguageRegistry;
8use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag};
9use rich_text::render_rich_text;
10use theme::{ActiveTheme, Theme};
11use ui::{h_flex, v_flex};
12
13enum TableState {
14 Header,
15 Body,
16}
17
18struct MarkdownTable {
19 header: Vec<Div>,
20 body: Vec<Vec<Div>>,
21 current_row: Vec<Div>,
22 state: TableState,
23 border_color: Hsla,
24}
25
26impl MarkdownTable {
27 fn new(border_color: Hsla) -> Self {
28 Self {
29 header: Vec::new(),
30 body: Vec::new(),
31 current_row: Vec::new(),
32 state: TableState::Header,
33 border_color,
34 }
35 }
36
37 fn finish_row(&mut self) {
38 match self.state {
39 TableState::Header => {
40 self.header.extend(self.current_row.drain(..));
41 self.state = TableState::Body;
42 }
43 TableState::Body => {
44 self.body.push(self.current_row.drain(..).collect());
45 }
46 }
47 }
48
49 fn add_cell(&mut self, contents: AnyElement) {
50 let cell = div()
51 .child(contents)
52 .w_full()
53 .px_2()
54 .py_1()
55 .border_color(self.border_color);
56
57 let cell = match self.state {
58 TableState::Header => cell.border_2(),
59 TableState::Body => cell.border_1(),
60 };
61
62 self.current_row.push(cell);
63 }
64
65 fn finish(self) -> Div {
66 let mut table = v_flex().w_full();
67 let mut header = h_flex();
68
69 for cell in self.header {
70 header = header.child(cell);
71 }
72 table = table.child(header);
73 for row in self.body {
74 let mut row_div = h_flex();
75 for cell in row {
76 row_div = row_div.child(cell);
77 }
78 table = table.child(row_div);
79 }
80 table
81 }
82}
83
84struct Renderer<I> {
85 source_contents: String,
86 iter: I,
87 theme: Arc<Theme>,
88 finished: Vec<Div>,
89 language_registry: Arc<LanguageRegistry>,
90 table: Option<MarkdownTable>,
91 list_depth: usize,
92 block_quote_depth: usize,
93}
94
95impl<'a, I> Renderer<I>
96where
97 I: Iterator<Item = (Event<'a>, Range<usize>)>,
98{
99 fn new(
100 iter: I,
101 source_contents: String,
102 language_registry: &Arc<LanguageRegistry>,
103 theme: Arc<Theme>,
104 ) -> Self {
105 Self {
106 iter,
107 source_contents,
108 theme,
109 table: None,
110 finished: vec![],
111 language_registry: language_registry.clone(),
112 list_depth: 0,
113 block_quote_depth: 0,
114 }
115 }
116
117 fn run(mut self, cx: &WindowContext) -> Self {
118 while let Some((event, source_range)) = self.iter.next() {
119 match event {
120 Event::Start(tag) => {
121 self.start_tag(tag);
122 }
123 Event::End(tag) => {
124 self.end_tag(tag, source_range, cx);
125 }
126 Event::Rule => {
127 let rule = div().w_full().h(px(2.)).bg(self.theme.colors().border);
128 self.finished.push(div().mb_4().child(rule));
129 }
130 _ => {}
131 }
132 }
133 self
134 }
135
136 fn start_tag(&mut self, tag: Tag<'a>) {
137 match tag {
138 Tag::List(_) => {
139 self.list_depth += 1;
140 }
141 Tag::BlockQuote => {
142 self.block_quote_depth += 1;
143 }
144 Tag::Table(_text_alignments) => {
145 self.table = Some(MarkdownTable::new(self.theme.colors().border));
146 }
147 _ => {}
148 }
149 }
150
151 fn end_tag(&mut self, tag: Tag, source_range: Range<usize>, cx: &WindowContext) {
152 match tag {
153 Tag::Paragraph => {
154 if self.list_depth > 0 || self.block_quote_depth > 0 {
155 return;
156 }
157
158 let element = self.render_md_from_range(source_range.clone(), cx);
159 let paragraph = h_flex().mb_3().child(element);
160
161 self.finished.push(paragraph);
162 }
163 Tag::Heading(level, _, _) => {
164 let mut headline = self.headline(level);
165 if source_range.start > 0 {
166 headline = headline.mt_4();
167 }
168
169 let element = self.render_md_from_range(source_range.clone(), cx);
170 let headline = headline.child(element);
171
172 self.finished.push(headline);
173 }
174 Tag::List(_) => {
175 if self.list_depth == 1 {
176 let element = self.render_md_from_range(source_range.clone(), cx);
177 let list = div().mb_3().child(element);
178
179 self.finished.push(list);
180 }
181
182 self.list_depth -= 1;
183 }
184 Tag::BlockQuote => {
185 let element = self.render_md_from_range(source_range.clone(), cx);
186
187 let block_quote = h_flex()
188 .mb_3()
189 .child(
190 div()
191 .w(px(4.))
192 .bg(self.theme.colors().border)
193 .h_full()
194 .mr_2()
195 .mt_1(),
196 )
197 .text_color(self.theme.colors().text_muted)
198 .child(element);
199
200 self.finished.push(block_quote);
201
202 self.block_quote_depth -= 1;
203 }
204 Tag::CodeBlock(kind) => {
205 let contents = self.source_contents[source_range.clone()].trim();
206 let contents = contents.trim_start_matches("```");
207 let contents = contents.trim_end_matches("```");
208 let contents = match kind {
209 CodeBlockKind::Fenced(language) => {
210 contents.trim_start_matches(&language.to_string())
211 }
212 CodeBlockKind::Indented => contents,
213 };
214 let contents: String = contents.into();
215 let contents = SharedString::from(contents);
216
217 let code_block = div()
218 .mb_3()
219 .px_4()
220 .py_0()
221 .bg(self.theme.colors().surface_background)
222 .child(StyledText::new(contents));
223
224 self.finished.push(code_block);
225 }
226 Tag::Table(_alignment) => {
227 if self.table.is_none() {
228 log::error!("Table end without table ({:?})", source_range);
229 return;
230 }
231
232 let table = self.table.take().unwrap();
233 let table = table.finish().mb_4();
234 self.finished.push(table);
235 }
236 Tag::TableHead => {
237 if self.table.is_none() {
238 log::error!("Table head without table ({:?})", source_range);
239 return;
240 }
241
242 self.table.as_mut().unwrap().finish_row();
243 }
244 Tag::TableRow => {
245 if self.table.is_none() {
246 log::error!("Table row without table ({:?})", source_range);
247 return;
248 }
249
250 self.table.as_mut().unwrap().finish_row();
251 }
252 Tag::TableCell => {
253 if self.table.is_none() {
254 log::error!("Table cell without table ({:?})", source_range);
255 return;
256 }
257
258 let contents = self.render_md_from_range(source_range.clone(), cx);
259 self.table.as_mut().unwrap().add_cell(contents);
260 }
261 _ => {}
262 }
263 }
264
265 fn render_md_from_range(
266 &self,
267 source_range: Range<usize>,
268 cx: &WindowContext,
269 ) -> gpui::AnyElement {
270 let mentions = &[];
271 let language = None;
272 let paragraph = &self.source_contents[source_range.clone()];
273 let rich_text = render_rich_text(
274 paragraph.into(),
275 mentions,
276 &self.language_registry,
277 language,
278 );
279 let id: ElementId = source_range.start.into();
280 rich_text.element(id, cx)
281 }
282
283 fn headline(&self, level: HeadingLevel) -> Div {
284 let size = match level {
285 HeadingLevel::H1 => rems(2.),
286 HeadingLevel::H2 => rems(1.5),
287 HeadingLevel::H3 => rems(1.25),
288 HeadingLevel::H4 => rems(1.),
289 HeadingLevel::H5 => rems(0.875),
290 HeadingLevel::H6 => rems(0.85),
291 };
292
293 let color = match level {
294 HeadingLevel::H6 => self.theme.colors().text_muted,
295 _ => self.theme.colors().text,
296 };
297
298 let line_height = DefiniteLength::from(rems(1.25));
299
300 let headline = h_flex()
301 .w_full()
302 .line_height(line_height)
303 .text_size(size)
304 .text_color(color)
305 .mb_4()
306 .pb(rems(0.15));
307
308 headline
309 }
310}
311
312pub fn render_markdown(
313 markdown_input: &str,
314 language_registry: &Arc<LanguageRegistry>,
315 cx: &WindowContext,
316) -> Vec<Div> {
317 let theme = cx.theme().clone();
318 let options = Options::all();
319 let parser = Parser::new_ext(markdown_input, options);
320 let renderer = Renderer::new(
321 parser.into_offset_iter(),
322 markdown_input.to_owned(),
323 language_registry,
324 theme,
325 );
326 let renderer = renderer.run(cx);
327 return renderer.finished;
328}