markdown.rs

  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}