markdown.rs

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