markdown.rs

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