markdown.rs

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