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