markdown.rs

   1pub mod parser;
   2
   3use crate::parser::CodeBlockKind;
   4use gpui::{
   5    actions, point, quad, AnyElement, App, Bounds, ClipboardItem, CursorStyle, DispatchPhase,
   6    Edges, Entity, FocusHandle, Focusable, FontStyle, FontWeight, GlobalElementId, Hitbox, Hsla,
   7    KeyContext, Length, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent, Point, Render,
   8    Stateful, StrikethroughStyle, StyleRefinement, StyledText, Task, TextLayout, TextRun,
   9    TextStyle, TextStyleRefinement,
  10};
  11use language::{Language, LanguageRegistry, Rope};
  12use parser::{parse_links_only, parse_markdown, MarkdownEvent, MarkdownTag, MarkdownTagEnd};
  13
  14use std::{collections::HashMap, iter, mem, ops::Range, rc::Rc, sync::Arc};
  15use theme::SyntaxTheme;
  16use ui::{prelude::*, Tooltip};
  17use util::{ResultExt, TryFutureExt};
  18
  19#[derive(Clone)]
  20pub struct MarkdownStyle {
  21    pub base_text_style: TextStyle,
  22    pub code_block: StyleRefinement,
  23    pub inline_code: TextStyleRefinement,
  24    pub block_quote: TextStyleRefinement,
  25    pub link: TextStyleRefinement,
  26    pub rule_color: Hsla,
  27    pub block_quote_border_color: Hsla,
  28    pub syntax: Arc<SyntaxTheme>,
  29    pub selection_background_color: Hsla,
  30    pub heading: StyleRefinement,
  31}
  32
  33impl Default for MarkdownStyle {
  34    fn default() -> Self {
  35        Self {
  36            base_text_style: Default::default(),
  37            code_block: Default::default(),
  38            inline_code: Default::default(),
  39            block_quote: Default::default(),
  40            link: Default::default(),
  41            rule_color: Default::default(),
  42            block_quote_border_color: Default::default(),
  43            syntax: Arc::new(SyntaxTheme::default()),
  44            selection_background_color: Default::default(),
  45            heading: Default::default(),
  46        }
  47    }
  48}
  49
  50pub struct Markdown {
  51    source: SharedString,
  52    selection: Selection,
  53    pressed_link: Option<RenderedLink>,
  54    autoscroll_request: Option<usize>,
  55    style: MarkdownStyle,
  56    parsed_markdown: ParsedMarkdown,
  57    should_reparse: bool,
  58    pending_parse: Option<Task<Option<()>>>,
  59    focus_handle: FocusHandle,
  60    language_registry: Option<Arc<LanguageRegistry>>,
  61    fallback_code_block_language: Option<String>,
  62    open_url: Option<Box<dyn Fn(SharedString, &mut Window, &mut App)>>,
  63    options: Options,
  64}
  65
  66#[derive(Debug)]
  67struct Options {
  68    parse_links_only: bool,
  69    copy_code_block_buttons: bool,
  70}
  71
  72actions!(markdown, [Copy]);
  73
  74impl Markdown {
  75    pub fn new(
  76        source: SharedString,
  77        style: MarkdownStyle,
  78        language_registry: Option<Arc<LanguageRegistry>>,
  79        fallback_code_block_language: Option<String>,
  80        cx: &mut Context<Self>,
  81    ) -> Self {
  82        let focus_handle = cx.focus_handle();
  83        let mut this = Self {
  84            source,
  85            selection: Selection::default(),
  86            pressed_link: None,
  87            autoscroll_request: None,
  88            style,
  89            should_reparse: false,
  90            parsed_markdown: ParsedMarkdown::default(),
  91            pending_parse: None,
  92            focus_handle,
  93            language_registry,
  94            fallback_code_block_language,
  95            options: Options {
  96                parse_links_only: false,
  97                copy_code_block_buttons: true,
  98            },
  99            open_url: None,
 100        };
 101        this.parse(cx);
 102        this
 103    }
 104
 105    pub fn open_url(
 106        self,
 107        open_url: impl Fn(SharedString, &mut Window, &mut App) + 'static,
 108    ) -> Self {
 109        Self {
 110            open_url: Some(Box::new(open_url)),
 111            ..self
 112        }
 113    }
 114
 115    pub fn new_text(source: SharedString, style: MarkdownStyle, cx: &mut Context<Self>) -> Self {
 116        let focus_handle = cx.focus_handle();
 117        let mut this = Self {
 118            source,
 119            selection: Selection::default(),
 120            pressed_link: None,
 121            autoscroll_request: None,
 122            style,
 123            should_reparse: false,
 124            parsed_markdown: ParsedMarkdown::default(),
 125            pending_parse: None,
 126            focus_handle,
 127            language_registry: None,
 128            fallback_code_block_language: None,
 129            options: Options {
 130                parse_links_only: true,
 131                copy_code_block_buttons: true,
 132            },
 133            open_url: None,
 134        };
 135        this.parse(cx);
 136        this
 137    }
 138
 139    pub fn source(&self) -> &str {
 140        &self.source
 141    }
 142
 143    pub fn append(&mut self, text: &str, cx: &mut Context<Self>) {
 144        self.source = SharedString::new(self.source.to_string() + text);
 145        self.parse(cx);
 146    }
 147
 148    pub fn reset(&mut self, source: SharedString, cx: &mut Context<Self>) {
 149        if source == self.source() {
 150            return;
 151        }
 152        self.source = source;
 153        self.selection = Selection::default();
 154        self.autoscroll_request = None;
 155        self.pending_parse = None;
 156        self.should_reparse = false;
 157        self.parsed_markdown = ParsedMarkdown::default();
 158        self.parse(cx);
 159    }
 160
 161    pub fn parsed_markdown(&self) -> &ParsedMarkdown {
 162        &self.parsed_markdown
 163    }
 164
 165    fn copy(&self, text: &RenderedText, _: &mut Window, cx: &mut Context<Self>) {
 166        if self.selection.end <= self.selection.start {
 167            return;
 168        }
 169        let text = text.text_for_range(self.selection.start..self.selection.end);
 170        cx.write_to_clipboard(ClipboardItem::new_string(text));
 171    }
 172
 173    fn parse(&mut self, cx: &mut Context<Self>) {
 174        if self.source.is_empty() {
 175            return;
 176        }
 177
 178        if self.pending_parse.is_some() {
 179            self.should_reparse = true;
 180            return;
 181        }
 182
 183        let source = self.source.clone();
 184        let parse_text_only = self.options.parse_links_only;
 185        let language_registry = self.language_registry.clone();
 186        let fallback = self.fallback_code_block_language.clone();
 187        let parsed = cx.background_spawn(async move {
 188            if parse_text_only {
 189                return anyhow::Ok(ParsedMarkdown {
 190                    events: Arc::from(parse_links_only(source.as_ref())),
 191                    source,
 192                    languages: HashMap::default(),
 193                });
 194            }
 195            let (events, language_names) = parse_markdown(&source);
 196            let mut languages = HashMap::with_capacity(language_names.len());
 197            for name in language_names {
 198                if let Some(registry) = language_registry.as_ref() {
 199                    let language = if !name.is_empty() {
 200                        registry.language_for_name(&name)
 201                    } else if let Some(fallback) = &fallback {
 202                        registry.language_for_name(fallback)
 203                    } else {
 204                        continue;
 205                    };
 206                    if let Ok(language) = language.await {
 207                        languages.insert(name, language);
 208                    }
 209                }
 210            }
 211            anyhow::Ok(ParsedMarkdown {
 212                source,
 213                events: Arc::from(events),
 214                languages,
 215            })
 216        });
 217
 218        self.should_reparse = false;
 219        self.pending_parse = Some(cx.spawn(|this, mut cx| {
 220            async move {
 221                let parsed = parsed.await?;
 222                this.update(&mut cx, |this, cx| {
 223                    this.parsed_markdown = parsed;
 224                    this.pending_parse.take();
 225                    if this.should_reparse {
 226                        this.parse(cx);
 227                    }
 228                    cx.notify();
 229                })
 230                .ok();
 231                anyhow::Ok(())
 232            }
 233            .log_err()
 234        }));
 235    }
 236
 237    pub fn copy_code_block_buttons(mut self, should_copy: bool) -> Self {
 238        self.options.copy_code_block_buttons = should_copy;
 239        self
 240    }
 241}
 242
 243impl Render for Markdown {
 244    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 245        MarkdownElement::new(cx.entity().clone(), self.style.clone())
 246    }
 247}
 248
 249impl Focusable for Markdown {
 250    fn focus_handle(&self, _cx: &App) -> FocusHandle {
 251        self.focus_handle.clone()
 252    }
 253}
 254
 255#[derive(Copy, Clone, Default, Debug)]
 256struct Selection {
 257    start: usize,
 258    end: usize,
 259    reversed: bool,
 260    pending: bool,
 261}
 262
 263impl Selection {
 264    fn set_head(&mut self, head: usize) {
 265        if head < self.tail() {
 266            if !self.reversed {
 267                self.end = self.start;
 268                self.reversed = true;
 269            }
 270            self.start = head;
 271        } else {
 272            if self.reversed {
 273                self.start = self.end;
 274                self.reversed = false;
 275            }
 276            self.end = head;
 277        }
 278    }
 279
 280    fn tail(&self) -> usize {
 281        if self.reversed {
 282            self.end
 283        } else {
 284            self.start
 285        }
 286    }
 287}
 288
 289#[derive(Default)]
 290pub struct ParsedMarkdown {
 291    source: SharedString,
 292    events: Arc<[(Range<usize>, MarkdownEvent)]>,
 293    languages: HashMap<SharedString, Arc<Language>>,
 294}
 295
 296impl ParsedMarkdown {
 297    pub fn source(&self) -> &SharedString {
 298        &self.source
 299    }
 300
 301    pub fn events(&self) -> &Arc<[(Range<usize>, MarkdownEvent)]> {
 302        &self.events
 303    }
 304}
 305
 306pub struct MarkdownElement {
 307    markdown: Entity<Markdown>,
 308    style: MarkdownStyle,
 309}
 310
 311impl MarkdownElement {
 312    fn new(markdown: Entity<Markdown>, style: MarkdownStyle) -> Self {
 313        Self { markdown, style }
 314    }
 315
 316    fn paint_selection(
 317        &self,
 318        bounds: Bounds<Pixels>,
 319        rendered_text: &RenderedText,
 320        window: &mut Window,
 321        cx: &mut App,
 322    ) {
 323        let selection = self.markdown.read(cx).selection;
 324        let selection_start = rendered_text.position_for_source_index(selection.start);
 325        let selection_end = rendered_text.position_for_source_index(selection.end);
 326
 327        if let Some(((start_position, start_line_height), (end_position, end_line_height))) =
 328            selection_start.zip(selection_end)
 329        {
 330            if start_position.y == end_position.y {
 331                window.paint_quad(quad(
 332                    Bounds::from_corners(
 333                        start_position,
 334                        point(end_position.x, end_position.y + end_line_height),
 335                    ),
 336                    Pixels::ZERO,
 337                    self.style.selection_background_color,
 338                    Edges::default(),
 339                    Hsla::transparent_black(),
 340                ));
 341            } else {
 342                window.paint_quad(quad(
 343                    Bounds::from_corners(
 344                        start_position,
 345                        point(bounds.right(), start_position.y + start_line_height),
 346                    ),
 347                    Pixels::ZERO,
 348                    self.style.selection_background_color,
 349                    Edges::default(),
 350                    Hsla::transparent_black(),
 351                ));
 352
 353                if end_position.y > start_position.y + start_line_height {
 354                    window.paint_quad(quad(
 355                        Bounds::from_corners(
 356                            point(bounds.left(), start_position.y + start_line_height),
 357                            point(bounds.right(), end_position.y),
 358                        ),
 359                        Pixels::ZERO,
 360                        self.style.selection_background_color,
 361                        Edges::default(),
 362                        Hsla::transparent_black(),
 363                    ));
 364                }
 365
 366                window.paint_quad(quad(
 367                    Bounds::from_corners(
 368                        point(bounds.left(), end_position.y),
 369                        point(end_position.x, end_position.y + end_line_height),
 370                    ),
 371                    Pixels::ZERO,
 372                    self.style.selection_background_color,
 373                    Edges::default(),
 374                    Hsla::transparent_black(),
 375                ));
 376            }
 377        }
 378    }
 379
 380    fn paint_mouse_listeners(
 381        &self,
 382        hitbox: &Hitbox,
 383        rendered_text: &RenderedText,
 384        window: &mut Window,
 385        cx: &mut App,
 386    ) {
 387        let is_hovering_link = hitbox.is_hovered(window)
 388            && !self.markdown.read(cx).selection.pending
 389            && rendered_text
 390                .link_for_position(window.mouse_position())
 391                .is_some();
 392
 393        if is_hovering_link {
 394            window.set_cursor_style(CursorStyle::PointingHand, hitbox);
 395        } else {
 396            window.set_cursor_style(CursorStyle::IBeam, hitbox);
 397        }
 398
 399        self.on_mouse_event(window, cx, {
 400            let rendered_text = rendered_text.clone();
 401            let hitbox = hitbox.clone();
 402            move |markdown, event: &MouseDownEvent, phase, window, cx| {
 403                if hitbox.is_hovered(window) {
 404                    if phase.bubble() {
 405                        if let Some(link) = rendered_text.link_for_position(event.position) {
 406                            markdown.pressed_link = Some(link.clone());
 407                        } else {
 408                            let source_index =
 409                                match rendered_text.source_index_for_position(event.position) {
 410                                    Ok(ix) | Err(ix) => ix,
 411                                };
 412                            let range = if event.click_count == 2 {
 413                                rendered_text.surrounding_word_range(source_index)
 414                            } else if event.click_count == 3 {
 415                                rendered_text.surrounding_line_range(source_index)
 416                            } else {
 417                                source_index..source_index
 418                            };
 419                            markdown.selection = Selection {
 420                                start: range.start,
 421                                end: range.end,
 422                                reversed: false,
 423                                pending: true,
 424                            };
 425                            window.focus(&markdown.focus_handle);
 426                            window.prevent_default();
 427                        }
 428
 429                        cx.notify();
 430                    }
 431                } else if phase.capture() {
 432                    markdown.selection = Selection::default();
 433                    markdown.pressed_link = None;
 434                    cx.notify();
 435                }
 436            }
 437        });
 438        self.on_mouse_event(window, cx, {
 439            let rendered_text = rendered_text.clone();
 440            let hitbox = hitbox.clone();
 441            let was_hovering_link = is_hovering_link;
 442            move |markdown, event: &MouseMoveEvent, phase, window, cx| {
 443                if phase.capture() {
 444                    return;
 445                }
 446
 447                if markdown.selection.pending {
 448                    let source_index = match rendered_text.source_index_for_position(event.position)
 449                    {
 450                        Ok(ix) | Err(ix) => ix,
 451                    };
 452                    markdown.selection.set_head(source_index);
 453                    markdown.autoscroll_request = Some(source_index);
 454                    cx.notify();
 455                } else {
 456                    let is_hovering_link = hitbox.is_hovered(window)
 457                        && rendered_text.link_for_position(event.position).is_some();
 458                    if is_hovering_link != was_hovering_link {
 459                        cx.notify();
 460                    }
 461                }
 462            }
 463        });
 464        self.on_mouse_event(window, cx, {
 465            let rendered_text = rendered_text.clone();
 466            move |markdown, event: &MouseUpEvent, phase, window, cx| {
 467                if phase.bubble() {
 468                    if let Some(pressed_link) = markdown.pressed_link.take() {
 469                        if Some(&pressed_link) == rendered_text.link_for_position(event.position) {
 470                            if let Some(open_url) = markdown.open_url.as_mut() {
 471                                open_url(pressed_link.destination_url, window, cx);
 472                            } else {
 473                                cx.open_url(&pressed_link.destination_url);
 474                            }
 475                        }
 476                    }
 477                } else if markdown.selection.pending {
 478                    markdown.selection.pending = false;
 479                    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 480                    {
 481                        let text = rendered_text
 482                            .text_for_range(markdown.selection.start..markdown.selection.end);
 483                        cx.write_to_primary(ClipboardItem::new_string(text))
 484                    }
 485                    cx.notify();
 486                }
 487            }
 488        });
 489    }
 490
 491    fn autoscroll(
 492        &self,
 493        rendered_text: &RenderedText,
 494        window: &mut Window,
 495        cx: &mut App,
 496    ) -> Option<()> {
 497        let autoscroll_index = self
 498            .markdown
 499            .update(cx, |markdown, _| markdown.autoscroll_request.take())?;
 500        let (position, line_height) = rendered_text.position_for_source_index(autoscroll_index)?;
 501
 502        let text_style = self.style.base_text_style.clone();
 503        let font_id = window.text_system().resolve_font(&text_style.font());
 504        let font_size = text_style.font_size.to_pixels(window.rem_size());
 505        let em_width = window.text_system().em_width(font_id, font_size).unwrap();
 506        window.request_autoscroll(Bounds::from_corners(
 507            point(position.x - 3. * em_width, position.y - 3. * line_height),
 508            point(position.x + 3. * em_width, position.y + 3. * line_height),
 509        ));
 510        Some(())
 511    }
 512
 513    fn on_mouse_event<T: MouseEvent>(
 514        &self,
 515        window: &mut Window,
 516        _cx: &mut App,
 517        mut f: impl 'static
 518            + FnMut(&mut Markdown, &T, DispatchPhase, &mut Window, &mut Context<Markdown>),
 519    ) {
 520        window.on_mouse_event({
 521            let markdown = self.markdown.downgrade();
 522            move |event, phase, window, cx| {
 523                markdown
 524                    .update(cx, |markdown, cx| f(markdown, event, phase, window, cx))
 525                    .log_err();
 526            }
 527        });
 528    }
 529}
 530
 531impl Element for MarkdownElement {
 532    type RequestLayoutState = RenderedMarkdown;
 533    type PrepaintState = Hitbox;
 534
 535    fn id(&self) -> Option<ElementId> {
 536        None
 537    }
 538
 539    fn request_layout(
 540        &mut self,
 541        _id: Option<&GlobalElementId>,
 542        window: &mut Window,
 543        cx: &mut App,
 544    ) -> (gpui::LayoutId, Self::RequestLayoutState) {
 545        let mut builder = MarkdownElementBuilder::new(
 546            self.style.base_text_style.clone(),
 547            self.style.syntax.clone(),
 548        );
 549        let parsed_markdown = &self.markdown.read(cx).parsed_markdown;
 550        let markdown_end = if let Some(last) = parsed_markdown.events.last() {
 551            last.0.end
 552        } else {
 553            0
 554        };
 555        for (range, event) in parsed_markdown.events.iter() {
 556            match event {
 557                MarkdownEvent::Start(tag) => {
 558                    match tag {
 559                        MarkdownTag::Paragraph => {
 560                            builder.push_div(
 561                                div().mb_2().line_height(rems(1.3)),
 562                                range,
 563                                markdown_end,
 564                            );
 565                        }
 566                        MarkdownTag::Heading { level, .. } => {
 567                            let mut heading = div().mb_2();
 568                            heading = match level {
 569                                pulldown_cmark::HeadingLevel::H1 => heading.text_3xl(),
 570                                pulldown_cmark::HeadingLevel::H2 => heading.text_2xl(),
 571                                pulldown_cmark::HeadingLevel::H3 => heading.text_xl(),
 572                                pulldown_cmark::HeadingLevel::H4 => heading.text_lg(),
 573                                _ => heading,
 574                            };
 575                            heading.style().refine(&self.style.heading);
 576                            builder.push_text_style(
 577                                self.style.heading.text_style().clone().unwrap_or_default(),
 578                            );
 579                            builder.push_div(heading, range, markdown_end);
 580                        }
 581                        MarkdownTag::BlockQuote => {
 582                            builder.push_text_style(self.style.block_quote.clone());
 583                            builder.push_div(
 584                                div()
 585                                    .pl_4()
 586                                    .mb_2()
 587                                    .border_l_4()
 588                                    .border_color(self.style.block_quote_border_color),
 589                                range,
 590                                markdown_end,
 591                            );
 592                        }
 593                        MarkdownTag::CodeBlock(kind) => {
 594                            let language = if let CodeBlockKind::Fenced(language) = kind {
 595                                parsed_markdown.languages.get(language).cloned()
 596                            } else {
 597                                None
 598                            };
 599
 600                            let mut d = div().w_full().rounded_lg();
 601                            d.style().refine(&self.style.code_block);
 602                            if let Some(code_block_text_style) = &self.style.code_block.text {
 603                                builder.push_text_style(code_block_text_style.to_owned());
 604                            }
 605                            builder.push_code_block(language);
 606                            builder.push_div(d, range, markdown_end);
 607                        }
 608                        MarkdownTag::HtmlBlock => builder.push_div(div(), range, markdown_end),
 609                        MarkdownTag::List(bullet_index) => {
 610                            builder.push_list(*bullet_index);
 611                            builder.push_div(div().pl_4(), range, markdown_end);
 612                        }
 613                        MarkdownTag::Item => {
 614                            let bullet = if let Some(bullet_index) = builder.next_bullet_index() {
 615                                format!("{}.", bullet_index)
 616                            } else {
 617                                "".to_string()
 618                            };
 619                            builder.push_div(
 620                                div()
 621                                    .mb_1()
 622                                    .h_flex()
 623                                    .items_start()
 624                                    .gap_1()
 625                                    .line_height(rems(1.3))
 626                                    .child(bullet),
 627                                range,
 628                                markdown_end,
 629                            );
 630                            // Without `w_0`, text doesn't wrap to the width of the container.
 631                            builder.push_div(div().flex_1().w_0(), range, markdown_end);
 632                        }
 633                        MarkdownTag::Emphasis => builder.push_text_style(TextStyleRefinement {
 634                            font_style: Some(FontStyle::Italic),
 635                            ..Default::default()
 636                        }),
 637                        MarkdownTag::Strong => builder.push_text_style(TextStyleRefinement {
 638                            font_weight: Some(FontWeight::BOLD),
 639                            ..Default::default()
 640                        }),
 641                        MarkdownTag::Strikethrough => {
 642                            builder.push_text_style(TextStyleRefinement {
 643                                strikethrough: Some(StrikethroughStyle {
 644                                    thickness: px(1.),
 645                                    color: None,
 646                                }),
 647                                ..Default::default()
 648                            })
 649                        }
 650                        MarkdownTag::Link { dest_url, .. } => {
 651                            if builder.code_block_stack.is_empty() {
 652                                builder.push_link(dest_url.clone(), range.clone());
 653                                builder.push_text_style(self.style.link.clone())
 654                            }
 655                        }
 656                        MarkdownTag::MetadataBlock(_) => {}
 657                        _ => log::error!("unsupported markdown tag {:?}", tag),
 658                    }
 659                }
 660                MarkdownEvent::End(tag) => match tag {
 661                    MarkdownTagEnd::Paragraph => {
 662                        builder.pop_div();
 663                    }
 664                    MarkdownTagEnd::Heading(_) => {
 665                        builder.pop_div();
 666                        builder.pop_text_style()
 667                    }
 668                    MarkdownTagEnd::BlockQuote(_kind) => {
 669                        builder.pop_text_style();
 670                        builder.pop_div()
 671                    }
 672                    MarkdownTagEnd::CodeBlock => {
 673                        builder.trim_trailing_newline();
 674
 675                        if self.markdown.read(cx).options.copy_code_block_buttons {
 676                            builder.flush_text();
 677                            builder.modify_current_div(|el| {
 678                                let id =
 679                                    ElementId::NamedInteger("copy-markdown-code".into(), range.end);
 680                                let copy_button = div().absolute().top_1().right_1().w_5().child(
 681                                    IconButton::new(id, IconName::Copy)
 682                                        .icon_color(Color::Muted)
 683                                        .shape(ui::IconButtonShape::Square)
 684                                        .tooltip(Tooltip::text("Copy Code Block"))
 685                                        .on_click({
 686                                            let code = without_fences(
 687                                                parsed_markdown.source()[range.clone()].trim(),
 688                                            )
 689                                            .to_string();
 690
 691                                            move |_, _, cx| {
 692                                                cx.write_to_clipboard(ClipboardItem::new_string(
 693                                                    code.clone(),
 694                                                ))
 695                                            }
 696                                        }),
 697                                );
 698
 699                                el.child(copy_button)
 700                            });
 701                        }
 702
 703                        builder.pop_div();
 704                        builder.pop_code_block();
 705                        if self.style.code_block.text.is_some() {
 706                            builder.pop_text_style();
 707                        }
 708                    }
 709                    MarkdownTagEnd::HtmlBlock => builder.pop_div(),
 710                    MarkdownTagEnd::List(_) => {
 711                        builder.pop_list();
 712                        builder.pop_div();
 713                    }
 714                    MarkdownTagEnd::Item => {
 715                        builder.pop_div();
 716                        builder.pop_div();
 717                    }
 718                    MarkdownTagEnd::Emphasis => builder.pop_text_style(),
 719                    MarkdownTagEnd::Strong => builder.pop_text_style(),
 720                    MarkdownTagEnd::Strikethrough => builder.pop_text_style(),
 721                    MarkdownTagEnd::Link => {
 722                        if builder.code_block_stack.is_empty() {
 723                            builder.pop_text_style()
 724                        }
 725                    }
 726                    _ => log::error!("unsupported markdown tag end: {:?}", tag),
 727                },
 728                MarkdownEvent::Text(parsed) => {
 729                    builder.push_text(parsed, range.start);
 730                }
 731                MarkdownEvent::Code => {
 732                    builder.push_text_style(self.style.inline_code.clone());
 733                    builder.push_text(&parsed_markdown.source[range.clone()], range.start);
 734                    builder.pop_text_style();
 735                }
 736                MarkdownEvent::Html => {
 737                    builder.push_text(&parsed_markdown.source[range.clone()], range.start);
 738                }
 739                MarkdownEvent::InlineHtml => {
 740                    builder.push_text(&parsed_markdown.source[range.clone()], range.start);
 741                }
 742                MarkdownEvent::Rule => {
 743                    builder.push_div(
 744                        div()
 745                            .border_b_1()
 746                            .my_2()
 747                            .border_color(self.style.rule_color),
 748                        range,
 749                        markdown_end,
 750                    );
 751                    builder.pop_div()
 752                }
 753                MarkdownEvent::SoftBreak => builder.push_text(" ", range.start),
 754                MarkdownEvent::HardBreak => builder.push_text("\n", range.start),
 755                _ => log::error!("unsupported markdown event {:?}", event),
 756            }
 757        }
 758        let mut rendered_markdown = builder.build();
 759        let child_layout_id = rendered_markdown.element.request_layout(window, cx);
 760        let layout_id = window.request_layout(gpui::Style::default(), [child_layout_id], cx);
 761        (layout_id, rendered_markdown)
 762    }
 763
 764    fn prepaint(
 765        &mut self,
 766        _id: Option<&GlobalElementId>,
 767        bounds: Bounds<Pixels>,
 768        rendered_markdown: &mut Self::RequestLayoutState,
 769        window: &mut Window,
 770        cx: &mut App,
 771    ) -> Self::PrepaintState {
 772        let focus_handle = self.markdown.read(cx).focus_handle.clone();
 773        window.set_focus_handle(&focus_handle, cx);
 774
 775        let hitbox = window.insert_hitbox(bounds, false);
 776        rendered_markdown.element.prepaint(window, cx);
 777        self.autoscroll(&rendered_markdown.text, window, cx);
 778        hitbox
 779    }
 780
 781    fn paint(
 782        &mut self,
 783        _id: Option<&GlobalElementId>,
 784        bounds: Bounds<Pixels>,
 785        rendered_markdown: &mut Self::RequestLayoutState,
 786        hitbox: &mut Self::PrepaintState,
 787        window: &mut Window,
 788        cx: &mut App,
 789    ) {
 790        let mut context = KeyContext::default();
 791        context.add("Markdown");
 792        window.set_key_context(context);
 793        let entity = self.markdown.clone();
 794        window.on_action(std::any::TypeId::of::<crate::Copy>(), {
 795            let text = rendered_markdown.text.clone();
 796            move |_, phase, window, cx| {
 797                let text = text.clone();
 798                if phase == DispatchPhase::Bubble {
 799                    entity.update(cx, move |this, cx| this.copy(&text, window, cx))
 800                }
 801            }
 802        });
 803
 804        self.paint_mouse_listeners(hitbox, &rendered_markdown.text, window, cx);
 805        rendered_markdown.element.paint(window, cx);
 806        self.paint_selection(bounds, &rendered_markdown.text, window, cx);
 807    }
 808}
 809
 810impl IntoElement for MarkdownElement {
 811    type Element = Self;
 812
 813    fn into_element(self) -> Self::Element {
 814        self
 815    }
 816}
 817
 818enum AnyDiv {
 819    Div(Div),
 820    Stateful(Stateful<Div>),
 821}
 822
 823impl AnyDiv {
 824    fn into_any_element(self) -> AnyElement {
 825        match self {
 826            Self::Div(div) => div.into_any_element(),
 827            Self::Stateful(div) => div.into_any_element(),
 828        }
 829    }
 830}
 831
 832impl From<Div> for AnyDiv {
 833    fn from(value: Div) -> Self {
 834        Self::Div(value)
 835    }
 836}
 837
 838impl From<Stateful<Div>> for AnyDiv {
 839    fn from(value: Stateful<Div>) -> Self {
 840        Self::Stateful(value)
 841    }
 842}
 843
 844impl Styled for AnyDiv {
 845    fn style(&mut self) -> &mut StyleRefinement {
 846        match self {
 847            Self::Div(div) => div.style(),
 848            Self::Stateful(div) => div.style(),
 849        }
 850    }
 851}
 852
 853impl ParentElement for AnyDiv {
 854    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
 855        match self {
 856            Self::Div(div) => div.extend(elements),
 857            Self::Stateful(div) => div.extend(elements),
 858        }
 859    }
 860}
 861
 862struct MarkdownElementBuilder {
 863    div_stack: Vec<AnyDiv>,
 864    rendered_lines: Vec<RenderedLine>,
 865    pending_line: PendingLine,
 866    rendered_links: Vec<RenderedLink>,
 867    current_source_index: usize,
 868    base_text_style: TextStyle,
 869    text_style_stack: Vec<TextStyleRefinement>,
 870    code_block_stack: Vec<Option<Arc<Language>>>,
 871    list_stack: Vec<ListStackEntry>,
 872    syntax_theme: Arc<SyntaxTheme>,
 873}
 874
 875#[derive(Default)]
 876struct PendingLine {
 877    text: String,
 878    runs: Vec<TextRun>,
 879    source_mappings: Vec<SourceMapping>,
 880}
 881
 882struct ListStackEntry {
 883    bullet_index: Option<u64>,
 884}
 885
 886impl MarkdownElementBuilder {
 887    fn new(base_text_style: TextStyle, syntax_theme: Arc<SyntaxTheme>) -> Self {
 888        Self {
 889            div_stack: vec![div().debug_selector(|| "inner".into()).into()],
 890            rendered_lines: Vec::new(),
 891            pending_line: PendingLine::default(),
 892            rendered_links: Vec::new(),
 893            current_source_index: 0,
 894            base_text_style,
 895            text_style_stack: Vec::new(),
 896            code_block_stack: Vec::new(),
 897            list_stack: Vec::new(),
 898            syntax_theme,
 899        }
 900    }
 901
 902    fn push_text_style(&mut self, style: TextStyleRefinement) {
 903        self.text_style_stack.push(style);
 904    }
 905
 906    fn text_style(&self) -> TextStyle {
 907        let mut style = self.base_text_style.clone();
 908        for refinement in &self.text_style_stack {
 909            style.refine(refinement);
 910        }
 911        style
 912    }
 913
 914    fn pop_text_style(&mut self) {
 915        self.text_style_stack.pop();
 916    }
 917
 918    fn push_div(&mut self, div: impl Into<AnyDiv>, range: &Range<usize>, markdown_end: usize) {
 919        let mut div = div.into();
 920        self.flush_text();
 921
 922        if range.start == 0 {
 923            // Remove the top margin on the first element.
 924            div.style().refine(&StyleRefinement {
 925                margin: gpui::EdgesRefinement {
 926                    top: Some(Length::Definite(px(0.).into())),
 927                    left: None,
 928                    right: None,
 929                    bottom: None,
 930                },
 931                ..Default::default()
 932            });
 933        }
 934
 935        if range.end == markdown_end {
 936            div.style().refine(&StyleRefinement {
 937                margin: gpui::EdgesRefinement {
 938                    top: None,
 939                    left: None,
 940                    right: None,
 941                    bottom: Some(Length::Definite(rems(0.).into())),
 942                },
 943                ..Default::default()
 944            });
 945        }
 946
 947        self.div_stack.push(div);
 948    }
 949
 950    fn modify_current_div(&mut self, f: impl FnOnce(AnyDiv) -> AnyDiv) {
 951        self.flush_text();
 952        if let Some(div) = self.div_stack.pop() {
 953            self.div_stack.push(f(div));
 954        }
 955    }
 956
 957    fn pop_div(&mut self) {
 958        self.flush_text();
 959        let div = self.div_stack.pop().unwrap().into_any_element();
 960        self.div_stack.last_mut().unwrap().extend(iter::once(div));
 961    }
 962
 963    fn push_list(&mut self, bullet_index: Option<u64>) {
 964        self.list_stack.push(ListStackEntry { bullet_index });
 965    }
 966
 967    fn next_bullet_index(&mut self) -> Option<u64> {
 968        self.list_stack.last_mut().and_then(|entry| {
 969            let item_index = entry.bullet_index.as_mut()?;
 970            *item_index += 1;
 971            Some(*item_index - 1)
 972        })
 973    }
 974
 975    fn pop_list(&mut self) {
 976        self.list_stack.pop();
 977    }
 978
 979    fn push_code_block(&mut self, language: Option<Arc<Language>>) {
 980        self.code_block_stack.push(language);
 981    }
 982
 983    fn pop_code_block(&mut self) {
 984        self.code_block_stack.pop();
 985    }
 986
 987    fn push_link(&mut self, destination_url: SharedString, source_range: Range<usize>) {
 988        self.rendered_links.push(RenderedLink {
 989            source_range,
 990            destination_url,
 991        });
 992    }
 993
 994    fn push_text(&mut self, text: &str, source_index: usize) {
 995        self.pending_line.source_mappings.push(SourceMapping {
 996            rendered_index: self.pending_line.text.len(),
 997            source_index,
 998        });
 999        self.pending_line.text.push_str(text);
1000        self.current_source_index = source_index + text.len();
1001
1002        if let Some(Some(language)) = self.code_block_stack.last() {
1003            let mut offset = 0;
1004            for (range, highlight_id) in language.highlight_text(&Rope::from(text), 0..text.len()) {
1005                if range.start > offset {
1006                    self.pending_line
1007                        .runs
1008                        .push(self.text_style().to_run(range.start - offset));
1009                }
1010
1011                let mut run_style = self.text_style();
1012                if let Some(highlight) = highlight_id.style(&self.syntax_theme) {
1013                    run_style = run_style.highlight(highlight);
1014                }
1015                self.pending_line.runs.push(run_style.to_run(range.len()));
1016                offset = range.end;
1017            }
1018
1019            if offset < text.len() {
1020                self.pending_line
1021                    .runs
1022                    .push(self.text_style().to_run(text.len() - offset));
1023            }
1024        } else {
1025            self.pending_line
1026                .runs
1027                .push(self.text_style().to_run(text.len()));
1028        }
1029    }
1030
1031    fn trim_trailing_newline(&mut self) {
1032        if self.pending_line.text.ends_with('\n') {
1033            self.pending_line
1034                .text
1035                .truncate(self.pending_line.text.len() - 1);
1036            self.pending_line.runs.last_mut().unwrap().len -= 1;
1037            self.current_source_index -= 1;
1038        }
1039    }
1040
1041    fn flush_text(&mut self) {
1042        let line = mem::take(&mut self.pending_line);
1043        if line.text.is_empty() {
1044            return;
1045        }
1046
1047        let text = StyledText::new(line.text).with_runs(line.runs);
1048        self.rendered_lines.push(RenderedLine {
1049            layout: text.layout().clone(),
1050            source_mappings: line.source_mappings,
1051            source_end: self.current_source_index,
1052        });
1053        self.div_stack.last_mut().unwrap().extend([text.into_any()]);
1054    }
1055
1056    fn build(mut self) -> RenderedMarkdown {
1057        debug_assert_eq!(self.div_stack.len(), 1);
1058        self.flush_text();
1059        RenderedMarkdown {
1060            element: self.div_stack.pop().unwrap().into_any_element(),
1061            text: RenderedText {
1062                lines: self.rendered_lines.into(),
1063                links: self.rendered_links.into(),
1064            },
1065        }
1066    }
1067}
1068
1069struct RenderedLine {
1070    layout: TextLayout,
1071    source_mappings: Vec<SourceMapping>,
1072    source_end: usize,
1073}
1074
1075impl RenderedLine {
1076    fn rendered_index_for_source_index(&self, source_index: usize) -> usize {
1077        let mapping = match self
1078            .source_mappings
1079            .binary_search_by_key(&source_index, |probe| probe.source_index)
1080        {
1081            Ok(ix) => &self.source_mappings[ix],
1082            Err(ix) => &self.source_mappings[ix - 1],
1083        };
1084        mapping.rendered_index + (source_index - mapping.source_index)
1085    }
1086
1087    fn source_index_for_rendered_index(&self, rendered_index: usize) -> usize {
1088        let mapping = match self
1089            .source_mappings
1090            .binary_search_by_key(&rendered_index, |probe| probe.rendered_index)
1091        {
1092            Ok(ix) => &self.source_mappings[ix],
1093            Err(ix) => &self.source_mappings[ix - 1],
1094        };
1095        mapping.source_index + (rendered_index - mapping.rendered_index)
1096    }
1097
1098    fn source_index_for_position(&self, position: Point<Pixels>) -> Result<usize, usize> {
1099        let line_rendered_index;
1100        let out_of_bounds;
1101        match self.layout.index_for_position(position) {
1102            Ok(ix) => {
1103                line_rendered_index = ix;
1104                out_of_bounds = false;
1105            }
1106            Err(ix) => {
1107                line_rendered_index = ix;
1108                out_of_bounds = true;
1109            }
1110        };
1111        let source_index = self.source_index_for_rendered_index(line_rendered_index);
1112        if out_of_bounds {
1113            Err(source_index)
1114        } else {
1115            Ok(source_index)
1116        }
1117    }
1118}
1119
1120#[derive(Copy, Clone, Debug, Default)]
1121struct SourceMapping {
1122    rendered_index: usize,
1123    source_index: usize,
1124}
1125
1126pub struct RenderedMarkdown {
1127    element: AnyElement,
1128    text: RenderedText,
1129}
1130
1131#[derive(Clone)]
1132struct RenderedText {
1133    lines: Rc<[RenderedLine]>,
1134    links: Rc<[RenderedLink]>,
1135}
1136
1137#[derive(Clone, Eq, PartialEq)]
1138struct RenderedLink {
1139    source_range: Range<usize>,
1140    destination_url: SharedString,
1141}
1142
1143impl RenderedText {
1144    fn source_index_for_position(&self, position: Point<Pixels>) -> Result<usize, usize> {
1145        let mut lines = self.lines.iter().peekable();
1146
1147        while let Some(line) = lines.next() {
1148            let line_bounds = line.layout.bounds();
1149            if position.y > line_bounds.bottom() {
1150                if let Some(next_line) = lines.peek() {
1151                    if position.y < next_line.layout.bounds().top() {
1152                        return Err(line.source_end);
1153                    }
1154                }
1155
1156                continue;
1157            }
1158
1159            return line.source_index_for_position(position);
1160        }
1161
1162        Err(self.lines.last().map_or(0, |line| line.source_end))
1163    }
1164
1165    fn position_for_source_index(&self, source_index: usize) -> Option<(Point<Pixels>, Pixels)> {
1166        for line in self.lines.iter() {
1167            let line_source_start = line.source_mappings.first().unwrap().source_index;
1168            if source_index < line_source_start {
1169                break;
1170            } else if source_index > line.source_end {
1171                continue;
1172            } else {
1173                let line_height = line.layout.line_height();
1174                let rendered_index_within_line = line.rendered_index_for_source_index(source_index);
1175                let position = line.layout.position_for_index(rendered_index_within_line)?;
1176                return Some((position, line_height));
1177            }
1178        }
1179        None
1180    }
1181
1182    fn surrounding_word_range(&self, source_index: usize) -> Range<usize> {
1183        for line in self.lines.iter() {
1184            if source_index > line.source_end {
1185                continue;
1186            }
1187
1188            let line_rendered_start = line.source_mappings.first().unwrap().rendered_index;
1189            let rendered_index_in_line =
1190                line.rendered_index_for_source_index(source_index) - line_rendered_start;
1191            let text = line.layout.text();
1192            let previous_space = if let Some(idx) = text[0..rendered_index_in_line].rfind(' ') {
1193                idx + ' '.len_utf8()
1194            } else {
1195                0
1196            };
1197            let next_space = if let Some(idx) = text[rendered_index_in_line..].find(' ') {
1198                rendered_index_in_line + idx
1199            } else {
1200                text.len()
1201            };
1202
1203            return line.source_index_for_rendered_index(line_rendered_start + previous_space)
1204                ..line.source_index_for_rendered_index(line_rendered_start + next_space);
1205        }
1206
1207        source_index..source_index
1208    }
1209
1210    fn surrounding_line_range(&self, source_index: usize) -> Range<usize> {
1211        for line in self.lines.iter() {
1212            if source_index > line.source_end {
1213                continue;
1214            }
1215            let line_source_start = line.source_mappings.first().unwrap().source_index;
1216            return line_source_start..line.source_end;
1217        }
1218
1219        source_index..source_index
1220    }
1221
1222    fn text_for_range(&self, range: Range<usize>) -> String {
1223        let mut ret = vec![];
1224
1225        for line in self.lines.iter() {
1226            if range.start > line.source_end {
1227                continue;
1228            }
1229            let line_source_start = line.source_mappings.first().unwrap().source_index;
1230            if range.end < line_source_start {
1231                break;
1232            }
1233
1234            let text = line.layout.text();
1235
1236            let start = if range.start < line_source_start {
1237                0
1238            } else {
1239                line.rendered_index_for_source_index(range.start)
1240            };
1241            let end = if range.end > line.source_end {
1242                line.rendered_index_for_source_index(line.source_end)
1243            } else {
1244                line.rendered_index_for_source_index(range.end)
1245            }
1246            .min(text.len());
1247
1248            ret.push(text[start..end].to_string());
1249        }
1250        ret.join("\n")
1251    }
1252
1253    fn link_for_position(&self, position: Point<Pixels>) -> Option<&RenderedLink> {
1254        let source_index = self.source_index_for_position(position).ok()?;
1255        self.links
1256            .iter()
1257            .find(|link| link.source_range.contains(&source_index))
1258    }
1259}
1260
1261/// Some markdown blocks are indented, and others have e.g. ```rust … ``` around them.
1262/// If this block is fenced with backticks, strip them off (and the language name).
1263/// We use this when copying code blocks to the clipboard.
1264fn without_fences(mut markdown: &str) -> &str {
1265    if let Some(opening_backticks) = markdown.find("```") {
1266        markdown = &markdown[opening_backticks..];
1267
1268        // Trim off the next newline. This also trims off a language name if it's there.
1269        if let Some(newline) = markdown.find('\n') {
1270            markdown = &markdown[newline + 1..];
1271        }
1272    };
1273
1274    if let Some(closing_backticks) = markdown.rfind("```") {
1275        markdown = &markdown[..closing_backticks];
1276    };
1277
1278    markdown
1279}
1280
1281#[cfg(test)]
1282mod tests {
1283    use super::*;
1284
1285    #[test]
1286    fn test_without_fences() {
1287        let input = "```rust\nlet x = 5;\n```";
1288        assert_eq!(without_fences(input), "let x = 5;\n");
1289
1290        let input = "   ```\nno language\n```   ";
1291        assert_eq!(without_fences(input), "no language\n");
1292
1293        let input = "plain text";
1294        assert_eq!(without_fences(input), "plain text");
1295
1296        let input = "```python\nprint('hello')\nprint('world')\n```";
1297        assert_eq!(without_fences(input), "print('hello')\nprint('world')\n");
1298    }
1299}