markdown.rs

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