markdown.rs

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