markdown.rs

   1pub mod parser;
   2mod path_range;
   3
   4use base64::Engine as _;
   5use futures::FutureExt as _;
   6use gpui::HitboxBehavior;
   7use language::LanguageName;
   8use log::Level;
   9pub use path_range::{LineCol, PathWithRange};
  10use ui::Checkbox;
  11
  12use std::borrow::Cow;
  13use std::iter;
  14use std::mem;
  15use std::ops::Range;
  16use std::path::Path;
  17use std::rc::Rc;
  18use std::sync::Arc;
  19use std::time::Duration;
  20
  21use collections::{HashMap, HashSet};
  22use gpui::{
  23    AnyElement, App, BorderStyle, Bounds, ClipboardItem, CursorStyle, DispatchPhase, Edges, Entity,
  24    FocusHandle, Focusable, FontStyle, FontWeight, GlobalElementId, Hitbox, Hsla, Image,
  25    ImageFormat, KeyContext, Length, MouseButton, MouseDownEvent, MouseEvent, MouseMoveEvent,
  26    MouseUpEvent, Point, ScrollHandle, Stateful, StrikethroughStyle, StyleRefinement, StyledText,
  27    Task, TextLayout, TextRun, TextStyle, TextStyleRefinement, actions, img, point, quad,
  28};
  29use language::{Language, LanguageRegistry, Rope};
  30use parser::CodeBlockMetadata;
  31use parser::{MarkdownEvent, MarkdownTag, MarkdownTagEnd, parse_links_only, parse_markdown};
  32use pulldown_cmark::Alignment;
  33use sum_tree::TreeMap;
  34use theme::SyntaxTheme;
  35use ui::{ScrollAxes, Scrollbars, Tooltip, WithScrollbar, prelude::*};
  36use util::ResultExt;
  37
  38use crate::parser::CodeBlockKind;
  39
  40/// A callback function that can be used to customize the style of links based on the destination URL.
  41/// If the callback returns `None`, the default link style will be used.
  42type LinkStyleCallback = Rc<dyn Fn(&str, &App) -> Option<TextStyleRefinement>>;
  43
  44/// Defines custom style refinements for each heading level (H1-H6)
  45#[derive(Clone, Default)]
  46pub struct HeadingLevelStyles {
  47    pub h1: Option<TextStyleRefinement>,
  48    pub h2: Option<TextStyleRefinement>,
  49    pub h3: Option<TextStyleRefinement>,
  50    pub h4: Option<TextStyleRefinement>,
  51    pub h5: Option<TextStyleRefinement>,
  52    pub h6: Option<TextStyleRefinement>,
  53}
  54
  55#[derive(Clone)]
  56pub struct MarkdownStyle {
  57    pub base_text_style: TextStyle,
  58    pub container_style: StyleRefinement,
  59    pub code_block: StyleRefinement,
  60    pub code_block_overflow_x_scroll: bool,
  61    pub inline_code: TextStyleRefinement,
  62    pub block_quote: TextStyleRefinement,
  63    pub link: TextStyleRefinement,
  64    pub link_callback: Option<LinkStyleCallback>,
  65    pub rule_color: Hsla,
  66    pub block_quote_border_color: Hsla,
  67    pub syntax: Arc<SyntaxTheme>,
  68    pub selection_background_color: Hsla,
  69    pub heading: StyleRefinement,
  70    pub heading_level_styles: Option<HeadingLevelStyles>,
  71    pub height_is_multiple_of_line_height: bool,
  72    pub prevent_mouse_interaction: bool,
  73    pub table_columns_min_size: bool,
  74}
  75
  76impl Default for MarkdownStyle {
  77    fn default() -> Self {
  78        Self {
  79            base_text_style: Default::default(),
  80            container_style: Default::default(),
  81            code_block: Default::default(),
  82            code_block_overflow_x_scroll: false,
  83            inline_code: Default::default(),
  84            block_quote: Default::default(),
  85            link: Default::default(),
  86            link_callback: None,
  87            rule_color: Default::default(),
  88            block_quote_border_color: Default::default(),
  89            syntax: Arc::new(SyntaxTheme::default()),
  90            selection_background_color: Default::default(),
  91            heading: Default::default(),
  92            heading_level_styles: None,
  93            height_is_multiple_of_line_height: false,
  94            prevent_mouse_interaction: false,
  95            table_columns_min_size: false,
  96        }
  97    }
  98}
  99
 100pub struct Markdown {
 101    source: SharedString,
 102    selection: Selection,
 103    pressed_link: Option<RenderedLink>,
 104    autoscroll_request: Option<usize>,
 105    parsed_markdown: ParsedMarkdown,
 106    images_by_source_offset: HashMap<usize, Arc<Image>>,
 107    should_reparse: bool,
 108    pending_parse: Option<Task<()>>,
 109    focus_handle: FocusHandle,
 110    language_registry: Option<Arc<LanguageRegistry>>,
 111    fallback_code_block_language: Option<LanguageName>,
 112    options: Options,
 113    copied_code_blocks: HashSet<ElementId>,
 114    code_block_scroll_handles: HashMap<usize, ScrollHandle>,
 115    context_menu_selected_text: Option<String>,
 116}
 117
 118struct Options {
 119    parse_links_only: bool,
 120}
 121
 122pub enum CodeBlockRenderer {
 123    Default {
 124        copy_button: bool,
 125        copy_button_on_hover: bool,
 126        border: bool,
 127    },
 128    Custom {
 129        render: CodeBlockRenderFn,
 130        /// A function that can modify the parent container after the code block
 131        /// content has been appended as a child element.
 132        transform: Option<CodeBlockTransformFn>,
 133    },
 134}
 135
 136pub type CodeBlockRenderFn = Arc<
 137    dyn Fn(
 138        &CodeBlockKind,
 139        &ParsedMarkdown,
 140        Range<usize>,
 141        CodeBlockMetadata,
 142        &mut Window,
 143        &App,
 144    ) -> Div,
 145>;
 146
 147pub type CodeBlockTransformFn =
 148    Arc<dyn Fn(AnyDiv, Range<usize>, CodeBlockMetadata, &mut Window, &App) -> AnyDiv>;
 149
 150actions!(
 151    markdown,
 152    [
 153        /// Copies the selected text to the clipboard.
 154        Copy,
 155        /// Copies the selected text as markdown to the clipboard.
 156        CopyAsMarkdown
 157    ]
 158);
 159
 160impl Markdown {
 161    pub fn new(
 162        source: SharedString,
 163        language_registry: Option<Arc<LanguageRegistry>>,
 164        fallback_code_block_language: Option<LanguageName>,
 165        cx: &mut Context<Self>,
 166    ) -> Self {
 167        let focus_handle = cx.focus_handle();
 168        let mut this = Self {
 169            source,
 170            selection: Selection::default(),
 171            pressed_link: None,
 172            autoscroll_request: None,
 173            should_reparse: false,
 174            images_by_source_offset: Default::default(),
 175            parsed_markdown: ParsedMarkdown::default(),
 176            pending_parse: None,
 177            focus_handle,
 178            language_registry,
 179            fallback_code_block_language,
 180            options: Options {
 181                parse_links_only: false,
 182            },
 183            copied_code_blocks: HashSet::default(),
 184            code_block_scroll_handles: HashMap::default(),
 185            context_menu_selected_text: None,
 186        };
 187        this.parse(cx);
 188        this
 189    }
 190
 191    pub fn new_text(source: SharedString, cx: &mut Context<Self>) -> Self {
 192        let focus_handle = cx.focus_handle();
 193        let mut this = Self {
 194            source,
 195            selection: Selection::default(),
 196            pressed_link: None,
 197            autoscroll_request: None,
 198            should_reparse: false,
 199            parsed_markdown: ParsedMarkdown::default(),
 200            images_by_source_offset: Default::default(),
 201            pending_parse: None,
 202            focus_handle,
 203            language_registry: None,
 204            fallback_code_block_language: None,
 205            options: Options {
 206                parse_links_only: true,
 207            },
 208            copied_code_blocks: HashSet::default(),
 209            code_block_scroll_handles: HashMap::default(),
 210            context_menu_selected_text: None,
 211        };
 212        this.parse(cx);
 213        this
 214    }
 215
 216    fn code_block_scroll_handle(&mut self, id: usize) -> ScrollHandle {
 217        self.code_block_scroll_handles
 218            .entry(id)
 219            .or_insert_with(ScrollHandle::new)
 220            .clone()
 221    }
 222
 223    fn retain_code_block_scroll_handles(&mut self, ids: &HashSet<usize>) {
 224        self.code_block_scroll_handles
 225            .retain(|id, _| ids.contains(id));
 226    }
 227
 228    fn clear_code_block_scroll_handles(&mut self) {
 229        self.code_block_scroll_handles.clear();
 230    }
 231
 232    pub fn is_parsing(&self) -> bool {
 233        self.pending_parse.is_some()
 234    }
 235
 236    pub fn source(&self) -> &str {
 237        &self.source
 238    }
 239
 240    pub fn append(&mut self, text: &str, cx: &mut Context<Self>) {
 241        self.source = SharedString::new(self.source.to_string() + text);
 242        self.parse(cx);
 243    }
 244
 245    pub fn replace(&mut self, source: impl Into<SharedString>, cx: &mut Context<Self>) {
 246        self.source = source.into();
 247        self.parse(cx);
 248    }
 249
 250    pub fn reset(&mut self, source: SharedString, cx: &mut Context<Self>) {
 251        if source == self.source() {
 252            return;
 253        }
 254        self.source = source;
 255        self.selection = Selection::default();
 256        self.autoscroll_request = None;
 257        self.pending_parse = None;
 258        self.should_reparse = false;
 259        // Don't clear parsed_markdown here - keep existing content visible until new parse completes
 260        self.parse(cx);
 261    }
 262
 263    #[cfg(any(test, feature = "test-support"))]
 264    pub fn parsed_markdown(&self) -> &ParsedMarkdown {
 265        &self.parsed_markdown
 266    }
 267
 268    pub fn escape(s: &str) -> Cow<'_, str> {
 269        // Valid to use bytes since multi-byte UTF-8 doesn't use ASCII chars.
 270        let count = s
 271            .bytes()
 272            .filter(|c| *c == b'\n' || c.is_ascii_punctuation())
 273            .count();
 274        if count > 0 {
 275            let mut output = String::with_capacity(s.len() + count);
 276            let mut is_newline = false;
 277            for c in s.chars() {
 278                if is_newline && c == ' ' {
 279                    continue;
 280                }
 281                is_newline = c == '\n';
 282                if c == '\n' {
 283                    output.push('\n')
 284                } else if c.is_ascii_punctuation() {
 285                    output.push('\\')
 286                }
 287                output.push(c)
 288            }
 289            output.into()
 290        } else {
 291            s.into()
 292        }
 293    }
 294
 295    pub fn selected_text(&self) -> Option<String> {
 296        if self.selection.end <= self.selection.start {
 297            None
 298        } else {
 299            Some(self.source[self.selection.start..self.selection.end].to_string())
 300        }
 301    }
 302
 303    fn copy(&self, text: &RenderedText, _: &mut Window, cx: &mut Context<Self>) {
 304        if self.selection.end <= self.selection.start {
 305            return;
 306        }
 307        let text = text.text_for_range(self.selection.start..self.selection.end);
 308        cx.write_to_clipboard(ClipboardItem::new_string(text));
 309    }
 310
 311    fn copy_as_markdown(&mut self, _: &mut Window, cx: &mut Context<Self>) {
 312        if let Some(text) = self.context_menu_selected_text.take() {
 313            cx.write_to_clipboard(ClipboardItem::new_string(text));
 314            return;
 315        }
 316        if self.selection.end <= self.selection.start {
 317            return;
 318        }
 319        let text = self.source[self.selection.start..self.selection.end].to_string();
 320        cx.write_to_clipboard(ClipboardItem::new_string(text));
 321    }
 322
 323    fn capture_selection_for_context_menu(&mut self) {
 324        self.context_menu_selected_text = self.selected_text();
 325    }
 326
 327    fn parse(&mut self, cx: &mut Context<Self>) {
 328        if self.source.is_empty() {
 329            return;
 330        }
 331
 332        if self.pending_parse.is_some() {
 333            self.should_reparse = true;
 334            return;
 335        }
 336        self.should_reparse = false;
 337        self.pending_parse = Some(self.start_background_parse(cx));
 338    }
 339
 340    fn start_background_parse(&self, cx: &Context<Self>) -> Task<()> {
 341        let source = self.source.clone();
 342        let should_parse_links_only = self.options.parse_links_only;
 343        let language_registry = self.language_registry.clone();
 344        let fallback = self.fallback_code_block_language.clone();
 345
 346        let parsed = cx.background_spawn(async move {
 347            if should_parse_links_only {
 348                return (
 349                    ParsedMarkdown {
 350                        events: Arc::from(parse_links_only(source.as_ref())),
 351                        source,
 352                        languages_by_name: TreeMap::default(),
 353                        languages_by_path: TreeMap::default(),
 354                    },
 355                    Default::default(),
 356                );
 357            }
 358
 359            let (events, language_names, paths) = parse_markdown(&source);
 360            let mut images_by_source_offset = HashMap::default();
 361            let mut languages_by_name = TreeMap::default();
 362            let mut languages_by_path = TreeMap::default();
 363            if let Some(registry) = language_registry.as_ref() {
 364                for name in language_names {
 365                    let language = if !name.is_empty() {
 366                        registry.language_for_name_or_extension(&name).left_future()
 367                    } else if let Some(fallback) = &fallback {
 368                        registry.language_for_name(fallback.as_ref()).right_future()
 369                    } else {
 370                        continue;
 371                    };
 372                    if let Ok(language) = language.await {
 373                        languages_by_name.insert(name, language);
 374                    }
 375                }
 376
 377                for path in paths {
 378                    if let Ok(language) = registry
 379                        .load_language_for_file_path(Path::new(path.as_ref()))
 380                        .await
 381                    {
 382                        languages_by_path.insert(path, language);
 383                    }
 384                }
 385            }
 386
 387            for (range, event) in &events {
 388                if let MarkdownEvent::Start(MarkdownTag::Image { dest_url, .. }) = event
 389                    && let Some(data_url) = dest_url.strip_prefix("data:")
 390                {
 391                    let Some((mime_info, data)) = data_url.split_once(',') else {
 392                        continue;
 393                    };
 394                    let Some((mime_type, encoding)) = mime_info.split_once(';') else {
 395                        continue;
 396                    };
 397                    let Some(format) = ImageFormat::from_mime_type(mime_type) else {
 398                        continue;
 399                    };
 400                    let is_base64 = encoding == "base64";
 401                    if is_base64
 402                        && let Some(bytes) = base64::prelude::BASE64_STANDARD
 403                            .decode(data)
 404                            .log_with_level(Level::Debug)
 405                    {
 406                        let image = Arc::new(Image::from_bytes(format, bytes));
 407                        images_by_source_offset.insert(range.start, image);
 408                    }
 409                }
 410            }
 411
 412            (
 413                ParsedMarkdown {
 414                    source,
 415                    events: Arc::from(events),
 416                    languages_by_name,
 417                    languages_by_path,
 418                },
 419                images_by_source_offset,
 420            )
 421        });
 422
 423        cx.spawn(async move |this, cx| {
 424            let (parsed, images_by_source_offset) = parsed.await;
 425
 426            this.update(cx, |this, cx| {
 427                this.parsed_markdown = parsed;
 428                this.images_by_source_offset = images_by_source_offset;
 429                this.pending_parse.take();
 430                if this.should_reparse {
 431                    this.parse(cx);
 432                }
 433                cx.refresh_windows();
 434            })
 435            .ok();
 436        })
 437    }
 438}
 439
 440impl Focusable for Markdown {
 441    fn focus_handle(&self, _cx: &App) -> FocusHandle {
 442        self.focus_handle.clone()
 443    }
 444}
 445
 446#[derive(Debug, Default, Clone)]
 447enum SelectMode {
 448    #[default]
 449    Character,
 450    Word(Range<usize>),
 451    Line(Range<usize>),
 452    All,
 453}
 454
 455#[derive(Clone, Default)]
 456struct Selection {
 457    start: usize,
 458    end: usize,
 459    reversed: bool,
 460    pending: bool,
 461    mode: SelectMode,
 462}
 463
 464impl Selection {
 465    fn set_head(&mut self, head: usize, rendered_text: &RenderedText) {
 466        match &self.mode {
 467            SelectMode::Character => {
 468                if head < self.tail() {
 469                    if !self.reversed {
 470                        self.end = self.start;
 471                        self.reversed = true;
 472                    }
 473                    self.start = head;
 474                } else {
 475                    if self.reversed {
 476                        self.start = self.end;
 477                        self.reversed = false;
 478                    }
 479                    self.end = head;
 480                }
 481            }
 482            SelectMode::Word(original_range) | SelectMode::Line(original_range) => {
 483                let head_range = if matches!(self.mode, SelectMode::Word(_)) {
 484                    rendered_text.surrounding_word_range(head)
 485                } else {
 486                    rendered_text.surrounding_line_range(head)
 487                };
 488
 489                if head < original_range.start {
 490                    self.start = head_range.start;
 491                    self.end = original_range.end;
 492                    self.reversed = true;
 493                } else if head >= original_range.end {
 494                    self.start = original_range.start;
 495                    self.end = head_range.end;
 496                    self.reversed = false;
 497                } else {
 498                    self.start = original_range.start;
 499                    self.end = original_range.end;
 500                    self.reversed = false;
 501                }
 502            }
 503            SelectMode::All => {
 504                self.start = 0;
 505                self.end = rendered_text
 506                    .lines
 507                    .last()
 508                    .map(|line| line.source_end)
 509                    .unwrap_or(0);
 510                self.reversed = false;
 511            }
 512        }
 513    }
 514
 515    fn tail(&self) -> usize {
 516        if self.reversed { self.end } else { self.start }
 517    }
 518}
 519
 520#[derive(Debug, Clone, Default)]
 521pub struct ParsedMarkdown {
 522    pub source: SharedString,
 523    pub events: Arc<[(Range<usize>, MarkdownEvent)]>,
 524    pub languages_by_name: TreeMap<SharedString, Arc<Language>>,
 525    pub languages_by_path: TreeMap<Arc<str>, Arc<Language>>,
 526}
 527
 528impl ParsedMarkdown {
 529    pub fn source(&self) -> &SharedString {
 530        &self.source
 531    }
 532
 533    pub fn events(&self) -> &Arc<[(Range<usize>, MarkdownEvent)]> {
 534        &self.events
 535    }
 536}
 537
 538pub struct MarkdownElement {
 539    markdown: Entity<Markdown>,
 540    style: MarkdownStyle,
 541    code_block_renderer: CodeBlockRenderer,
 542    on_url_click: Option<Box<dyn Fn(SharedString, &mut Window, &mut App)>>,
 543}
 544
 545impl MarkdownElement {
 546    pub fn new(markdown: Entity<Markdown>, style: MarkdownStyle) -> Self {
 547        Self {
 548            markdown,
 549            style,
 550            code_block_renderer: CodeBlockRenderer::Default {
 551                copy_button: true,
 552                copy_button_on_hover: false,
 553                border: false,
 554            },
 555            on_url_click: None,
 556        }
 557    }
 558
 559    #[cfg(any(test, feature = "test-support"))]
 560    pub fn rendered_text(
 561        markdown: Entity<Markdown>,
 562        cx: &mut gpui::VisualTestContext,
 563        style: impl FnOnce(&Window, &App) -> MarkdownStyle,
 564    ) -> String {
 565        use gpui::size;
 566
 567        let (text, _) = cx.draw(
 568            Default::default(),
 569            size(px(600.0), px(600.0)),
 570            |window, cx| Self::new(markdown, style(window, cx)),
 571        );
 572        text.text
 573            .lines
 574            .iter()
 575            .map(|line| line.layout.wrapped_text())
 576            .collect::<Vec<_>>()
 577            .join("\n")
 578    }
 579
 580    pub fn code_block_renderer(mut self, variant: CodeBlockRenderer) -> Self {
 581        self.code_block_renderer = variant;
 582        self
 583    }
 584
 585    pub fn on_url_click(
 586        mut self,
 587        handler: impl Fn(SharedString, &mut Window, &mut App) + 'static,
 588    ) -> Self {
 589        self.on_url_click = Some(Box::new(handler));
 590        self
 591    }
 592
 593    fn paint_selection(
 594        &self,
 595        bounds: Bounds<Pixels>,
 596        rendered_text: &RenderedText,
 597        window: &mut Window,
 598        cx: &mut App,
 599    ) {
 600        let selection = self.markdown.read(cx).selection.clone();
 601        let selection_start = rendered_text.position_for_source_index(selection.start);
 602        let selection_end = rendered_text.position_for_source_index(selection.end);
 603        if let Some(((start_position, start_line_height), (end_position, end_line_height))) =
 604            selection_start.zip(selection_end)
 605        {
 606            if start_position.y == end_position.y {
 607                window.paint_quad(quad(
 608                    Bounds::from_corners(
 609                        start_position,
 610                        point(end_position.x, end_position.y + end_line_height),
 611                    ),
 612                    Pixels::ZERO,
 613                    self.style.selection_background_color,
 614                    Edges::default(),
 615                    Hsla::transparent_black(),
 616                    BorderStyle::default(),
 617                ));
 618            } else {
 619                window.paint_quad(quad(
 620                    Bounds::from_corners(
 621                        start_position,
 622                        point(bounds.right(), start_position.y + start_line_height),
 623                    ),
 624                    Pixels::ZERO,
 625                    self.style.selection_background_color,
 626                    Edges::default(),
 627                    Hsla::transparent_black(),
 628                    BorderStyle::default(),
 629                ));
 630
 631                if end_position.y > start_position.y + start_line_height {
 632                    window.paint_quad(quad(
 633                        Bounds::from_corners(
 634                            point(bounds.left(), start_position.y + start_line_height),
 635                            point(bounds.right(), end_position.y),
 636                        ),
 637                        Pixels::ZERO,
 638                        self.style.selection_background_color,
 639                        Edges::default(),
 640                        Hsla::transparent_black(),
 641                        BorderStyle::default(),
 642                    ));
 643                }
 644
 645                window.paint_quad(quad(
 646                    Bounds::from_corners(
 647                        point(bounds.left(), end_position.y),
 648                        point(end_position.x, end_position.y + end_line_height),
 649                    ),
 650                    Pixels::ZERO,
 651                    self.style.selection_background_color,
 652                    Edges::default(),
 653                    Hsla::transparent_black(),
 654                    BorderStyle::default(),
 655                ));
 656            }
 657        }
 658    }
 659
 660    fn paint_mouse_listeners(
 661        &mut self,
 662        hitbox: &Hitbox,
 663        rendered_text: &RenderedText,
 664        window: &mut Window,
 665        cx: &mut App,
 666    ) {
 667        if self.style.prevent_mouse_interaction {
 668            return;
 669        }
 670
 671        let is_hovering_link = hitbox.is_hovered(window)
 672            && !self.markdown.read(cx).selection.pending
 673            && rendered_text
 674                .link_for_position(window.mouse_position())
 675                .is_some();
 676
 677        if !self.style.prevent_mouse_interaction {
 678            if is_hovering_link {
 679                window.set_cursor_style(CursorStyle::PointingHand, hitbox);
 680            } else {
 681                window.set_cursor_style(CursorStyle::IBeam, hitbox);
 682            }
 683        }
 684
 685        let on_open_url = self.on_url_click.take();
 686
 687        self.on_mouse_event(window, cx, {
 688            let hitbox = hitbox.clone();
 689            move |markdown, event: &MouseDownEvent, phase, window, _| {
 690                if phase.capture()
 691                    && event.button == MouseButton::Right
 692                    && hitbox.is_hovered(window)
 693                {
 694                    // Capture selected text so it survives until menu item is clicked
 695                    markdown.capture_selection_for_context_menu();
 696                }
 697            }
 698        });
 699
 700        self.on_mouse_event(window, cx, {
 701            let rendered_text = rendered_text.clone();
 702            let hitbox = hitbox.clone();
 703            move |markdown, event: &MouseDownEvent, phase, window, cx| {
 704                if hitbox.is_hovered(window) {
 705                    if phase.bubble() {
 706                        if let Some(link) = rendered_text.link_for_position(event.position) {
 707                            markdown.pressed_link = Some(link.clone());
 708                        } else {
 709                            let source_index =
 710                                match rendered_text.source_index_for_position(event.position) {
 711                                    Ok(ix) | Err(ix) => ix,
 712                                };
 713                            let (range, mode) = match event.click_count {
 714                                1 => {
 715                                    let range = source_index..source_index;
 716                                    (range, SelectMode::Character)
 717                                }
 718                                2 => {
 719                                    let range = rendered_text.surrounding_word_range(source_index);
 720                                    (range.clone(), SelectMode::Word(range))
 721                                }
 722                                3 => {
 723                                    let range = rendered_text.surrounding_line_range(source_index);
 724                                    (range.clone(), SelectMode::Line(range))
 725                                }
 726                                _ => {
 727                                    let range = 0..rendered_text
 728                                        .lines
 729                                        .last()
 730                                        .map(|line| line.source_end)
 731                                        .unwrap_or(0);
 732                                    (range, SelectMode::All)
 733                                }
 734                            };
 735                            markdown.selection = Selection {
 736                                start: range.start,
 737                                end: range.end,
 738                                reversed: false,
 739                                pending: true,
 740                                mode,
 741                            };
 742                            window.focus(&markdown.focus_handle, cx);
 743                        }
 744
 745                        window.prevent_default();
 746                        cx.notify();
 747                    }
 748                } else if phase.capture() && event.button == MouseButton::Left {
 749                    markdown.selection = Selection::default();
 750                    markdown.pressed_link = None;
 751                    cx.notify();
 752                }
 753            }
 754        });
 755        self.on_mouse_event(window, cx, {
 756            let rendered_text = rendered_text.clone();
 757            let hitbox = hitbox.clone();
 758            let was_hovering_link = is_hovering_link;
 759            move |markdown, event: &MouseMoveEvent, phase, window, cx| {
 760                if phase.capture() {
 761                    return;
 762                }
 763
 764                if markdown.selection.pending {
 765                    let source_index = match rendered_text.source_index_for_position(event.position)
 766                    {
 767                        Ok(ix) | Err(ix) => ix,
 768                    };
 769                    markdown.selection.set_head(source_index, &rendered_text);
 770                    markdown.autoscroll_request = Some(source_index);
 771                    cx.notify();
 772                } else {
 773                    let is_hovering_link = hitbox.is_hovered(window)
 774                        && rendered_text.link_for_position(event.position).is_some();
 775                    if is_hovering_link != was_hovering_link {
 776                        cx.notify();
 777                    }
 778                }
 779            }
 780        });
 781        self.on_mouse_event(window, cx, {
 782            let rendered_text = rendered_text.clone();
 783            move |markdown, event: &MouseUpEvent, phase, window, cx| {
 784                if phase.bubble() {
 785                    if let Some(pressed_link) = markdown.pressed_link.take()
 786                        && Some(&pressed_link) == rendered_text.link_for_position(event.position)
 787                    {
 788                        if let Some(open_url) = on_open_url.as_ref() {
 789                            open_url(pressed_link.destination_url, window, cx);
 790                        } else {
 791                            cx.open_url(&pressed_link.destination_url);
 792                        }
 793                    }
 794                } else if markdown.selection.pending {
 795                    markdown.selection.pending = false;
 796                    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 797                    {
 798                        let text = rendered_text
 799                            .text_for_range(markdown.selection.start..markdown.selection.end);
 800                        cx.write_to_primary(ClipboardItem::new_string(text))
 801                    }
 802                    cx.notify();
 803                }
 804            }
 805        });
 806    }
 807
 808    fn autoscroll(
 809        &self,
 810        rendered_text: &RenderedText,
 811        window: &mut Window,
 812        cx: &mut App,
 813    ) -> Option<()> {
 814        let autoscroll_index = self
 815            .markdown
 816            .update(cx, |markdown, _| markdown.autoscroll_request.take())?;
 817        let (position, line_height) = rendered_text.position_for_source_index(autoscroll_index)?;
 818
 819        let text_style = self.style.base_text_style.clone();
 820        let font_id = window.text_system().resolve_font(&text_style.font());
 821        let font_size = text_style.font_size.to_pixels(window.rem_size());
 822        let em_width = window.text_system().em_width(font_id, font_size).unwrap();
 823        window.request_autoscroll(Bounds::from_corners(
 824            point(position.x - 3. * em_width, position.y - 3. * line_height),
 825            point(position.x + 3. * em_width, position.y + 3. * line_height),
 826        ));
 827        Some(())
 828    }
 829
 830    fn on_mouse_event<T: MouseEvent>(
 831        &self,
 832        window: &mut Window,
 833        _cx: &mut App,
 834        mut f: impl 'static
 835        + FnMut(&mut Markdown, &T, DispatchPhase, &mut Window, &mut Context<Markdown>),
 836    ) {
 837        window.on_mouse_event({
 838            let markdown = self.markdown.downgrade();
 839            move |event, phase, window, cx| {
 840                markdown
 841                    .update(cx, |markdown, cx| f(markdown, event, phase, window, cx))
 842                    .log_err();
 843            }
 844        });
 845    }
 846}
 847
 848impl Styled for MarkdownElement {
 849    fn style(&mut self) -> &mut StyleRefinement {
 850        &mut self.style.container_style
 851    }
 852}
 853
 854impl Element for MarkdownElement {
 855    type RequestLayoutState = RenderedMarkdown;
 856    type PrepaintState = Hitbox;
 857
 858    fn id(&self) -> Option<ElementId> {
 859        None
 860    }
 861
 862    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
 863        None
 864    }
 865
 866    fn request_layout(
 867        &mut self,
 868        _id: Option<&GlobalElementId>,
 869        _inspector_id: Option<&gpui::InspectorElementId>,
 870        window: &mut Window,
 871        cx: &mut App,
 872    ) -> (gpui::LayoutId, Self::RequestLayoutState) {
 873        let mut builder = MarkdownElementBuilder::new(
 874            &self.style.container_style,
 875            self.style.base_text_style.clone(),
 876            self.style.syntax.clone(),
 877        );
 878        let (parsed_markdown, images) = {
 879            let markdown = self.markdown.read(cx);
 880            (
 881                markdown.parsed_markdown.clone(),
 882                markdown.images_by_source_offset.clone(),
 883            )
 884        };
 885        let markdown_end = if let Some(last) = parsed_markdown.events.last() {
 886            last.0.end
 887        } else {
 888            0
 889        };
 890        let mut code_block_ids = HashSet::default();
 891
 892        let mut current_img_block_range: Option<Range<usize>> = None;
 893        for (index, (range, event)) in parsed_markdown.events.iter().enumerate() {
 894            // Skip alt text for images that rendered
 895            if let Some(current_img_block_range) = &current_img_block_range
 896                && current_img_block_range.end > range.end
 897            {
 898                continue;
 899            }
 900
 901            match event {
 902                MarkdownEvent::Start(tag) => {
 903                    match tag {
 904                        MarkdownTag::Image { .. } => {
 905                            if let Some(image) = images.get(&range.start) {
 906                                current_img_block_range = Some(range.clone());
 907                                builder.modify_current_div(|el| {
 908                                    el.items_center()
 909                                        .flex()
 910                                        .flex_row()
 911                                        .child(img(image.clone()))
 912                                });
 913                            }
 914                        }
 915                        MarkdownTag::Paragraph => {
 916                            builder.push_div(
 917                                div().when(!self.style.height_is_multiple_of_line_height, |el| {
 918                                    el.mb_2().line_height(rems(1.3))
 919                                }),
 920                                range,
 921                                markdown_end,
 922                            );
 923                        }
 924                        MarkdownTag::Heading { level, .. } => {
 925                            let mut heading = div().mb_2();
 926
 927                            heading = apply_heading_style(
 928                                heading,
 929                                *level,
 930                                self.style.heading_level_styles.as_ref(),
 931                            );
 932
 933                            heading.style().refine(&self.style.heading);
 934
 935                            let text_style = self.style.heading.text_style().clone();
 936
 937                            builder.push_text_style(text_style);
 938                            builder.push_div(heading, range, markdown_end);
 939                        }
 940                        MarkdownTag::BlockQuote => {
 941                            builder.push_text_style(self.style.block_quote.clone());
 942                            builder.push_div(
 943                                div()
 944                                    .pl_4()
 945                                    .mb_2()
 946                                    .border_l_4()
 947                                    .border_color(self.style.block_quote_border_color),
 948                                range,
 949                                markdown_end,
 950                            );
 951                        }
 952                        MarkdownTag::CodeBlock { kind, .. } => {
 953                            let language = match kind {
 954                                CodeBlockKind::Fenced => None,
 955                                CodeBlockKind::FencedLang(language) => {
 956                                    parsed_markdown.languages_by_name.get(language).cloned()
 957                                }
 958                                CodeBlockKind::FencedSrc(path_range) => parsed_markdown
 959                                    .languages_by_path
 960                                    .get(&path_range.path)
 961                                    .cloned(),
 962                                _ => None,
 963                            };
 964
 965                            let is_indented = matches!(kind, CodeBlockKind::Indented);
 966                            let scroll_handle = if self.style.code_block_overflow_x_scroll {
 967                                code_block_ids.insert(range.start);
 968                                Some(self.markdown.update(cx, |markdown, _| {
 969                                    markdown.code_block_scroll_handle(range.start)
 970                                }))
 971                            } else {
 972                                None
 973                            };
 974
 975                            match (&self.code_block_renderer, is_indented) {
 976                                (CodeBlockRenderer::Default { .. }, _) | (_, true) => {
 977                                    // This is a parent container that we can position the copy button inside.
 978                                    let parent_container =
 979                                        div().group("code_block").relative().w_full();
 980
 981                                    let mut parent_container: AnyDiv = if let Some(scroll_handle) =
 982                                        scroll_handle.as_ref()
 983                                    {
 984                                        let scrollbars = Scrollbars::new(ScrollAxes::Horizontal)
 985                                            .id(("markdown-code-block-scrollbar", range.start))
 986                                            .tracked_scroll_handle(scroll_handle)
 987                                            .with_track_along(
 988                                                ScrollAxes::Horizontal,
 989                                                cx.theme().colors().editor_background,
 990                                            )
 991                                            .notify_content();
 992
 993                                        parent_container
 994                                            .rounded_lg()
 995                                            .custom_scrollbars(scrollbars, window, cx)
 996                                            .into()
 997                                    } else {
 998                                        parent_container.into()
 999                                    };
1000
1001                                    if let CodeBlockRenderer::Default { border: true, .. } =
1002                                        &self.code_block_renderer
1003                                    {
1004                                        parent_container = parent_container
1005                                            .rounded_md()
1006                                            .border_1()
1007                                            .border_color(cx.theme().colors().border_variant);
1008                                    }
1009
1010                                    parent_container.style().refine(&self.style.code_block);
1011                                    builder.push_div(parent_container, range, markdown_end);
1012
1013                                    let code_block = div()
1014                                        .id(("code-block", range.start))
1015                                        .rounded_lg()
1016                                        .map(|mut code_block| {
1017                                            if let Some(scroll_handle) = scroll_handle.as_ref() {
1018                                                code_block.style().restrict_scroll_to_axis =
1019                                                    Some(true);
1020                                                code_block
1021                                                    .flex()
1022                                                    .overflow_x_scroll()
1023                                                    .track_scroll(scroll_handle)
1024                                            } else {
1025                                                code_block.w_full()
1026                                            }
1027                                        });
1028
1029                                    builder.push_text_style(self.style.code_block.text.to_owned());
1030                                    builder.push_code_block(language);
1031                                    builder.push_div(code_block, range, markdown_end);
1032                                }
1033                                (CodeBlockRenderer::Custom { .. }, _) => {}
1034                            }
1035                        }
1036                        MarkdownTag::HtmlBlock => builder.push_div(div(), range, markdown_end),
1037                        MarkdownTag::List(bullet_index) => {
1038                            builder.push_list(*bullet_index);
1039                            builder.push_div(div().pl_2p5(), range, markdown_end);
1040                        }
1041                        MarkdownTag::Item => {
1042                            let bullet = if let Some((_, MarkdownEvent::TaskListMarker(checked))) =
1043                                parsed_markdown.events.get(index.saturating_add(1))
1044                            {
1045                                let source = &parsed_markdown.source()[range.clone()];
1046
1047                                Checkbox::new(
1048                                    ElementId::Name(source.to_string().into()),
1049                                    if *checked {
1050                                        ToggleState::Selected
1051                                    } else {
1052                                        ToggleState::Unselected
1053                                    },
1054                                )
1055                                .fill()
1056                                .visualization_only(true)
1057                                .into_any_element()
1058                            } else if let Some(bullet_index) = builder.next_bullet_index() {
1059                                div().child(format!("{}.", bullet_index)).into_any_element()
1060                            } else {
1061                                div().child("").into_any_element()
1062                            };
1063                            builder.push_div(
1064                                div()
1065                                    .when(!self.style.height_is_multiple_of_line_height, |el| {
1066                                        el.mb_1().gap_1().line_height(rems(1.3))
1067                                    })
1068                                    .h_flex()
1069                                    .items_start()
1070                                    .child(bullet),
1071                                range,
1072                                markdown_end,
1073                            );
1074                            // Without `w_0`, text doesn't wrap to the width of the container.
1075                            builder.push_div(div().flex_1().w_0(), range, markdown_end);
1076                        }
1077                        MarkdownTag::Emphasis => builder.push_text_style(TextStyleRefinement {
1078                            font_style: Some(FontStyle::Italic),
1079                            ..Default::default()
1080                        }),
1081                        MarkdownTag::Strong => builder.push_text_style(TextStyleRefinement {
1082                            font_weight: Some(FontWeight::BOLD),
1083                            ..Default::default()
1084                        }),
1085                        MarkdownTag::Strikethrough => {
1086                            builder.push_text_style(TextStyleRefinement {
1087                                strikethrough: Some(StrikethroughStyle {
1088                                    thickness: px(1.),
1089                                    color: None,
1090                                }),
1091                                ..Default::default()
1092                            })
1093                        }
1094                        MarkdownTag::Link { dest_url, .. } => {
1095                            if builder.code_block_stack.is_empty() {
1096                                builder.push_link(dest_url.clone(), range.clone());
1097                                let style = self
1098                                    .style
1099                                    .link_callback
1100                                    .as_ref()
1101                                    .and_then(|callback| callback(dest_url, cx))
1102                                    .unwrap_or_else(|| self.style.link.clone());
1103                                builder.push_text_style(style)
1104                            }
1105                        }
1106                        MarkdownTag::MetadataBlock(_) => {}
1107                        MarkdownTag::Table(alignments) => {
1108                            builder.table.start(alignments.clone());
1109
1110                            let column_count = alignments.len();
1111                            builder.push_div(
1112                                div()
1113                                    .id(("table", range.start))
1114                                    .grid()
1115                                    .grid_cols(column_count as u16)
1116                                    .when(self.style.table_columns_min_size, |this| {
1117                                        this.grid_cols_min_content(column_count as u16)
1118                                    })
1119                                    .when(!self.style.table_columns_min_size, |this| {
1120                                        this.grid_cols(column_count as u16)
1121                                    })
1122                                    .size_full()
1123                                    .mb_2()
1124                                    .border(px(1.5))
1125                                    .border_color(cx.theme().colors().border)
1126                                    .rounded_sm()
1127                                    .overflow_hidden(),
1128                                range,
1129                                markdown_end,
1130                            );
1131                        }
1132                        MarkdownTag::TableHead => {
1133                            builder.table.start_head();
1134                            builder.push_text_style(TextStyleRefinement {
1135                                font_weight: Some(FontWeight::SEMIBOLD),
1136                                ..Default::default()
1137                            });
1138                        }
1139                        MarkdownTag::TableRow => {
1140                            builder.table.start_row();
1141                        }
1142                        MarkdownTag::TableCell => {
1143                            let is_header = builder.table.in_head;
1144                            let row_index = builder.table.row_index;
1145                            let col_index = builder.table.col_index;
1146
1147                            builder.push_div(
1148                                div()
1149                                    .when(col_index > 0, |this| this.border_l_1())
1150                                    .when(row_index > 0, |this| this.border_t_1())
1151                                    .border_color(cx.theme().colors().border)
1152                                    .px_1()
1153                                    .py_0p5()
1154                                    .when(is_header, |this| {
1155                                        this.bg(cx.theme().colors().title_bar_background)
1156                                    })
1157                                    .when(!is_header && row_index % 2 == 1, |this| {
1158                                        this.bg(cx.theme().colors().panel_background)
1159                                    }),
1160                                range,
1161                                markdown_end,
1162                            );
1163                        }
1164                        _ => log::debug!("unsupported markdown tag {:?}", tag),
1165                    }
1166                }
1167                MarkdownEvent::End(tag) => match tag {
1168                    MarkdownTagEnd::Image => {
1169                        current_img_block_range.take();
1170                    }
1171                    MarkdownTagEnd::Paragraph => {
1172                        builder.pop_div();
1173                    }
1174                    MarkdownTagEnd::Heading(_) => {
1175                        builder.pop_div();
1176                        builder.pop_text_style()
1177                    }
1178                    MarkdownTagEnd::BlockQuote(_kind) => {
1179                        builder.pop_text_style();
1180                        builder.pop_div()
1181                    }
1182                    MarkdownTagEnd::CodeBlock => {
1183                        builder.trim_trailing_newline();
1184
1185                        builder.pop_div();
1186                        builder.pop_code_block();
1187                        builder.pop_text_style();
1188
1189                        if let CodeBlockRenderer::Default {
1190                            copy_button: true, ..
1191                        } = &self.code_block_renderer
1192                        {
1193                            builder.modify_current_div(|el| {
1194                                let content_range = parser::extract_code_block_content_range(
1195                                    &parsed_markdown.source()[range.clone()],
1196                                );
1197                                let content_range = content_range.start + range.start
1198                                    ..content_range.end + range.start;
1199
1200                                let code = parsed_markdown.source()[content_range].to_string();
1201                                let codeblock = render_copy_code_block_button(
1202                                    range.end,
1203                                    code,
1204                                    self.markdown.clone(),
1205                                    cx,
1206                                );
1207                                el.child(
1208                                    h_flex()
1209                                        .w_4()
1210                                        .absolute()
1211                                        .top_1p5()
1212                                        .right_1p5()
1213                                        .justify_end()
1214                                        .child(codeblock),
1215                                )
1216                            });
1217                        }
1218
1219                        if let CodeBlockRenderer::Default {
1220                            copy_button_on_hover: true,
1221                            ..
1222                        } = &self.code_block_renderer
1223                        {
1224                            builder.modify_current_div(|el| {
1225                                let content_range = parser::extract_code_block_content_range(
1226                                    &parsed_markdown.source()[range.clone()],
1227                                );
1228                                let content_range = content_range.start + range.start
1229                                    ..content_range.end + range.start;
1230
1231                                let code = parsed_markdown.source()[content_range].to_string();
1232                                let codeblock = render_copy_code_block_button(
1233                                    range.end,
1234                                    code,
1235                                    self.markdown.clone(),
1236                                    cx,
1237                                );
1238                                el.child(
1239                                    h_flex()
1240                                        .w_4()
1241                                        .absolute()
1242                                        .top_0()
1243                                        .right_0()
1244                                        .justify_end()
1245                                        .visible_on_hover("code_block")
1246                                        .child(codeblock),
1247                                )
1248                            });
1249                        }
1250
1251                        // Pop the parent container.
1252                        builder.pop_div();
1253                    }
1254                    MarkdownTagEnd::HtmlBlock => builder.pop_div(),
1255                    MarkdownTagEnd::List(_) => {
1256                        builder.pop_list();
1257                        builder.pop_div();
1258                    }
1259                    MarkdownTagEnd::Item => {
1260                        builder.pop_div();
1261                        builder.pop_div();
1262                    }
1263                    MarkdownTagEnd::Emphasis => builder.pop_text_style(),
1264                    MarkdownTagEnd::Strong => builder.pop_text_style(),
1265                    MarkdownTagEnd::Strikethrough => builder.pop_text_style(),
1266                    MarkdownTagEnd::Link => {
1267                        if builder.code_block_stack.is_empty() {
1268                            builder.pop_text_style()
1269                        }
1270                    }
1271                    MarkdownTagEnd::Table => {
1272                        builder.pop_div();
1273                        builder.table.end();
1274                    }
1275                    MarkdownTagEnd::TableHead => {
1276                        builder.pop_text_style();
1277                        builder.table.end_head();
1278                    }
1279                    MarkdownTagEnd::TableRow => {
1280                        builder.table.end_row();
1281                    }
1282                    MarkdownTagEnd::TableCell => {
1283                        builder.pop_div();
1284                        builder.table.end_cell();
1285                    }
1286                    _ => log::debug!("unsupported markdown tag end: {:?}", tag),
1287                },
1288                MarkdownEvent::Text => {
1289                    builder.push_text(&parsed_markdown.source[range.clone()], range.clone());
1290                }
1291                MarkdownEvent::SubstitutedText(text) => {
1292                    builder.push_text(text, range.clone());
1293                }
1294                MarkdownEvent::Code => {
1295                    builder.push_text_style(self.style.inline_code.clone());
1296                    builder.push_text(&parsed_markdown.source[range.clone()], range.clone());
1297                    builder.pop_text_style();
1298                }
1299                MarkdownEvent::Html => {
1300                    let html = &parsed_markdown.source[range.clone()];
1301                    if html.starts_with("<!--") {
1302                        builder.html_comment = true;
1303                    }
1304                    if html.trim_end().ends_with("-->") {
1305                        builder.html_comment = false;
1306                        continue;
1307                    }
1308                    if builder.html_comment {
1309                        continue;
1310                    }
1311                    builder.push_text(html, range.clone());
1312                }
1313                MarkdownEvent::InlineHtml => {
1314                    let html = &parsed_markdown.source[range.clone()];
1315                    if html.starts_with("<code>") {
1316                        builder.push_text_style(self.style.inline_code.clone());
1317                        continue;
1318                    }
1319                    if html.trim_end().starts_with("</code>") {
1320                        builder.pop_text_style();
1321                        continue;
1322                    }
1323                    builder.push_text(&parsed_markdown.source[range.clone()], range.clone());
1324                }
1325                MarkdownEvent::Rule => {
1326                    builder.push_div(
1327                        div()
1328                            .border_b_1()
1329                            .my_2()
1330                            .border_color(self.style.rule_color),
1331                        range,
1332                        markdown_end,
1333                    );
1334                    builder.pop_div()
1335                }
1336                MarkdownEvent::SoftBreak => builder.push_text(" ", range.clone()),
1337                MarkdownEvent::HardBreak => builder.push_text("\n", range.clone()),
1338                MarkdownEvent::TaskListMarker(_) => {
1339                    // handled inside the `MarkdownTag::Item` case
1340                }
1341                _ => log::debug!("unsupported markdown event {:?}", event),
1342            }
1343        }
1344        if self.style.code_block_overflow_x_scroll {
1345            let code_block_ids = code_block_ids;
1346            self.markdown.update(cx, move |markdown, _| {
1347                markdown.retain_code_block_scroll_handles(&code_block_ids);
1348            });
1349        } else {
1350            self.markdown
1351                .update(cx, |markdown, _| markdown.clear_code_block_scroll_handles());
1352        }
1353        let mut rendered_markdown = builder.build();
1354        let child_layout_id = rendered_markdown.element.request_layout(window, cx);
1355        let layout_id = window.request_layout(gpui::Style::default(), [child_layout_id], cx);
1356        (layout_id, rendered_markdown)
1357    }
1358
1359    fn prepaint(
1360        &mut self,
1361        _id: Option<&GlobalElementId>,
1362        _inspector_id: Option<&gpui::InspectorElementId>,
1363        bounds: Bounds<Pixels>,
1364        rendered_markdown: &mut Self::RequestLayoutState,
1365        window: &mut Window,
1366        cx: &mut App,
1367    ) -> Self::PrepaintState {
1368        let focus_handle = self.markdown.read(cx).focus_handle.clone();
1369        window.set_focus_handle(&focus_handle, cx);
1370        window.set_view_id(self.markdown.entity_id());
1371
1372        let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
1373        rendered_markdown.element.prepaint(window, cx);
1374        self.autoscroll(&rendered_markdown.text, window, cx);
1375        hitbox
1376    }
1377
1378    fn paint(
1379        &mut self,
1380        _id: Option<&GlobalElementId>,
1381        _inspector_id: Option<&gpui::InspectorElementId>,
1382        bounds: Bounds<Pixels>,
1383        rendered_markdown: &mut Self::RequestLayoutState,
1384        hitbox: &mut Self::PrepaintState,
1385        window: &mut Window,
1386        cx: &mut App,
1387    ) {
1388        let mut context = KeyContext::default();
1389        context.add("Markdown");
1390        window.set_key_context(context);
1391        window.on_action(std::any::TypeId::of::<crate::Copy>(), {
1392            let entity = self.markdown.clone();
1393            let text = rendered_markdown.text.clone();
1394            move |_, phase, window, cx| {
1395                let text = text.clone();
1396                if phase == DispatchPhase::Bubble {
1397                    entity.update(cx, move |this, cx| this.copy(&text, window, cx))
1398                }
1399            }
1400        });
1401        window.on_action(std::any::TypeId::of::<crate::CopyAsMarkdown>(), {
1402            let entity = self.markdown.clone();
1403            move |_, phase, window, cx| {
1404                if phase == DispatchPhase::Bubble {
1405                    entity.update(cx, move |this, cx| this.copy_as_markdown(window, cx))
1406                }
1407            }
1408        });
1409
1410        self.paint_mouse_listeners(hitbox, &rendered_markdown.text, window, cx);
1411        rendered_markdown.element.paint(window, cx);
1412        self.paint_selection(bounds, &rendered_markdown.text, window, cx);
1413    }
1414}
1415
1416fn apply_heading_style(
1417    mut heading: Div,
1418    level: pulldown_cmark::HeadingLevel,
1419    custom_styles: Option<&HeadingLevelStyles>,
1420) -> Div {
1421    heading = match level {
1422        pulldown_cmark::HeadingLevel::H1 => heading.text_3xl(),
1423        pulldown_cmark::HeadingLevel::H2 => heading.text_2xl(),
1424        pulldown_cmark::HeadingLevel::H3 => heading.text_xl(),
1425        pulldown_cmark::HeadingLevel::H4 => heading.text_lg(),
1426        pulldown_cmark::HeadingLevel::H5 => heading.text_base(),
1427        pulldown_cmark::HeadingLevel::H6 => heading.text_sm(),
1428    };
1429
1430    if let Some(styles) = custom_styles {
1431        let style_opt = match level {
1432            pulldown_cmark::HeadingLevel::H1 => &styles.h1,
1433            pulldown_cmark::HeadingLevel::H2 => &styles.h2,
1434            pulldown_cmark::HeadingLevel::H3 => &styles.h3,
1435            pulldown_cmark::HeadingLevel::H4 => &styles.h4,
1436            pulldown_cmark::HeadingLevel::H5 => &styles.h5,
1437            pulldown_cmark::HeadingLevel::H6 => &styles.h6,
1438        };
1439
1440        if let Some(style) = style_opt {
1441            heading.style().text = style.clone();
1442        }
1443    }
1444
1445    heading
1446}
1447
1448fn render_copy_code_block_button(
1449    id: usize,
1450    code: String,
1451    markdown: Entity<Markdown>,
1452    cx: &App,
1453) -> impl IntoElement {
1454    let id = ElementId::named_usize("copy-markdown-code", id);
1455    let was_copied = markdown.read(cx).copied_code_blocks.contains(&id);
1456    IconButton::new(
1457        id.clone(),
1458        if was_copied {
1459            IconName::Check
1460        } else {
1461            IconName::Copy
1462        },
1463    )
1464    .icon_color(Color::Muted)
1465    .icon_size(IconSize::Small)
1466    .style(ButtonStyle::Filled)
1467    .shape(ui::IconButtonShape::Square)
1468    .tooltip(Tooltip::text("Copy"))
1469    .on_click({
1470        let markdown = markdown;
1471        move |_event, _window, cx| {
1472            let id = id.clone();
1473            markdown.update(cx, |this, cx| {
1474                this.copied_code_blocks.insert(id.clone());
1475
1476                cx.write_to_clipboard(ClipboardItem::new_string(code.clone()));
1477
1478                cx.spawn(async move |this, cx| {
1479                    cx.background_executor().timer(Duration::from_secs(2)).await;
1480
1481                    cx.update(|cx| {
1482                        this.update(cx, |this, cx| {
1483                            this.copied_code_blocks.remove(&id);
1484                            cx.notify();
1485                        })
1486                    })
1487                    .ok();
1488                })
1489                .detach();
1490            });
1491        }
1492    })
1493}
1494
1495impl IntoElement for MarkdownElement {
1496    type Element = Self;
1497
1498    fn into_element(self) -> Self::Element {
1499        self
1500    }
1501}
1502
1503pub enum AnyDiv {
1504    Div(Div),
1505    Stateful(Stateful<Div>),
1506}
1507
1508impl AnyDiv {
1509    fn into_any_element(self) -> AnyElement {
1510        match self {
1511            Self::Div(div) => div.into_any_element(),
1512            Self::Stateful(div) => div.into_any_element(),
1513        }
1514    }
1515}
1516
1517impl From<Div> for AnyDiv {
1518    fn from(value: Div) -> Self {
1519        Self::Div(value)
1520    }
1521}
1522
1523impl From<Stateful<Div>> for AnyDiv {
1524    fn from(value: Stateful<Div>) -> Self {
1525        Self::Stateful(value)
1526    }
1527}
1528
1529impl Styled for AnyDiv {
1530    fn style(&mut self) -> &mut StyleRefinement {
1531        match self {
1532            Self::Div(div) => div.style(),
1533            Self::Stateful(div) => div.style(),
1534        }
1535    }
1536}
1537
1538impl ParentElement for AnyDiv {
1539    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1540        match self {
1541            Self::Div(div) => div.extend(elements),
1542            Self::Stateful(div) => div.extend(elements),
1543        }
1544    }
1545}
1546
1547#[derive(Default)]
1548struct TableState {
1549    alignments: Vec<Alignment>,
1550    in_head: bool,
1551    row_index: usize,
1552    col_index: usize,
1553}
1554
1555impl TableState {
1556    fn start(&mut self, alignments: Vec<Alignment>) {
1557        self.alignments = alignments;
1558        self.in_head = false;
1559        self.row_index = 0;
1560        self.col_index = 0;
1561    }
1562
1563    fn end(&mut self) {
1564        self.alignments.clear();
1565        self.in_head = false;
1566        self.row_index = 0;
1567        self.col_index = 0;
1568    }
1569
1570    fn start_head(&mut self) {
1571        self.in_head = true;
1572    }
1573
1574    fn end_head(&mut self) {
1575        self.in_head = false;
1576    }
1577
1578    fn start_row(&mut self) {
1579        self.col_index = 0;
1580    }
1581
1582    fn end_row(&mut self) {
1583        self.row_index += 1;
1584    }
1585
1586    fn end_cell(&mut self) {
1587        self.col_index += 1;
1588    }
1589}
1590
1591struct MarkdownElementBuilder {
1592    div_stack: Vec<AnyDiv>,
1593    rendered_lines: Vec<RenderedLine>,
1594    pending_line: PendingLine,
1595    rendered_links: Vec<RenderedLink>,
1596    current_source_index: usize,
1597    html_comment: bool,
1598    base_text_style: TextStyle,
1599    text_style_stack: Vec<TextStyleRefinement>,
1600    code_block_stack: Vec<Option<Arc<Language>>>,
1601    list_stack: Vec<ListStackEntry>,
1602    table: TableState,
1603    syntax_theme: Arc<SyntaxTheme>,
1604}
1605
1606#[derive(Default)]
1607struct PendingLine {
1608    text: String,
1609    runs: Vec<TextRun>,
1610    source_mappings: Vec<SourceMapping>,
1611}
1612
1613struct ListStackEntry {
1614    bullet_index: Option<u64>,
1615}
1616
1617impl MarkdownElementBuilder {
1618    fn new(
1619        container_style: &StyleRefinement,
1620        base_text_style: TextStyle,
1621        syntax_theme: Arc<SyntaxTheme>,
1622    ) -> Self {
1623        Self {
1624            div_stack: vec![{
1625                let mut base_div = div();
1626                base_div.style().refine(container_style);
1627                base_div.debug_selector(|| "inner".into()).into()
1628            }],
1629            rendered_lines: Vec::new(),
1630            pending_line: PendingLine::default(),
1631            rendered_links: Vec::new(),
1632            current_source_index: 0,
1633            html_comment: false,
1634            base_text_style,
1635            text_style_stack: Vec::new(),
1636            code_block_stack: Vec::new(),
1637            list_stack: Vec::new(),
1638            table: TableState::default(),
1639            syntax_theme,
1640        }
1641    }
1642
1643    fn push_text_style(&mut self, style: TextStyleRefinement) {
1644        self.text_style_stack.push(style);
1645    }
1646
1647    fn text_style(&self) -> TextStyle {
1648        let mut style = self.base_text_style.clone();
1649        for refinement in &self.text_style_stack {
1650            style.refine(refinement);
1651        }
1652        style
1653    }
1654
1655    fn pop_text_style(&mut self) {
1656        self.text_style_stack.pop();
1657    }
1658
1659    fn push_div(&mut self, div: impl Into<AnyDiv>, range: &Range<usize>, markdown_end: usize) {
1660        let mut div = div.into();
1661        self.flush_text();
1662
1663        if range.start == 0 {
1664            // Remove the top margin on the first element.
1665            div.style().refine(&StyleRefinement {
1666                margin: gpui::EdgesRefinement {
1667                    top: Some(Length::Definite(px(0.).into())),
1668                    left: None,
1669                    right: None,
1670                    bottom: None,
1671                },
1672                ..Default::default()
1673            });
1674        }
1675
1676        if range.end == markdown_end {
1677            div.style().refine(&StyleRefinement {
1678                margin: gpui::EdgesRefinement {
1679                    top: None,
1680                    left: None,
1681                    right: None,
1682                    bottom: Some(Length::Definite(rems(0.).into())),
1683                },
1684                ..Default::default()
1685            });
1686        }
1687
1688        self.div_stack.push(div);
1689    }
1690
1691    fn modify_current_div(&mut self, f: impl FnOnce(AnyDiv) -> AnyDiv) {
1692        self.flush_text();
1693        if let Some(div) = self.div_stack.pop() {
1694            self.div_stack.push(f(div));
1695        }
1696    }
1697
1698    fn pop_div(&mut self) {
1699        self.flush_text();
1700        let div = self.div_stack.pop().unwrap().into_any_element();
1701        self.div_stack.last_mut().unwrap().extend(iter::once(div));
1702    }
1703
1704    fn push_list(&mut self, bullet_index: Option<u64>) {
1705        self.list_stack.push(ListStackEntry { bullet_index });
1706    }
1707
1708    fn next_bullet_index(&mut self) -> Option<u64> {
1709        self.list_stack.last_mut().and_then(|entry| {
1710            let item_index = entry.bullet_index.as_mut()?;
1711            *item_index += 1;
1712            Some(*item_index - 1)
1713        })
1714    }
1715
1716    fn pop_list(&mut self) {
1717        self.list_stack.pop();
1718    }
1719
1720    fn push_code_block(&mut self, language: Option<Arc<Language>>) {
1721        self.code_block_stack.push(language);
1722    }
1723
1724    fn pop_code_block(&mut self) {
1725        self.code_block_stack.pop();
1726    }
1727
1728    fn push_link(&mut self, destination_url: SharedString, source_range: Range<usize>) {
1729        self.rendered_links.push(RenderedLink {
1730            source_range,
1731            destination_url,
1732        });
1733    }
1734
1735    fn push_text(&mut self, text: &str, source_range: Range<usize>) {
1736        self.pending_line.source_mappings.push(SourceMapping {
1737            rendered_index: self.pending_line.text.len(),
1738            source_index: source_range.start,
1739        });
1740        self.pending_line.text.push_str(text);
1741        self.current_source_index = source_range.end;
1742
1743        if let Some(Some(language)) = self.code_block_stack.last() {
1744            let mut offset = 0;
1745            for (range, highlight_id) in language.highlight_text(&Rope::from(text), 0..text.len()) {
1746                if range.start > offset {
1747                    self.pending_line
1748                        .runs
1749                        .push(self.text_style().to_run(range.start - offset));
1750                }
1751
1752                let mut run_style = self.text_style();
1753                if let Some(highlight) = highlight_id.style(&self.syntax_theme) {
1754                    run_style = run_style.highlight(highlight);
1755                }
1756                self.pending_line.runs.push(run_style.to_run(range.len()));
1757                offset = range.end;
1758            }
1759
1760            if offset < text.len() {
1761                self.pending_line
1762                    .runs
1763                    .push(self.text_style().to_run(text.len() - offset));
1764            }
1765        } else {
1766            self.pending_line
1767                .runs
1768                .push(self.text_style().to_run(text.len()));
1769        }
1770    }
1771
1772    fn trim_trailing_newline(&mut self) {
1773        if self.pending_line.text.ends_with('\n') {
1774            self.pending_line
1775                .text
1776                .truncate(self.pending_line.text.len() - 1);
1777            self.pending_line.runs.last_mut().unwrap().len -= 1;
1778            self.current_source_index -= 1;
1779        }
1780    }
1781
1782    fn flush_text(&mut self) {
1783        let line = mem::take(&mut self.pending_line);
1784        if line.text.is_empty() {
1785            return;
1786        }
1787
1788        let text = StyledText::new(line.text).with_runs(line.runs);
1789        self.rendered_lines.push(RenderedLine {
1790            layout: text.layout().clone(),
1791            source_mappings: line.source_mappings,
1792            source_end: self.current_source_index,
1793        });
1794        self.div_stack.last_mut().unwrap().extend([text.into_any()]);
1795    }
1796
1797    fn build(mut self) -> RenderedMarkdown {
1798        debug_assert_eq!(self.div_stack.len(), 1);
1799        self.flush_text();
1800        RenderedMarkdown {
1801            element: self.div_stack.pop().unwrap().into_any_element(),
1802            text: RenderedText {
1803                lines: self.rendered_lines.into(),
1804                links: self.rendered_links.into(),
1805            },
1806        }
1807    }
1808}
1809
1810struct RenderedLine {
1811    layout: TextLayout,
1812    source_mappings: Vec<SourceMapping>,
1813    source_end: usize,
1814}
1815
1816impl RenderedLine {
1817    fn rendered_index_for_source_index(&self, source_index: usize) -> usize {
1818        if source_index >= self.source_end {
1819            return self.layout.len();
1820        }
1821
1822        let mapping = match self
1823            .source_mappings
1824            .binary_search_by_key(&source_index, |probe| probe.source_index)
1825        {
1826            Ok(ix) => &self.source_mappings[ix],
1827            Err(ix) => &self.source_mappings[ix - 1],
1828        };
1829        mapping.rendered_index + (source_index - mapping.source_index)
1830    }
1831
1832    fn source_index_for_rendered_index(&self, rendered_index: usize) -> usize {
1833        if rendered_index >= self.layout.len() {
1834            return self.source_end;
1835        }
1836
1837        let mapping = match self
1838            .source_mappings
1839            .binary_search_by_key(&rendered_index, |probe| probe.rendered_index)
1840        {
1841            Ok(ix) => &self.source_mappings[ix],
1842            Err(ix) => &self.source_mappings[ix - 1],
1843        };
1844        mapping.source_index + (rendered_index - mapping.rendered_index)
1845    }
1846
1847    fn source_index_for_position(&self, position: Point<Pixels>) -> Result<usize, usize> {
1848        let line_rendered_index;
1849        let out_of_bounds;
1850        match self.layout.index_for_position(position) {
1851            Ok(ix) => {
1852                line_rendered_index = ix;
1853                out_of_bounds = false;
1854            }
1855            Err(ix) => {
1856                line_rendered_index = ix;
1857                out_of_bounds = true;
1858            }
1859        };
1860        let source_index = self.source_index_for_rendered_index(line_rendered_index);
1861        if out_of_bounds {
1862            Err(source_index)
1863        } else {
1864            Ok(source_index)
1865        }
1866    }
1867}
1868
1869#[derive(Copy, Clone, Debug, Default)]
1870struct SourceMapping {
1871    rendered_index: usize,
1872    source_index: usize,
1873}
1874
1875pub struct RenderedMarkdown {
1876    element: AnyElement,
1877    text: RenderedText,
1878}
1879
1880#[derive(Clone)]
1881struct RenderedText {
1882    lines: Rc<[RenderedLine]>,
1883    links: Rc<[RenderedLink]>,
1884}
1885
1886#[derive(Debug, Clone, Eq, PartialEq)]
1887struct RenderedLink {
1888    source_range: Range<usize>,
1889    destination_url: SharedString,
1890}
1891
1892impl RenderedText {
1893    fn source_index_for_position(&self, position: Point<Pixels>) -> Result<usize, usize> {
1894        let mut lines = self.lines.iter().peekable();
1895
1896        while let Some(line) = lines.next() {
1897            let line_bounds = line.layout.bounds();
1898            if position.y > line_bounds.bottom() {
1899                if let Some(next_line) = lines.peek()
1900                    && position.y < next_line.layout.bounds().top()
1901                {
1902                    return Err(line.source_end);
1903                }
1904
1905                continue;
1906            }
1907
1908            return line.source_index_for_position(position);
1909        }
1910
1911        Err(self.lines.last().map_or(0, |line| line.source_end))
1912    }
1913
1914    fn position_for_source_index(&self, source_index: usize) -> Option<(Point<Pixels>, Pixels)> {
1915        for line in self.lines.iter() {
1916            let line_source_start = line.source_mappings.first().unwrap().source_index;
1917            if source_index < line_source_start {
1918                break;
1919            } else if source_index > line.source_end {
1920                continue;
1921            } else {
1922                let line_height = line.layout.line_height();
1923                let rendered_index_within_line = line.rendered_index_for_source_index(source_index);
1924                let position = line.layout.position_for_index(rendered_index_within_line)?;
1925                return Some((position, line_height));
1926            }
1927        }
1928        None
1929    }
1930
1931    fn surrounding_word_range(&self, source_index: usize) -> Range<usize> {
1932        for line in self.lines.iter() {
1933            if source_index > line.source_end {
1934                continue;
1935            }
1936
1937            let line_rendered_start = line.source_mappings.first().unwrap().rendered_index;
1938            let rendered_index_in_line =
1939                line.rendered_index_for_source_index(source_index) - line_rendered_start;
1940            let text = line.layout.text();
1941            let previous_space = if let Some(idx) = text[0..rendered_index_in_line].rfind(' ') {
1942                idx + ' '.len_utf8()
1943            } else {
1944                0
1945            };
1946            let next_space = if let Some(idx) = text[rendered_index_in_line..].find(' ') {
1947                rendered_index_in_line + idx
1948            } else {
1949                text.len()
1950            };
1951
1952            return line.source_index_for_rendered_index(line_rendered_start + previous_space)
1953                ..line.source_index_for_rendered_index(line_rendered_start + next_space);
1954        }
1955
1956        source_index..source_index
1957    }
1958
1959    fn surrounding_line_range(&self, source_index: usize) -> Range<usize> {
1960        for line in self.lines.iter() {
1961            if source_index > line.source_end {
1962                continue;
1963            }
1964            let line_source_start = line.source_mappings.first().unwrap().source_index;
1965            return line_source_start..line.source_end;
1966        }
1967
1968        source_index..source_index
1969    }
1970
1971    fn text_for_range(&self, range: Range<usize>) -> String {
1972        let mut accumulator = String::new();
1973
1974        for line in self.lines.iter() {
1975            if range.start > line.source_end {
1976                continue;
1977            }
1978            let line_source_start = line.source_mappings.first().unwrap().source_index;
1979            if range.end < line_source_start {
1980                break;
1981            }
1982
1983            let text = line.layout.text();
1984
1985            let start = if range.start < line_source_start {
1986                0
1987            } else {
1988                line.rendered_index_for_source_index(range.start)
1989            };
1990            let end = if range.end > line.source_end {
1991                line.rendered_index_for_source_index(line.source_end)
1992            } else {
1993                line.rendered_index_for_source_index(range.end)
1994            }
1995            .min(text.len());
1996
1997            accumulator.push_str(&text[start..end]);
1998            accumulator.push('\n');
1999        }
2000        // Remove trailing newline
2001        accumulator.pop();
2002        accumulator
2003    }
2004
2005    fn link_for_position(&self, position: Point<Pixels>) -> Option<&RenderedLink> {
2006        let source_index = self.source_index_for_position(position).ok()?;
2007        self.links
2008            .iter()
2009            .find(|link| link.source_range.contains(&source_index))
2010    }
2011}
2012
2013#[cfg(test)]
2014mod tests {
2015    use super::*;
2016    use gpui::{TestAppContext, size};
2017
2018    #[gpui::test]
2019    fn test_mappings(cx: &mut TestAppContext) {
2020        // Formatting.
2021        assert_mappings(
2022            &render_markdown("He*l*lo", cx),
2023            vec![vec![(0, 0), (1, 1), (2, 3), (3, 5), (4, 6), (5, 7)]],
2024        );
2025
2026        // Multiple lines.
2027        assert_mappings(
2028            &render_markdown("Hello\n\nWorld", cx),
2029            vec![
2030                vec![(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5)],
2031                vec![(0, 7), (1, 8), (2, 9), (3, 10), (4, 11), (5, 12)],
2032            ],
2033        );
2034
2035        // Multi-byte characters.
2036        assert_mappings(
2037            &render_markdown("αβγ\n\nδεζ", cx),
2038            vec![
2039                vec![(0, 0), (2, 2), (4, 4), (6, 6)],
2040                vec![(0, 8), (2, 10), (4, 12), (6, 14)],
2041            ],
2042        );
2043
2044        // Smart quotes.
2045        assert_mappings(&render_markdown("\"", cx), vec![vec![(0, 0), (3, 1)]]);
2046        assert_mappings(
2047            &render_markdown("\"hey\"", cx),
2048            vec![vec![(0, 0), (3, 1), (4, 2), (5, 3), (6, 4), (9, 5)]],
2049        );
2050
2051        // HTML Comments are ignored
2052        assert_mappings(
2053            &render_markdown(
2054                "<!--\nrdoc-file=string.c\n- str.intern   -> symbol\n- str.to_sym   -> symbol\n-->\nReturns",
2055                cx,
2056            ),
2057            vec![vec![
2058                (0, 78),
2059                (1, 79),
2060                (2, 80),
2061                (3, 81),
2062                (4, 82),
2063                (5, 83),
2064                (6, 84),
2065            ]],
2066        );
2067    }
2068
2069    fn render_markdown(markdown: &str, cx: &mut TestAppContext) -> RenderedText {
2070        struct TestWindow;
2071
2072        impl Render for TestWindow {
2073            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
2074                div()
2075            }
2076        }
2077
2078        let (_, cx) = cx.add_window_view(|_, _| TestWindow);
2079        let markdown = cx.new(|cx| Markdown::new(markdown.to_string().into(), None, None, cx));
2080        cx.run_until_parked();
2081        let (rendered, _) = cx.draw(
2082            Default::default(),
2083            size(px(600.0), px(600.0)),
2084            |_window, _cx| MarkdownElement::new(markdown, MarkdownStyle::default()),
2085        );
2086        rendered.text
2087    }
2088
2089    #[gpui::test]
2090    fn test_surrounding_word_range(cx: &mut TestAppContext) {
2091        let rendered = render_markdown("Hello world tesεζ", cx);
2092
2093        // Test word selection for "Hello"
2094        let word_range = rendered.surrounding_word_range(2); // Simulate click on 'l' in "Hello"
2095        let selected_text = rendered.text_for_range(word_range);
2096        assert_eq!(selected_text, "Hello");
2097
2098        // Test word selection for "world"
2099        let word_range = rendered.surrounding_word_range(7); // Simulate click on 'o' in "world"
2100        let selected_text = rendered.text_for_range(word_range);
2101        assert_eq!(selected_text, "world");
2102
2103        // Test word selection for "tesεζ"
2104        let word_range = rendered.surrounding_word_range(14); // Simulate click on 's' in "tesεζ"
2105        let selected_text = rendered.text_for_range(word_range);
2106        assert_eq!(selected_text, "tesεζ");
2107
2108        // Test word selection at word boundary (space)
2109        let word_range = rendered.surrounding_word_range(5); // Simulate click on space between "Hello" and "world", expect highlighting word to the left
2110        let selected_text = rendered.text_for_range(word_range);
2111        assert_eq!(selected_text, "Hello");
2112    }
2113
2114    #[gpui::test]
2115    fn test_surrounding_line_range(cx: &mut TestAppContext) {
2116        let rendered = render_markdown("First line\n\nSecond line\n\nThird lineεζ", cx);
2117
2118        // Test getting line range for first line
2119        let line_range = rendered.surrounding_line_range(5); // Simulate click somewhere in first line
2120        let selected_text = rendered.text_for_range(line_range);
2121        assert_eq!(selected_text, "First line");
2122
2123        // Test getting line range for second line
2124        let line_range = rendered.surrounding_line_range(13); // Simulate click at beginning in second line
2125        let selected_text = rendered.text_for_range(line_range);
2126        assert_eq!(selected_text, "Second line");
2127
2128        // Test getting line range for third line
2129        let line_range = rendered.surrounding_line_range(37); // Simulate click at end of third line with multi-byte chars
2130        let selected_text = rendered.text_for_range(line_range);
2131        assert_eq!(selected_text, "Third lineεζ");
2132    }
2133
2134    #[gpui::test]
2135    fn test_selection_head_movement(cx: &mut TestAppContext) {
2136        let rendered = render_markdown("Hello world test", cx);
2137
2138        let mut selection = Selection {
2139            start: 5,
2140            end: 5,
2141            reversed: false,
2142            pending: false,
2143            mode: SelectMode::Character,
2144        };
2145
2146        // Test forward selection
2147        selection.set_head(10, &rendered);
2148        assert_eq!(selection.start, 5);
2149        assert_eq!(selection.end, 10);
2150        assert!(!selection.reversed);
2151        assert_eq!(selection.tail(), 5);
2152
2153        // Test backward selection
2154        selection.set_head(2, &rendered);
2155        assert_eq!(selection.start, 2);
2156        assert_eq!(selection.end, 5);
2157        assert!(selection.reversed);
2158        assert_eq!(selection.tail(), 5);
2159
2160        // Test forward selection again from reversed state
2161        selection.set_head(15, &rendered);
2162        assert_eq!(selection.start, 5);
2163        assert_eq!(selection.end, 15);
2164        assert!(!selection.reversed);
2165        assert_eq!(selection.tail(), 5);
2166    }
2167
2168    #[gpui::test]
2169    fn test_word_selection_drag(cx: &mut TestAppContext) {
2170        let rendered = render_markdown("Hello world test", cx);
2171
2172        // Start with a simulated double-click on "world" (index 6-10)
2173        let word_range = rendered.surrounding_word_range(7); // Click on 'o' in "world"
2174        let mut selection = Selection {
2175            start: word_range.start,
2176            end: word_range.end,
2177            reversed: false,
2178            pending: true,
2179            mode: SelectMode::Word(word_range),
2180        };
2181
2182        // Drag forward to "test" - should expand selection to include "test"
2183        selection.set_head(13, &rendered); // Index in "test"
2184        assert_eq!(selection.start, 6); // Start of "world"
2185        assert_eq!(selection.end, 16); // End of "test"
2186        assert!(!selection.reversed);
2187        let selected_text = rendered.text_for_range(selection.start..selection.end);
2188        assert_eq!(selected_text, "world test");
2189
2190        // Drag backward to "Hello" - should expand selection to include "Hello"
2191        selection.set_head(2, &rendered); // Index in "Hello"
2192        assert_eq!(selection.start, 0); // Start of "Hello"
2193        assert_eq!(selection.end, 11); // End of "world" (original selection)
2194        assert!(selection.reversed);
2195        let selected_text = rendered.text_for_range(selection.start..selection.end);
2196        assert_eq!(selected_text, "Hello world");
2197
2198        // Drag back within original word - should revert to original selection
2199        selection.set_head(8, &rendered); // Back within "world"
2200        assert_eq!(selection.start, 6); // Start of "world"
2201        assert_eq!(selection.end, 11); // End of "world"
2202        assert!(!selection.reversed);
2203        let selected_text = rendered.text_for_range(selection.start..selection.end);
2204        assert_eq!(selected_text, "world");
2205    }
2206
2207    #[gpui::test]
2208    fn test_selection_with_markdown_formatting(cx: &mut TestAppContext) {
2209        let rendered = render_markdown(
2210            "This is **bold** text, this is *italic* text, use `code` here",
2211            cx,
2212        );
2213        let word_range = rendered.surrounding_word_range(10); // Inside "bold"
2214        let selected_text = rendered.text_for_range(word_range);
2215        assert_eq!(selected_text, "bold");
2216
2217        let word_range = rendered.surrounding_word_range(32); // Inside "italic"
2218        let selected_text = rendered.text_for_range(word_range);
2219        assert_eq!(selected_text, "italic");
2220
2221        let word_range = rendered.surrounding_word_range(51); // Inside "code"
2222        let selected_text = rendered.text_for_range(word_range);
2223        assert_eq!(selected_text, "code");
2224    }
2225
2226    #[gpui::test]
2227    fn test_all_selection(cx: &mut TestAppContext) {
2228        let rendered = render_markdown("Hello world\n\nThis is a test\n\nwith multiple lines", cx);
2229
2230        let total_length = rendered
2231            .lines
2232            .last()
2233            .map(|line| line.source_end)
2234            .unwrap_or(0);
2235
2236        let mut selection = Selection {
2237            start: 0,
2238            end: total_length,
2239            reversed: false,
2240            pending: true,
2241            mode: SelectMode::All,
2242        };
2243
2244        selection.set_head(5, &rendered); // Try to set head in middle
2245        assert_eq!(selection.start, 0);
2246        assert_eq!(selection.end, total_length);
2247        assert!(!selection.reversed);
2248
2249        selection.set_head(25, &rendered); // Try to set head near end
2250        assert_eq!(selection.start, 0);
2251        assert_eq!(selection.end, total_length);
2252        assert!(!selection.reversed);
2253
2254        let selected_text = rendered.text_for_range(selection.start..selection.end);
2255        assert_eq!(
2256            selected_text,
2257            "Hello world\nThis is a test\nwith multiple lines"
2258        );
2259    }
2260
2261    #[test]
2262    fn test_escape() {
2263        assert_eq!(Markdown::escape("hello `world`"), "hello \\`world\\`");
2264        assert_eq!(
2265            Markdown::escape("hello\n    cool world"),
2266            "hello\n\ncool world"
2267        );
2268    }
2269
2270    #[track_caller]
2271    fn assert_mappings(rendered: &RenderedText, expected: Vec<Vec<(usize, usize)>>) {
2272        assert_eq!(rendered.lines.len(), expected.len(), "line count mismatch");
2273        for (line_ix, line_mappings) in expected.into_iter().enumerate() {
2274            let line = &rendered.lines[line_ix];
2275
2276            assert!(
2277                line.source_mappings.windows(2).all(|mappings| {
2278                    mappings[0].source_index < mappings[1].source_index
2279                        && mappings[0].rendered_index < mappings[1].rendered_index
2280                }),
2281                "line {} has duplicate mappings: {:?}",
2282                line_ix,
2283                line.source_mappings
2284            );
2285
2286            for (rendered_ix, source_ix) in line_mappings {
2287                assert_eq!(
2288                    line.source_index_for_rendered_index(rendered_ix),
2289                    source_ix,
2290                    "line {}, rendered_ix {}",
2291                    line_ix,
2292                    rendered_ix
2293                );
2294
2295                assert_eq!(
2296                    line.rendered_index_for_source_index(source_ix),
2297                    rendered_ix,
2298                    "line {}, source_ix {}",
2299                    line_ix,
2300                    source_ix
2301                );
2302            }
2303        }
2304    }
2305}