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
 532            .text_system()
 533            .typographic_bounds(font_id, font_size, 'm')
 534            .unwrap()
 535            .size
 536            .width;
 537        window.request_autoscroll(Bounds::from_corners(
 538            point(position.x - 3. * em_width, position.y - 3. * line_height),
 539            point(position.x + 3. * em_width, position.y + 3. * line_height),
 540        ));
 541        Some(())
 542    }
 543
 544    fn on_mouse_event<T: MouseEvent>(
 545        &self,
 546        window: &mut Window,
 547        _cx: &mut App,
 548        mut f: impl 'static
 549            + FnMut(&mut Markdown, &T, DispatchPhase, &mut Window, &mut Context<Markdown>),
 550    ) {
 551        window.on_mouse_event({
 552            let markdown = self.markdown.downgrade();
 553            move |event, phase, window, cx| {
 554                markdown
 555                    .update(cx, |markdown, cx| f(markdown, event, phase, window, cx))
 556                    .log_err();
 557            }
 558        });
 559    }
 560}
 561
 562impl Element for MarkdownElement {
 563    type RequestLayoutState = RenderedMarkdown;
 564    type PrepaintState = Hitbox;
 565
 566    fn id(&self) -> Option<ElementId> {
 567        None
 568    }
 569
 570    fn request_layout(
 571        &mut self,
 572        _id: Option<&GlobalElementId>,
 573        window: &mut Window,
 574        cx: &mut App,
 575    ) -> (gpui::LayoutId, Self::RequestLayoutState) {
 576        let mut builder = MarkdownElementBuilder::new(
 577            self.style.base_text_style.clone(),
 578            self.style.syntax.clone(),
 579        );
 580        let parsed_markdown = self.markdown.read(cx).parsed_markdown.clone();
 581        let markdown_end = if let Some(last) = parsed_markdown.events.last() {
 582            last.0.end
 583        } else {
 584            0
 585        };
 586        for (range, event) in parsed_markdown.events.iter() {
 587            match event {
 588                MarkdownEvent::Start(tag) => {
 589                    match tag {
 590                        MarkdownTag::Paragraph => {
 591                            builder.push_div(
 592                                div().mb_2().line_height(rems(1.3)),
 593                                range,
 594                                markdown_end,
 595                            );
 596                        }
 597                        MarkdownTag::Heading { level, .. } => {
 598                            let mut heading = div().mb_2();
 599                            heading = match level {
 600                                pulldown_cmark::HeadingLevel::H1 => heading.text_3xl(),
 601                                pulldown_cmark::HeadingLevel::H2 => heading.text_2xl(),
 602                                pulldown_cmark::HeadingLevel::H3 => heading.text_xl(),
 603                                pulldown_cmark::HeadingLevel::H4 => heading.text_lg(),
 604                                _ => heading,
 605                            };
 606                            heading.style().refine(&self.style.heading);
 607                            builder.push_text_style(
 608                                self.style.heading.text_style().clone().unwrap_or_default(),
 609                            );
 610                            builder.push_div(heading, range, markdown_end);
 611                        }
 612                        MarkdownTag::BlockQuote => {
 613                            builder.push_text_style(self.style.block_quote.clone());
 614                            builder.push_div(
 615                                div()
 616                                    .pl_4()
 617                                    .mb_2()
 618                                    .border_l_4()
 619                                    .border_color(self.style.block_quote_border_color),
 620                                range,
 621                                markdown_end,
 622                            );
 623                        }
 624                        MarkdownTag::CodeBlock(kind) => {
 625                            let language = if let CodeBlockKind::Fenced(language) = kind {
 626                                self.load_language(language.as_ref(), window, cx)
 627                            } else {
 628                                None
 629                            };
 630
 631                            let mut d = div().w_full().rounded_lg();
 632                            d.style().refine(&self.style.code_block);
 633                            if let Some(code_block_text_style) = &self.style.code_block.text {
 634                                builder.push_text_style(code_block_text_style.to_owned());
 635                            }
 636                            builder.push_code_block(language);
 637                            builder.push_div(d, range, markdown_end);
 638                        }
 639                        MarkdownTag::HtmlBlock => builder.push_div(div(), range, markdown_end),
 640                        MarkdownTag::List(bullet_index) => {
 641                            builder.push_list(*bullet_index);
 642                            builder.push_div(div().pl_4(), range, markdown_end);
 643                        }
 644                        MarkdownTag::Item => {
 645                            let bullet = if let Some(bullet_index) = builder.next_bullet_index() {
 646                                format!("{}.", bullet_index)
 647                            } else {
 648                                "".to_string()
 649                            };
 650                            builder.push_div(
 651                                div()
 652                                    .mb_1()
 653                                    .h_flex()
 654                                    .items_start()
 655                                    .gap_1()
 656                                    .line_height(rems(1.3))
 657                                    .child(bullet),
 658                                range,
 659                                markdown_end,
 660                            );
 661                            // Without `w_0`, text doesn't wrap to the width of the container.
 662                            builder.push_div(div().flex_1().w_0(), range, markdown_end);
 663                        }
 664                        MarkdownTag::Emphasis => builder.push_text_style(TextStyleRefinement {
 665                            font_style: Some(FontStyle::Italic),
 666                            ..Default::default()
 667                        }),
 668                        MarkdownTag::Strong => builder.push_text_style(TextStyleRefinement {
 669                            font_weight: Some(FontWeight::BOLD),
 670                            ..Default::default()
 671                        }),
 672                        MarkdownTag::Strikethrough => {
 673                            builder.push_text_style(TextStyleRefinement {
 674                                strikethrough: Some(StrikethroughStyle {
 675                                    thickness: px(1.),
 676                                    color: None,
 677                                }),
 678                                ..Default::default()
 679                            })
 680                        }
 681                        MarkdownTag::Link { dest_url, .. } => {
 682                            if builder.code_block_stack.is_empty() {
 683                                builder.push_link(dest_url.clone(), range.clone());
 684                                builder.push_text_style(self.style.link.clone())
 685                            }
 686                        }
 687                        MarkdownTag::MetadataBlock(_) => {}
 688                        _ => log::error!("unsupported markdown tag {:?}", tag),
 689                    }
 690                }
 691                MarkdownEvent::End(tag) => match tag {
 692                    MarkdownTagEnd::Paragraph => {
 693                        builder.pop_div();
 694                    }
 695                    MarkdownTagEnd::Heading(_) => {
 696                        builder.pop_div();
 697                        builder.pop_text_style()
 698                    }
 699                    MarkdownTagEnd::BlockQuote(_kind) => {
 700                        builder.pop_text_style();
 701                        builder.pop_div()
 702                    }
 703                    MarkdownTagEnd::CodeBlock => {
 704                        builder.trim_trailing_newline();
 705
 706                        if self.markdown.read(cx).options.copy_code_block_buttons {
 707                            builder.flush_text();
 708                            builder.modify_current_div(|el| {
 709                                let id =
 710                                    ElementId::NamedInteger("copy-markdown-code".into(), range.end);
 711                                let copy_button = div().absolute().top_1().right_1().w_5().child(
 712                                    IconButton::new(id, IconName::Copy)
 713                                        .icon_color(Color::Muted)
 714                                        .shape(ui::IconButtonShape::Square)
 715                                        .tooltip(Tooltip::text("Copy Code Block"))
 716                                        .on_click({
 717                                            let code = without_fences(
 718                                                parsed_markdown.source()[range.clone()].trim(),
 719                                            )
 720                                            .to_string();
 721
 722                                            move |_, _, cx| {
 723                                                cx.write_to_clipboard(ClipboardItem::new_string(
 724                                                    code.clone(),
 725                                                ))
 726                                            }
 727                                        }),
 728                                );
 729
 730                                el.child(copy_button)
 731                            });
 732                        }
 733
 734                        builder.pop_div();
 735                        builder.pop_code_block();
 736                        if self.style.code_block.text.is_some() {
 737                            builder.pop_text_style();
 738                        }
 739                    }
 740                    MarkdownTagEnd::HtmlBlock => builder.pop_div(),
 741                    MarkdownTagEnd::List(_) => {
 742                        builder.pop_list();
 743                        builder.pop_div();
 744                    }
 745                    MarkdownTagEnd::Item => {
 746                        builder.pop_div();
 747                        builder.pop_div();
 748                    }
 749                    MarkdownTagEnd::Emphasis => builder.pop_text_style(),
 750                    MarkdownTagEnd::Strong => builder.pop_text_style(),
 751                    MarkdownTagEnd::Strikethrough => builder.pop_text_style(),
 752                    MarkdownTagEnd::Link => {
 753                        if builder.code_block_stack.is_empty() {
 754                            builder.pop_text_style()
 755                        }
 756                    }
 757                    _ => log::error!("unsupported markdown tag end: {:?}", tag),
 758                },
 759                MarkdownEvent::Text => {
 760                    builder.push_text(&parsed_markdown.source[range.clone()], range.start);
 761                }
 762                MarkdownEvent::Code => {
 763                    builder.push_text_style(self.style.inline_code.clone());
 764                    builder.push_text(&parsed_markdown.source[range.clone()], range.start);
 765                    builder.pop_text_style();
 766                }
 767                MarkdownEvent::Html => {
 768                    builder.push_text(&parsed_markdown.source[range.clone()], range.start);
 769                }
 770                MarkdownEvent::InlineHtml => {
 771                    builder.push_text(&parsed_markdown.source[range.clone()], range.start);
 772                }
 773                MarkdownEvent::Rule => {
 774                    builder.push_div(
 775                        div()
 776                            .border_b_1()
 777                            .my_2()
 778                            .border_color(self.style.rule_color),
 779                        range,
 780                        markdown_end,
 781                    );
 782                    builder.pop_div()
 783                }
 784                MarkdownEvent::SoftBreak => builder.push_text(" ", range.start),
 785                MarkdownEvent::HardBreak => {
 786                    let mut d = div().py_3();
 787                    d.style().refine(&self.style.break_style);
 788                    builder.push_div(d, range, markdown_end);
 789                    builder.pop_div()
 790                }
 791                _ => log::error!("unsupported markdown event {:?}", event),
 792            }
 793        }
 794        let mut rendered_markdown = builder.build();
 795        let child_layout_id = rendered_markdown.element.request_layout(window, cx);
 796        let layout_id = window.request_layout(gpui::Style::default(), [child_layout_id], cx);
 797        (layout_id, rendered_markdown)
 798    }
 799
 800    fn prepaint(
 801        &mut self,
 802        _id: Option<&GlobalElementId>,
 803        bounds: Bounds<Pixels>,
 804        rendered_markdown: &mut Self::RequestLayoutState,
 805        window: &mut Window,
 806        cx: &mut App,
 807    ) -> Self::PrepaintState {
 808        let focus_handle = self.markdown.read(cx).focus_handle.clone();
 809        window.set_focus_handle(&focus_handle, cx);
 810
 811        let hitbox = window.insert_hitbox(bounds, false);
 812        rendered_markdown.element.prepaint(window, cx);
 813        self.autoscroll(&rendered_markdown.text, window, cx);
 814        hitbox
 815    }
 816
 817    fn paint(
 818        &mut self,
 819        _id: Option<&GlobalElementId>,
 820        bounds: Bounds<Pixels>,
 821        rendered_markdown: &mut Self::RequestLayoutState,
 822        hitbox: &mut Self::PrepaintState,
 823        window: &mut Window,
 824        cx: &mut App,
 825    ) {
 826        let mut context = KeyContext::default();
 827        context.add("Markdown");
 828        window.set_key_context(context);
 829        let model = self.markdown.clone();
 830        window.on_action(std::any::TypeId::of::<crate::Copy>(), {
 831            let text = rendered_markdown.text.clone();
 832            move |_, phase, window, cx| {
 833                let text = text.clone();
 834                if phase == DispatchPhase::Bubble {
 835                    model.update(cx, move |this, cx| this.copy(&text, window, cx))
 836                }
 837            }
 838        });
 839
 840        self.paint_mouse_listeners(hitbox, &rendered_markdown.text, window, cx);
 841        rendered_markdown.element.paint(window, cx);
 842        self.paint_selection(bounds, &rendered_markdown.text, window, cx);
 843    }
 844}
 845
 846impl IntoElement for MarkdownElement {
 847    type Element = Self;
 848
 849    fn into_element(self) -> Self::Element {
 850        self
 851    }
 852}
 853
 854enum AnyDiv {
 855    Div(Div),
 856    Stateful(Stateful<Div>),
 857}
 858
 859impl AnyDiv {
 860    fn into_any_element(self) -> AnyElement {
 861        match self {
 862            Self::Div(div) => div.into_any_element(),
 863            Self::Stateful(div) => div.into_any_element(),
 864        }
 865    }
 866}
 867
 868impl From<Div> for AnyDiv {
 869    fn from(value: Div) -> Self {
 870        Self::Div(value)
 871    }
 872}
 873
 874impl From<Stateful<Div>> for AnyDiv {
 875    fn from(value: Stateful<Div>) -> Self {
 876        Self::Stateful(value)
 877    }
 878}
 879
 880impl Styled for AnyDiv {
 881    fn style(&mut self) -> &mut StyleRefinement {
 882        match self {
 883            Self::Div(div) => div.style(),
 884            Self::Stateful(div) => div.style(),
 885        }
 886    }
 887}
 888
 889impl ParentElement for AnyDiv {
 890    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
 891        match self {
 892            Self::Div(div) => div.extend(elements),
 893            Self::Stateful(div) => div.extend(elements),
 894        }
 895    }
 896}
 897
 898struct MarkdownElementBuilder {
 899    div_stack: Vec<AnyDiv>,
 900    rendered_lines: Vec<RenderedLine>,
 901    pending_line: PendingLine,
 902    rendered_links: Vec<RenderedLink>,
 903    current_source_index: usize,
 904    base_text_style: TextStyle,
 905    text_style_stack: Vec<TextStyleRefinement>,
 906    code_block_stack: Vec<Option<Arc<Language>>>,
 907    list_stack: Vec<ListStackEntry>,
 908    syntax_theme: Arc<SyntaxTheme>,
 909}
 910
 911#[derive(Default)]
 912struct PendingLine {
 913    text: String,
 914    runs: Vec<TextRun>,
 915    source_mappings: Vec<SourceMapping>,
 916}
 917
 918struct ListStackEntry {
 919    bullet_index: Option<u64>,
 920}
 921
 922impl MarkdownElementBuilder {
 923    fn new(base_text_style: TextStyle, syntax_theme: Arc<SyntaxTheme>) -> Self {
 924        Self {
 925            div_stack: vec![div().debug_selector(|| "inner".into()).into()],
 926            rendered_lines: Vec::new(),
 927            pending_line: PendingLine::default(),
 928            rendered_links: Vec::new(),
 929            current_source_index: 0,
 930            base_text_style,
 931            text_style_stack: Vec::new(),
 932            code_block_stack: Vec::new(),
 933            list_stack: Vec::new(),
 934            syntax_theme,
 935        }
 936    }
 937
 938    fn push_text_style(&mut self, style: TextStyleRefinement) {
 939        self.text_style_stack.push(style);
 940    }
 941
 942    fn text_style(&self) -> TextStyle {
 943        let mut style = self.base_text_style.clone();
 944        for refinement in &self.text_style_stack {
 945            style.refine(refinement);
 946        }
 947        style
 948    }
 949
 950    fn pop_text_style(&mut self) {
 951        self.text_style_stack.pop();
 952    }
 953
 954    fn push_div(&mut self, div: impl Into<AnyDiv>, range: &Range<usize>, markdown_end: usize) {
 955        let mut div = div.into();
 956        self.flush_text();
 957
 958        if range.start == 0 {
 959            // Remove the top margin on the first element.
 960            div.style().refine(&StyleRefinement {
 961                margin: gpui::EdgesRefinement {
 962                    top: Some(Length::Definite(px(0.).into())),
 963                    left: None,
 964                    right: None,
 965                    bottom: None,
 966                },
 967                ..Default::default()
 968            });
 969        }
 970
 971        if range.end == markdown_end {
 972            div.style().refine(&StyleRefinement {
 973                margin: gpui::EdgesRefinement {
 974                    top: None,
 975                    left: None,
 976                    right: None,
 977                    bottom: Some(Length::Definite(rems(0.).into())),
 978                },
 979                ..Default::default()
 980            });
 981        }
 982
 983        self.div_stack.push(div);
 984    }
 985
 986    fn modify_current_div(&mut self, f: impl FnOnce(AnyDiv) -> AnyDiv) {
 987        self.flush_text();
 988        if let Some(div) = self.div_stack.pop() {
 989            self.div_stack.push(f(div));
 990        }
 991    }
 992
 993    fn pop_div(&mut self) {
 994        self.flush_text();
 995        let div = self.div_stack.pop().unwrap().into_any_element();
 996        self.div_stack.last_mut().unwrap().extend(iter::once(div));
 997    }
 998
 999    fn push_list(&mut self, bullet_index: Option<u64>) {
1000        self.list_stack.push(ListStackEntry { bullet_index });
1001    }
1002
1003    fn next_bullet_index(&mut self) -> Option<u64> {
1004        self.list_stack.last_mut().and_then(|entry| {
1005            let item_index = entry.bullet_index.as_mut()?;
1006            *item_index += 1;
1007            Some(*item_index - 1)
1008        })
1009    }
1010
1011    fn pop_list(&mut self) {
1012        self.list_stack.pop();
1013    }
1014
1015    fn push_code_block(&mut self, language: Option<Arc<Language>>) {
1016        self.code_block_stack.push(language);
1017    }
1018
1019    fn pop_code_block(&mut self) {
1020        self.code_block_stack.pop();
1021    }
1022
1023    fn push_link(&mut self, destination_url: SharedString, source_range: Range<usize>) {
1024        self.rendered_links.push(RenderedLink {
1025            source_range,
1026            destination_url,
1027        });
1028    }
1029
1030    fn push_text(&mut self, text: &str, source_index: usize) {
1031        self.pending_line.source_mappings.push(SourceMapping {
1032            rendered_index: self.pending_line.text.len(),
1033            source_index,
1034        });
1035        self.pending_line.text.push_str(text);
1036        self.current_source_index = source_index + text.len();
1037
1038        if let Some(Some(language)) = self.code_block_stack.last() {
1039            let mut offset = 0;
1040            for (range, highlight_id) in language.highlight_text(&Rope::from(text), 0..text.len()) {
1041                if range.start > offset {
1042                    self.pending_line
1043                        .runs
1044                        .push(self.text_style().to_run(range.start - offset));
1045                }
1046
1047                let mut run_style = self.text_style();
1048                if let Some(highlight) = highlight_id.style(&self.syntax_theme) {
1049                    run_style = run_style.highlight(highlight);
1050                }
1051                self.pending_line.runs.push(run_style.to_run(range.len()));
1052                offset = range.end;
1053            }
1054
1055            if offset < text.len() {
1056                self.pending_line
1057                    .runs
1058                    .push(self.text_style().to_run(text.len() - offset));
1059            }
1060        } else {
1061            self.pending_line
1062                .runs
1063                .push(self.text_style().to_run(text.len()));
1064        }
1065    }
1066
1067    fn trim_trailing_newline(&mut self) {
1068        if self.pending_line.text.ends_with('\n') {
1069            self.pending_line
1070                .text
1071                .truncate(self.pending_line.text.len() - 1);
1072            self.pending_line.runs.last_mut().unwrap().len -= 1;
1073            self.current_source_index -= 1;
1074        }
1075    }
1076
1077    fn flush_text(&mut self) {
1078        let line = mem::take(&mut self.pending_line);
1079        if line.text.is_empty() {
1080            return;
1081        }
1082
1083        let text = StyledText::new(line.text).with_runs(line.runs);
1084        self.rendered_lines.push(RenderedLine {
1085            layout: text.layout().clone(),
1086            source_mappings: line.source_mappings,
1087            source_end: self.current_source_index,
1088        });
1089        self.div_stack.last_mut().unwrap().extend([text.into_any()]);
1090    }
1091
1092    fn build(mut self) -> RenderedMarkdown {
1093        debug_assert_eq!(self.div_stack.len(), 1);
1094        self.flush_text();
1095        RenderedMarkdown {
1096            element: self.div_stack.pop().unwrap().into_any_element(),
1097            text: RenderedText {
1098                lines: self.rendered_lines.into(),
1099                links: self.rendered_links.into(),
1100            },
1101        }
1102    }
1103}
1104
1105struct RenderedLine {
1106    layout: TextLayout,
1107    source_mappings: Vec<SourceMapping>,
1108    source_end: usize,
1109}
1110
1111impl RenderedLine {
1112    fn rendered_index_for_source_index(&self, source_index: usize) -> usize {
1113        let mapping = match self
1114            .source_mappings
1115            .binary_search_by_key(&source_index, |probe| probe.source_index)
1116        {
1117            Ok(ix) => &self.source_mappings[ix],
1118            Err(ix) => &self.source_mappings[ix - 1],
1119        };
1120        mapping.rendered_index + (source_index - mapping.source_index)
1121    }
1122
1123    fn source_index_for_rendered_index(&self, rendered_index: usize) -> usize {
1124        let mapping = match self
1125            .source_mappings
1126            .binary_search_by_key(&rendered_index, |probe| probe.rendered_index)
1127        {
1128            Ok(ix) => &self.source_mappings[ix],
1129            Err(ix) => &self.source_mappings[ix - 1],
1130        };
1131        mapping.source_index + (rendered_index - mapping.rendered_index)
1132    }
1133
1134    fn source_index_for_position(&self, position: Point<Pixels>) -> Result<usize, usize> {
1135        let line_rendered_index;
1136        let out_of_bounds;
1137        match self.layout.index_for_position(position) {
1138            Ok(ix) => {
1139                line_rendered_index = ix;
1140                out_of_bounds = false;
1141            }
1142            Err(ix) => {
1143                line_rendered_index = ix;
1144                out_of_bounds = true;
1145            }
1146        };
1147        let source_index = self.source_index_for_rendered_index(line_rendered_index);
1148        if out_of_bounds {
1149            Err(source_index)
1150        } else {
1151            Ok(source_index)
1152        }
1153    }
1154}
1155
1156#[derive(Copy, Clone, Debug, Default)]
1157struct SourceMapping {
1158    rendered_index: usize,
1159    source_index: usize,
1160}
1161
1162pub struct RenderedMarkdown {
1163    element: AnyElement,
1164    text: RenderedText,
1165}
1166
1167#[derive(Clone)]
1168struct RenderedText {
1169    lines: Rc<[RenderedLine]>,
1170    links: Rc<[RenderedLink]>,
1171}
1172
1173#[derive(Clone, Eq, PartialEq)]
1174struct RenderedLink {
1175    source_range: Range<usize>,
1176    destination_url: SharedString,
1177}
1178
1179impl RenderedText {
1180    fn source_index_for_position(&self, position: Point<Pixels>) -> Result<usize, usize> {
1181        let mut lines = self.lines.iter().peekable();
1182
1183        while let Some(line) = lines.next() {
1184            let line_bounds = line.layout.bounds();
1185            if position.y > line_bounds.bottom() {
1186                if let Some(next_line) = lines.peek() {
1187                    if position.y < next_line.layout.bounds().top() {
1188                        return Err(line.source_end);
1189                    }
1190                }
1191
1192                continue;
1193            }
1194
1195            return line.source_index_for_position(position);
1196        }
1197
1198        Err(self.lines.last().map_or(0, |line| line.source_end))
1199    }
1200
1201    fn position_for_source_index(&self, source_index: usize) -> Option<(Point<Pixels>, Pixels)> {
1202        for line in self.lines.iter() {
1203            let line_source_start = line.source_mappings.first().unwrap().source_index;
1204            if source_index < line_source_start {
1205                break;
1206            } else if source_index > line.source_end {
1207                continue;
1208            } else {
1209                let line_height = line.layout.line_height();
1210                let rendered_index_within_line = line.rendered_index_for_source_index(source_index);
1211                let position = line.layout.position_for_index(rendered_index_within_line)?;
1212                return Some((position, line_height));
1213            }
1214        }
1215        None
1216    }
1217
1218    fn surrounding_word_range(&self, source_index: usize) -> Range<usize> {
1219        for line in self.lines.iter() {
1220            if source_index > line.source_end {
1221                continue;
1222            }
1223
1224            let line_rendered_start = line.source_mappings.first().unwrap().rendered_index;
1225            let rendered_index_in_line =
1226                line.rendered_index_for_source_index(source_index) - line_rendered_start;
1227            let text = line.layout.text();
1228            let previous_space = if let Some(idx) = text[0..rendered_index_in_line].rfind(' ') {
1229                idx + ' '.len_utf8()
1230            } else {
1231                0
1232            };
1233            let next_space = if let Some(idx) = text[rendered_index_in_line..].find(' ') {
1234                rendered_index_in_line + idx
1235            } else {
1236                text.len()
1237            };
1238
1239            return line.source_index_for_rendered_index(line_rendered_start + previous_space)
1240                ..line.source_index_for_rendered_index(line_rendered_start + next_space);
1241        }
1242
1243        source_index..source_index
1244    }
1245
1246    fn surrounding_line_range(&self, source_index: usize) -> Range<usize> {
1247        for line in self.lines.iter() {
1248            if source_index > line.source_end {
1249                continue;
1250            }
1251            let line_source_start = line.source_mappings.first().unwrap().source_index;
1252            return line_source_start..line.source_end;
1253        }
1254
1255        source_index..source_index
1256    }
1257
1258    fn text_for_range(&self, range: Range<usize>) -> String {
1259        let mut ret = vec![];
1260
1261        for line in self.lines.iter() {
1262            if range.start > line.source_end {
1263                continue;
1264            }
1265            let line_source_start = line.source_mappings.first().unwrap().source_index;
1266            if range.end < line_source_start {
1267                break;
1268            }
1269
1270            let text = line.layout.text();
1271
1272            let start = if range.start < line_source_start {
1273                0
1274            } else {
1275                line.rendered_index_for_source_index(range.start)
1276            };
1277            let end = if range.end > line.source_end {
1278                line.rendered_index_for_source_index(line.source_end)
1279            } else {
1280                line.rendered_index_for_source_index(range.end)
1281            }
1282            .min(text.len());
1283
1284            ret.push(text[start..end].to_string());
1285        }
1286        ret.join("\n")
1287    }
1288
1289    fn link_for_position(&self, position: Point<Pixels>) -> Option<&RenderedLink> {
1290        let source_index = self.source_index_for_position(position).ok()?;
1291        self.links
1292            .iter()
1293            .find(|link| link.source_range.contains(&source_index))
1294    }
1295}
1296
1297/// Some markdown blocks are indented, and others have e.g. ```rust … ``` around them.
1298/// If this block is fenced with backticks, strip them off (and the language name).
1299/// We use this when copying code blocks to the clipboard.
1300fn without_fences(mut markdown: &str) -> &str {
1301    if let Some(opening_backticks) = markdown.find("```") {
1302        markdown = &markdown[opening_backticks..];
1303
1304        // Trim off the next newline. This also trims off a language name if it's there.
1305        if let Some(newline) = markdown.find('\n') {
1306            markdown = &markdown[newline + 1..];
1307        }
1308    };
1309
1310    if let Some(closing_backticks) = markdown.rfind("```") {
1311        markdown = &markdown[..closing_backticks];
1312    };
1313
1314    markdown
1315}
1316
1317#[cfg(test)]
1318mod tests {
1319    use super::*;
1320
1321    #[test]
1322    fn test_without_fences() {
1323        let input = "```rust\nlet x = 5;\n```";
1324        assert_eq!(without_fences(input), "let x = 5;\n");
1325
1326        let input = "   ```\nno language\n```   ";
1327        assert_eq!(without_fences(input), "no language\n");
1328
1329        let input = "plain text";
1330        assert_eq!(without_fences(input), "plain text");
1331
1332        let input = "```python\nprint('hello')\nprint('world')\n```";
1333        assert_eq!(without_fences(input), "print('hello')\nprint('world')\n");
1334    }
1335}