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