1mod parser;
2
3use crate::parser::CodeBlockKind;
4use futures::FutureExt;
5use gpui::{
6 point, quad, AnyElement, AppContext, Bounds, CursorStyle, DispatchPhase, Edges, FocusHandle,
7 FocusableView, FontStyle, FontWeight, GlobalElementId, Hitbox, Hsla, KeyContext,
8 MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent, Point, Render, StrikethroughStyle,
9 Style, StyledText, Task, TextLayout, TextRun, TextStyle, TextStyleRefinement, View,
10};
11use language::{Language, LanguageRegistry, Rope};
12use parser::{parse_markdown, MarkdownEvent, MarkdownTag, MarkdownTagEnd};
13use std::{iter, mem, ops::Range, rc::Rc, sync::Arc};
14use theme::SyntaxTheme;
15use ui::prelude::*;
16use util::{ResultExt, TryFutureExt};
17
18#[derive(Clone)]
19pub struct MarkdownStyle {
20 pub code_block: TextStyleRefinement,
21 pub inline_code: TextStyleRefinement,
22 pub block_quote: TextStyleRefinement,
23 pub link: TextStyleRefinement,
24 pub rule_color: Hsla,
25 pub block_quote_border_color: Hsla,
26 pub syntax: Arc<SyntaxTheme>,
27 pub selection_background_color: Hsla,
28}
29
30pub struct Markdown {
31 source: String,
32 selection: Selection,
33 pressed_link: Option<RenderedLink>,
34 autoscroll_request: Option<usize>,
35 style: MarkdownStyle,
36 parsed_markdown: ParsedMarkdown,
37 should_reparse: bool,
38 pending_parse: Option<Task<Option<()>>>,
39 focus_handle: FocusHandle,
40 language_registry: Arc<LanguageRegistry>,
41}
42
43impl Markdown {
44 pub fn new(
45 source: String,
46 style: MarkdownStyle,
47 language_registry: Arc<LanguageRegistry>,
48 cx: &mut ViewContext<Self>,
49 ) -> Self {
50 let focus_handle = cx.focus_handle();
51 let mut this = Self {
52 source,
53 selection: Selection::default(),
54 pressed_link: None,
55 autoscroll_request: None,
56 style,
57 should_reparse: false,
58 parsed_markdown: ParsedMarkdown::default(),
59 pending_parse: None,
60 focus_handle,
61 language_registry,
62 };
63 this.parse(cx);
64 this
65 }
66
67 pub fn append(&mut self, text: &str, cx: &mut ViewContext<Self>) {
68 self.source.push_str(text);
69 self.parse(cx);
70 }
71
72 pub fn reset(&mut self, source: String, cx: &mut ViewContext<Self>) {
73 self.source = source;
74 self.selection = Selection::default();
75 self.autoscroll_request = None;
76 self.pending_parse = None;
77 self.should_reparse = false;
78 self.parsed_markdown = ParsedMarkdown::default();
79 self.parse(cx);
80 }
81
82 pub fn source(&self) -> &str {
83 &self.source
84 }
85
86 fn parse(&mut self, cx: &mut ViewContext<Self>) {
87 if self.source.is_empty() {
88 return;
89 }
90
91 if self.pending_parse.is_some() {
92 self.should_reparse = true;
93 return;
94 }
95
96 let text = self.source.clone();
97 let parsed = cx.background_executor().spawn(async move {
98 let text = SharedString::from(text);
99 let events = Arc::from(parse_markdown(text.as_ref()));
100 anyhow::Ok(ParsedMarkdown {
101 source: text,
102 events,
103 })
104 });
105
106 self.should_reparse = false;
107 self.pending_parse = Some(cx.spawn(|this, mut cx| {
108 async move {
109 let parsed = parsed.await?;
110 this.update(&mut cx, |this, cx| {
111 this.parsed_markdown = parsed;
112 this.pending_parse.take();
113 if this.should_reparse {
114 this.parse(cx);
115 }
116 cx.notify();
117 })
118 .ok();
119 anyhow::Ok(())
120 }
121 .log_err()
122 }));
123 }
124}
125
126impl Render for Markdown {
127 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
128 MarkdownElement::new(
129 cx.view().clone(),
130 self.style.clone(),
131 self.language_registry.clone(),
132 )
133 }
134}
135
136impl FocusableView for Markdown {
137 fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
138 self.focus_handle.clone()
139 }
140}
141
142#[derive(Copy, Clone, Default, Debug)]
143struct Selection {
144 start: usize,
145 end: usize,
146 reversed: bool,
147 pending: bool,
148}
149
150impl Selection {
151 fn set_head(&mut self, head: usize) {
152 if head < self.tail() {
153 if !self.reversed {
154 self.end = self.start;
155 self.reversed = true;
156 }
157 self.start = head;
158 } else {
159 if self.reversed {
160 self.start = self.end;
161 self.reversed = false;
162 }
163 self.end = head;
164 }
165 }
166
167 fn tail(&self) -> usize {
168 if self.reversed {
169 self.end
170 } else {
171 self.start
172 }
173 }
174}
175
176#[derive(Clone)]
177struct ParsedMarkdown {
178 source: SharedString,
179 events: Arc<[(Range<usize>, MarkdownEvent)]>,
180}
181
182impl Default for ParsedMarkdown {
183 fn default() -> Self {
184 Self {
185 source: SharedString::default(),
186 events: Arc::from([]),
187 }
188 }
189}
190
191pub struct MarkdownElement {
192 markdown: View<Markdown>,
193 style: MarkdownStyle,
194 language_registry: Arc<LanguageRegistry>,
195}
196
197impl MarkdownElement {
198 fn new(
199 markdown: View<Markdown>,
200 style: MarkdownStyle,
201 language_registry: Arc<LanguageRegistry>,
202 ) -> Self {
203 Self {
204 markdown,
205 style,
206 language_registry,
207 }
208 }
209
210 fn load_language(&self, name: &str, cx: &mut WindowContext) -> Option<Arc<Language>> {
211 let language = self
212 .language_registry
213 .language_for_name(name)
214 .map(|language| language.ok())
215 .shared();
216
217 match language.clone().now_or_never() {
218 Some(language) => language,
219 None => {
220 let markdown = self.markdown.downgrade();
221 cx.spawn(|mut cx| async move {
222 language.await;
223 markdown.update(&mut cx, |_, cx| cx.notify())
224 })
225 .detach_and_log_err(cx);
226 None
227 }
228 }
229 }
230
231 fn paint_selection(
232 &mut self,
233 bounds: Bounds<Pixels>,
234 rendered_text: &RenderedText,
235 cx: &mut WindowContext,
236 ) {
237 let selection = self.markdown.read(cx).selection;
238 let selection_start = rendered_text.position_for_source_index(selection.start);
239 let selection_end = rendered_text.position_for_source_index(selection.end);
240
241 if let Some(((start_position, start_line_height), (end_position, end_line_height))) =
242 selection_start.zip(selection_end)
243 {
244 if start_position.y == end_position.y {
245 cx.paint_quad(quad(
246 Bounds::from_corners(
247 start_position,
248 point(end_position.x, end_position.y + end_line_height),
249 ),
250 Pixels::ZERO,
251 self.style.selection_background_color,
252 Edges::default(),
253 Hsla::transparent_black(),
254 ));
255 } else {
256 cx.paint_quad(quad(
257 Bounds::from_corners(
258 start_position,
259 point(bounds.right(), start_position.y + start_line_height),
260 ),
261 Pixels::ZERO,
262 self.style.selection_background_color,
263 Edges::default(),
264 Hsla::transparent_black(),
265 ));
266
267 if end_position.y > start_position.y + start_line_height {
268 cx.paint_quad(quad(
269 Bounds::from_corners(
270 point(bounds.left(), start_position.y + start_line_height),
271 point(bounds.right(), end_position.y),
272 ),
273 Pixels::ZERO,
274 self.style.selection_background_color,
275 Edges::default(),
276 Hsla::transparent_black(),
277 ));
278 }
279
280 cx.paint_quad(quad(
281 Bounds::from_corners(
282 point(bounds.left(), end_position.y),
283 point(end_position.x, end_position.y + end_line_height),
284 ),
285 Pixels::ZERO,
286 self.style.selection_background_color,
287 Edges::default(),
288 Hsla::transparent_black(),
289 ));
290 }
291 }
292 }
293
294 fn paint_mouse_listeners(
295 &mut self,
296 hitbox: &Hitbox,
297 rendered_text: &RenderedText,
298 cx: &mut WindowContext,
299 ) {
300 let is_hovering_link = hitbox.is_hovered(cx)
301 && !self.markdown.read(cx).selection.pending
302 && rendered_text
303 .link_for_position(cx.mouse_position())
304 .is_some();
305
306 if is_hovering_link {
307 cx.set_cursor_style(CursorStyle::PointingHand, hitbox);
308 } else {
309 cx.set_cursor_style(CursorStyle::IBeam, hitbox);
310 }
311
312 self.on_mouse_event(cx, {
313 let rendered_text = rendered_text.clone();
314 let hitbox = hitbox.clone();
315 move |markdown, event: &MouseDownEvent, phase, cx| {
316 if hitbox.is_hovered(cx) {
317 if phase.bubble() {
318 if let Some(link) = rendered_text.link_for_position(event.position) {
319 markdown.pressed_link = Some(link.clone());
320 } else {
321 let source_index =
322 match rendered_text.source_index_for_position(event.position) {
323 Ok(ix) | Err(ix) => ix,
324 };
325 markdown.selection = Selection {
326 start: source_index,
327 end: source_index,
328 reversed: false,
329 pending: true,
330 };
331 cx.focus(&markdown.focus_handle);
332 }
333
334 cx.notify();
335 }
336 } else if phase.capture() {
337 markdown.selection = Selection::default();
338 markdown.pressed_link = None;
339 cx.notify();
340 }
341 }
342 });
343 self.on_mouse_event(cx, {
344 let rendered_text = rendered_text.clone();
345 let hitbox = hitbox.clone();
346 let was_hovering_link = is_hovering_link;
347 move |markdown, event: &MouseMoveEvent, phase, cx| {
348 if phase.capture() {
349 return;
350 }
351
352 if markdown.selection.pending {
353 let source_index = match rendered_text.source_index_for_position(event.position)
354 {
355 Ok(ix) | Err(ix) => ix,
356 };
357 markdown.selection.set_head(source_index);
358 markdown.autoscroll_request = Some(source_index);
359 cx.notify();
360 } else {
361 let is_hovering_link = hitbox.is_hovered(cx)
362 && rendered_text.link_for_position(event.position).is_some();
363 if is_hovering_link != was_hovering_link {
364 cx.notify();
365 }
366 }
367 }
368 });
369 self.on_mouse_event(cx, {
370 let rendered_text = rendered_text.clone();
371 move |markdown, event: &MouseUpEvent, phase, cx| {
372 if phase.bubble() {
373 if let Some(pressed_link) = markdown.pressed_link.take() {
374 if Some(&pressed_link) == rendered_text.link_for_position(event.position) {
375 cx.open_url(&pressed_link.destination_url);
376 }
377 }
378 } else {
379 if markdown.selection.pending {
380 markdown.selection.pending = false;
381 cx.notify();
382 }
383 }
384 }
385 });
386 }
387
388 fn autoscroll(&mut self, rendered_text: &RenderedText, cx: &mut WindowContext) -> Option<()> {
389 let autoscroll_index = self
390 .markdown
391 .update(cx, |markdown, _| markdown.autoscroll_request.take())?;
392 let (position, line_height) = rendered_text.position_for_source_index(autoscroll_index)?;
393
394 let text_style = cx.text_style();
395 let font_id = cx.text_system().resolve_font(&text_style.font());
396 let font_size = text_style.font_size.to_pixels(cx.rem_size());
397 let em_width = cx
398 .text_system()
399 .typographic_bounds(font_id, font_size, 'm')
400 .unwrap()
401 .size
402 .width;
403 cx.request_autoscroll(Bounds::from_corners(
404 point(position.x - 3. * em_width, position.y - 3. * line_height),
405 point(position.x + 3. * em_width, position.y + 3. * line_height),
406 ));
407 Some(())
408 }
409
410 fn on_mouse_event<T: MouseEvent>(
411 &self,
412 cx: &mut WindowContext,
413 mut f: impl 'static + FnMut(&mut Markdown, &T, DispatchPhase, &mut ViewContext<Markdown>),
414 ) {
415 cx.on_mouse_event({
416 let markdown = self.markdown.downgrade();
417 move |event, phase, cx| {
418 markdown
419 .update(cx, |markdown, cx| f(markdown, event, phase, cx))
420 .log_err();
421 }
422 });
423 }
424}
425
426impl Element for MarkdownElement {
427 type RequestLayoutState = RenderedMarkdown;
428 type PrepaintState = Hitbox;
429
430 fn id(&self) -> Option<ElementId> {
431 None
432 }
433
434 fn request_layout(
435 &mut self,
436 _id: Option<&GlobalElementId>,
437 cx: &mut WindowContext,
438 ) -> (gpui::LayoutId, Self::RequestLayoutState) {
439 let mut builder = MarkdownElementBuilder::new(cx.text_style(), self.style.syntax.clone());
440 let parsed_markdown = self.markdown.read(cx).parsed_markdown.clone();
441 for (range, event) in parsed_markdown.events.iter() {
442 match event {
443 MarkdownEvent::Start(tag) => {
444 match tag {
445 MarkdownTag::Paragraph => {
446 builder.push_div(div().mb_2().line_height(rems(1.3)));
447 }
448 MarkdownTag::Heading { level, .. } => {
449 let mut heading = div().mb_2();
450 heading = match level {
451 pulldown_cmark::HeadingLevel::H1 => heading.text_3xl(),
452 pulldown_cmark::HeadingLevel::H2 => heading.text_2xl(),
453 pulldown_cmark::HeadingLevel::H3 => heading.text_xl(),
454 pulldown_cmark::HeadingLevel::H4 => heading.text_lg(),
455 _ => heading,
456 };
457 builder.push_div(heading);
458 }
459 MarkdownTag::BlockQuote => {
460 builder.push_text_style(self.style.block_quote.clone());
461 builder.push_div(
462 div()
463 .pl_4()
464 .mb_2()
465 .border_l_4()
466 .border_color(self.style.block_quote_border_color),
467 );
468 }
469 MarkdownTag::CodeBlock(kind) => {
470 let language = if let CodeBlockKind::Fenced(language) = kind {
471 self.load_language(language.as_ref(), cx)
472 } else {
473 None
474 };
475
476 builder.push_code_block(language);
477 builder.push_text_style(self.style.code_block.clone());
478 builder.push_div(div().rounded_lg().p_4().mb_2().w_full().when_some(
479 self.style.code_block.background_color,
480 |div, color| div.bg(color),
481 ));
482 }
483 MarkdownTag::HtmlBlock => builder.push_div(div()),
484 MarkdownTag::List(bullet_index) => {
485 builder.push_list(*bullet_index);
486 builder.push_div(div().pl_4());
487 }
488 MarkdownTag::Item => {
489 let bullet = if let Some(bullet_index) = builder.next_bullet_index() {
490 format!("{}.", bullet_index)
491 } else {
492 "•".to_string()
493 };
494 builder.push_div(
495 div()
496 .h_flex()
497 .mb_2()
498 .line_height(rems(1.3))
499 .items_start()
500 .gap_1()
501 .child(bullet),
502 );
503 // Without `w_0`, text doesn't wrap to the width of the container.
504 builder.push_div(div().flex_1().w_0());
505 }
506 MarkdownTag::Emphasis => builder.push_text_style(TextStyleRefinement {
507 font_style: Some(FontStyle::Italic),
508 ..Default::default()
509 }),
510 MarkdownTag::Strong => builder.push_text_style(TextStyleRefinement {
511 font_weight: Some(FontWeight::BOLD),
512 ..Default::default()
513 }),
514 MarkdownTag::Strikethrough => {
515 builder.push_text_style(TextStyleRefinement {
516 strikethrough: Some(StrikethroughStyle {
517 thickness: px(1.),
518 color: None,
519 }),
520 ..Default::default()
521 })
522 }
523 MarkdownTag::Link { dest_url, .. } => {
524 builder.push_link(dest_url.clone(), range.clone());
525 builder.push_text_style(self.style.link.clone())
526 }
527 _ => log::error!("unsupported markdown tag {:?}", tag),
528 }
529 }
530 MarkdownEvent::End(tag) => match tag {
531 MarkdownTagEnd::Paragraph => {
532 builder.pop_div();
533 }
534 MarkdownTagEnd::Heading(_) => builder.pop_div(),
535 MarkdownTagEnd::BlockQuote => {
536 builder.pop_text_style();
537 builder.pop_div()
538 }
539 MarkdownTagEnd::CodeBlock => {
540 builder.trim_trailing_newline();
541 builder.pop_div();
542 builder.pop_text_style();
543 builder.pop_code_block();
544 }
545 MarkdownTagEnd::HtmlBlock => builder.pop_div(),
546 MarkdownTagEnd::List(_) => {
547 builder.pop_list();
548 builder.pop_div();
549 }
550 MarkdownTagEnd::Item => {
551 builder.pop_div();
552 builder.pop_div();
553 }
554 MarkdownTagEnd::Emphasis => builder.pop_text_style(),
555 MarkdownTagEnd::Strong => builder.pop_text_style(),
556 MarkdownTagEnd::Strikethrough => builder.pop_text_style(),
557 MarkdownTagEnd::Link => builder.pop_text_style(),
558 _ => log::error!("unsupported markdown tag end: {:?}", tag),
559 },
560 MarkdownEvent::Text => {
561 builder.push_text(&parsed_markdown.source[range.clone()], range.start);
562 }
563 MarkdownEvent::Code => {
564 builder.push_text_style(self.style.inline_code.clone());
565 builder.push_text(&parsed_markdown.source[range.clone()], range.start);
566 builder.pop_text_style();
567 }
568 MarkdownEvent::Html => {
569 builder.push_text(&parsed_markdown.source[range.clone()], range.start);
570 }
571 MarkdownEvent::InlineHtml => {
572 builder.push_text(&parsed_markdown.source[range.clone()], range.start);
573 }
574 MarkdownEvent::Rule => {
575 builder.push_div(
576 div()
577 .border_b_1()
578 .my_2()
579 .border_color(self.style.rule_color),
580 );
581 builder.pop_div()
582 }
583 MarkdownEvent::SoftBreak => builder.push_text("\n", range.start),
584 MarkdownEvent::HardBreak => builder.push_text("\n", range.start),
585 _ => log::error!("unsupported markdown event {:?}", event),
586 }
587 }
588
589 let mut rendered_markdown = builder.build();
590 let child_layout_id = rendered_markdown.element.request_layout(cx);
591 let layout_id = cx.request_layout(Style::default(), [child_layout_id]);
592 (layout_id, rendered_markdown)
593 }
594
595 fn prepaint(
596 &mut self,
597 _id: Option<&GlobalElementId>,
598 bounds: Bounds<Pixels>,
599 rendered_markdown: &mut Self::RequestLayoutState,
600 cx: &mut WindowContext,
601 ) -> Self::PrepaintState {
602 let hitbox = cx.insert_hitbox(bounds, false);
603 rendered_markdown.element.prepaint(cx);
604 self.autoscroll(&rendered_markdown.text, cx);
605 hitbox
606 }
607
608 fn paint(
609 &mut self,
610 _id: Option<&GlobalElementId>,
611 bounds: Bounds<Pixels>,
612 rendered_markdown: &mut Self::RequestLayoutState,
613 hitbox: &mut Self::PrepaintState,
614 cx: &mut WindowContext,
615 ) {
616 let focus_handle = self.markdown.read(cx).focus_handle.clone();
617 cx.set_focus_handle(&focus_handle);
618
619 let mut context = KeyContext::default();
620 context.add("Markdown");
621 cx.set_key_context(context);
622
623 self.paint_mouse_listeners(hitbox, &rendered_markdown.text, cx);
624 rendered_markdown.element.paint(cx);
625 self.paint_selection(bounds, &rendered_markdown.text, cx);
626 }
627}
628
629impl IntoElement for MarkdownElement {
630 type Element = Self;
631
632 fn into_element(self) -> Self::Element {
633 self
634 }
635}
636
637struct MarkdownElementBuilder {
638 div_stack: Vec<Div>,
639 rendered_lines: Vec<RenderedLine>,
640 pending_line: PendingLine,
641 rendered_links: Vec<RenderedLink>,
642 current_source_index: usize,
643 base_text_style: TextStyle,
644 text_style_stack: Vec<TextStyleRefinement>,
645 code_block_stack: Vec<Option<Arc<Language>>>,
646 list_stack: Vec<ListStackEntry>,
647 syntax_theme: Arc<SyntaxTheme>,
648}
649
650#[derive(Default)]
651struct PendingLine {
652 text: String,
653 runs: Vec<TextRun>,
654 source_mappings: Vec<SourceMapping>,
655}
656
657struct ListStackEntry {
658 bullet_index: Option<u64>,
659}
660
661impl MarkdownElementBuilder {
662 fn new(base_text_style: TextStyle, syntax_theme: Arc<SyntaxTheme>) -> Self {
663 Self {
664 div_stack: vec![div().debug_selector(|| "inner".into())],
665 rendered_lines: Vec::new(),
666 pending_line: PendingLine::default(),
667 rendered_links: Vec::new(),
668 current_source_index: 0,
669 base_text_style,
670 text_style_stack: Vec::new(),
671 code_block_stack: Vec::new(),
672 list_stack: Vec::new(),
673 syntax_theme,
674 }
675 }
676
677 fn push_text_style(&mut self, style: TextStyleRefinement) {
678 self.text_style_stack.push(style);
679 }
680
681 fn text_style(&self) -> TextStyle {
682 let mut style = self.base_text_style.clone();
683 for refinement in &self.text_style_stack {
684 style.refine(refinement);
685 }
686 style
687 }
688
689 fn pop_text_style(&mut self) {
690 self.text_style_stack.pop();
691 }
692
693 fn push_div(&mut self, div: Div) {
694 self.flush_text();
695 self.div_stack.push(div);
696 }
697
698 fn pop_div(&mut self) {
699 self.flush_text();
700 let div = self.div_stack.pop().unwrap().into_any();
701 self.div_stack.last_mut().unwrap().extend(iter::once(div));
702 }
703
704 fn push_list(&mut self, bullet_index: Option<u64>) {
705 self.list_stack.push(ListStackEntry { bullet_index });
706 }
707
708 fn next_bullet_index(&mut self) -> Option<u64> {
709 self.list_stack.last_mut().and_then(|entry| {
710 let item_index = entry.bullet_index.as_mut()?;
711 *item_index += 1;
712 Some(*item_index - 1)
713 })
714 }
715
716 fn pop_list(&mut self) {
717 self.list_stack.pop();
718 }
719
720 fn push_code_block(&mut self, language: Option<Arc<Language>>) {
721 self.code_block_stack.push(language);
722 }
723
724 fn pop_code_block(&mut self) {
725 self.code_block_stack.pop();
726 }
727
728 fn push_link(&mut self, destination_url: SharedString, source_range: Range<usize>) {
729 self.rendered_links.push(RenderedLink {
730 source_range,
731 destination_url,
732 });
733 }
734
735 fn push_text(&mut self, text: &str, source_index: usize) {
736 self.pending_line.source_mappings.push(SourceMapping {
737 rendered_index: self.pending_line.text.len(),
738 source_index,
739 });
740 self.pending_line.text.push_str(text);
741 self.current_source_index = source_index + text.len();
742
743 if let Some(Some(language)) = self.code_block_stack.last() {
744 let mut offset = 0;
745 for (range, highlight_id) in language.highlight_text(&Rope::from(text), 0..text.len()) {
746 if range.start > offset {
747 self.pending_line
748 .runs
749 .push(self.text_style().to_run(range.start - offset));
750 }
751
752 let mut run_style = self.text_style();
753 if let Some(highlight) = highlight_id.style(&self.syntax_theme) {
754 run_style = run_style.highlight(highlight);
755 }
756 self.pending_line.runs.push(run_style.to_run(range.len()));
757 offset = range.end;
758 }
759
760 if offset < text.len() {
761 self.pending_line
762 .runs
763 .push(self.text_style().to_run(text.len() - offset));
764 }
765 } else {
766 self.pending_line
767 .runs
768 .push(self.text_style().to_run(text.len()));
769 }
770 }
771
772 fn trim_trailing_newline(&mut self) {
773 if self.pending_line.text.ends_with('\n') {
774 self.pending_line
775 .text
776 .truncate(self.pending_line.text.len() - 1);
777 self.pending_line.runs.last_mut().unwrap().len -= 1;
778 self.current_source_index -= 1;
779 }
780 }
781
782 fn flush_text(&mut self) {
783 let line = mem::take(&mut self.pending_line);
784 if line.text.is_empty() {
785 return;
786 }
787
788 let text = StyledText::new(line.text).with_runs(line.runs);
789 self.rendered_lines.push(RenderedLine {
790 layout: text.layout().clone(),
791 source_mappings: line.source_mappings,
792 source_end: self.current_source_index,
793 });
794 self.div_stack.last_mut().unwrap().extend([text.into_any()]);
795 }
796
797 fn build(mut self) -> RenderedMarkdown {
798 debug_assert_eq!(self.div_stack.len(), 1);
799 self.flush_text();
800 RenderedMarkdown {
801 element: self.div_stack.pop().unwrap().into_any(),
802 text: RenderedText {
803 lines: self.rendered_lines.into(),
804 links: self.rendered_links.into(),
805 },
806 }
807 }
808}
809
810struct RenderedLine {
811 layout: TextLayout,
812 source_mappings: Vec<SourceMapping>,
813 source_end: usize,
814}
815
816impl RenderedLine {
817 fn rendered_index_for_source_index(&self, source_index: usize) -> usize {
818 let mapping = match self
819 .source_mappings
820 .binary_search_by_key(&source_index, |probe| probe.source_index)
821 {
822 Ok(ix) => &self.source_mappings[ix],
823 Err(ix) => &self.source_mappings[ix - 1],
824 };
825 mapping.rendered_index + (source_index - mapping.source_index)
826 }
827
828 fn source_index_for_rendered_index(&self, rendered_index: usize) -> usize {
829 let mapping = match self
830 .source_mappings
831 .binary_search_by_key(&rendered_index, |probe| probe.rendered_index)
832 {
833 Ok(ix) => &self.source_mappings[ix],
834 Err(ix) => &self.source_mappings[ix - 1],
835 };
836 mapping.source_index + (rendered_index - mapping.rendered_index)
837 }
838
839 fn source_index_for_position(&self, position: Point<Pixels>) -> Result<usize, usize> {
840 let line_rendered_index;
841 let out_of_bounds;
842 match self.layout.index_for_position(position) {
843 Ok(ix) => {
844 line_rendered_index = ix;
845 out_of_bounds = false;
846 }
847 Err(ix) => {
848 line_rendered_index = ix;
849 out_of_bounds = true;
850 }
851 };
852 let source_index = self.source_index_for_rendered_index(line_rendered_index);
853 if out_of_bounds {
854 Err(source_index)
855 } else {
856 Ok(source_index)
857 }
858 }
859}
860
861#[derive(Copy, Clone, Debug, Default)]
862struct SourceMapping {
863 rendered_index: usize,
864 source_index: usize,
865}
866
867pub struct RenderedMarkdown {
868 element: AnyElement,
869 text: RenderedText,
870}
871
872#[derive(Clone)]
873struct RenderedText {
874 lines: Rc<[RenderedLine]>,
875 links: Rc<[RenderedLink]>,
876}
877
878#[derive(Clone, Eq, PartialEq)]
879struct RenderedLink {
880 source_range: Range<usize>,
881 destination_url: SharedString,
882}
883
884impl RenderedText {
885 fn source_index_for_position(&self, position: Point<Pixels>) -> Result<usize, usize> {
886 let mut lines = self.lines.iter().peekable();
887
888 while let Some(line) = lines.next() {
889 let line_bounds = line.layout.bounds();
890 if position.y > line_bounds.bottom() {
891 if let Some(next_line) = lines.peek() {
892 if position.y < next_line.layout.bounds().top() {
893 return Err(line.source_end);
894 }
895 }
896
897 continue;
898 }
899
900 return line.source_index_for_position(position);
901 }
902
903 Err(self.lines.last().map_or(0, |line| line.source_end))
904 }
905
906 fn position_for_source_index(&self, source_index: usize) -> Option<(Point<Pixels>, Pixels)> {
907 for line in self.lines.iter() {
908 let line_source_start = line.source_mappings.first().unwrap().source_index;
909 if source_index < line_source_start {
910 break;
911 } else if source_index > line.source_end {
912 continue;
913 } else {
914 let line_height = line.layout.line_height();
915 let rendered_index_within_line = line.rendered_index_for_source_index(source_index);
916 let position = line.layout.position_for_index(rendered_index_within_line)?;
917 return Some((position, line_height));
918 }
919 }
920 None
921 }
922
923 fn link_for_position(&self, position: Point<Pixels>) -> Option<&RenderedLink> {
924 let source_index = self.source_index_for_position(position).ok()?;
925 self.links
926 .iter()
927 .find(|link| link.source_range.contains(&source_index))
928 }
929}