markdown.rs

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