1033 lines · 35.8 KB · Rust
RawDownload
mod parser; use crate::parser::CodeBlockKind;use futures::FutureExt;use gpui::{    actions, point, quad, AnyElement, AppContext, Bounds, ClipboardItem, CursorStyle,    DispatchPhase, Edges, FocusHandle, FocusableView, FontStyle, FontWeight, GlobalElementId,    Hitbox, Hsla, KeyContext, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent, Point,    Render, StrikethroughStyle, Style, StyledText, Task, TextLayout, TextRun, TextStyle,    TextStyleRefinement, View,};use language::{Language, LanguageRegistry, Rope};use parser::{parse_markdown, MarkdownEvent, MarkdownTag, MarkdownTagEnd};use std::{iter, mem, ops::Range, rc::Rc, sync::Arc};use theme::SyntaxTheme;use ui::prelude::*;use util::{ResultExt, TryFutureExt}; #[derive(Clone)]pub struct MarkdownStyle {    pub code_block: TextStyleRefinement,    pub inline_code: TextStyleRefinement,    pub block_quote: TextStyleRefinement,    pub link: TextStyleRefinement,    pub rule_color: Hsla,    pub block_quote_border_color: Hsla,    pub syntax: Arc<SyntaxTheme>,    pub selection_background_color: Hsla,} pub struct Markdown {    source: String,    selection: Selection,    pressed_link: Option<RenderedLink>,    autoscroll_request: Option<usize>,    style: MarkdownStyle,    parsed_markdown: ParsedMarkdown,    should_reparse: bool,    pending_parse: Option<Task<Option<()>>>,    focus_handle: FocusHandle,    language_registry: Option<Arc<LanguageRegistry>>,} actions!(markdown, [Copy]); impl Markdown {    pub fn new(        source: String,        style: MarkdownStyle,        language_registry: Option<Arc<LanguageRegistry>>,        cx: &mut ViewContext<Self>,    ) -> Self {        let focus_handle = cx.focus_handle();        let mut this = Self {            source,            selection: Selection::default(),            pressed_link: None,            autoscroll_request: None,            style,            should_reparse: false,            parsed_markdown: ParsedMarkdown::default(),            pending_parse: None,            focus_handle,            language_registry,        };        this.parse(cx);        this    }     pub fn append(&mut self, text: &str, cx: &mut ViewContext<Self>) {        self.source.push_str(text);        self.parse(cx);    }     pub fn reset(&mut self, source: String, cx: &mut ViewContext<Self>) {        self.source = source;        self.selection = Selection::default();        self.autoscroll_request = None;        self.pending_parse = None;        self.should_reparse = false;        self.parsed_markdown = ParsedMarkdown::default();        self.parse(cx);    }     pub fn source(&self) -> &str {        &self.source    }     fn copy(&self, text: &RenderedText, cx: &mut ViewContext<Self>) {        let text = text.text_for_range(self.selection.start..self.selection.end);        cx.write_to_clipboard(ClipboardItem::new(text));    }     fn parse(&mut self, cx: &mut ViewContext<Self>) {        if self.source.is_empty() {            return;        }         if self.pending_parse.is_some() {            self.should_reparse = true;            return;        }         let text = self.source.clone();        let parsed = cx.background_executor().spawn(async move {            let text = SharedString::from(text);            let events = Arc::from(parse_markdown(text.as_ref()));            anyhow::Ok(ParsedMarkdown {                source: text,                events,            })        });         self.should_reparse = false;        self.pending_parse = Some(cx.spawn(|this, mut cx| {            async move {                let parsed = parsed.await?;                this.update(&mut cx, |this, cx| {                    this.parsed_markdown = parsed;                    this.pending_parse.take();                    if this.should_reparse {                        this.parse(cx);                    }                    cx.notify();                })                .ok();                anyhow::Ok(())            }            .log_err()        }));    }} impl Render for Markdown {    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {        MarkdownElement::new(            cx.view().clone(),            self.style.clone(),            self.language_registry.clone(),        )    }} impl FocusableView for Markdown {    fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {        self.focus_handle.clone()    }} #[derive(Copy, Clone, Default, Debug)]struct Selection {    start: usize,    end: usize,    reversed: bool,    pending: bool,} impl Selection {    fn set_head(&mut self, head: usize) {        if head < self.tail() {            if !self.reversed {                self.end = self.start;                self.reversed = true;            }            self.start = head;        } else {            if self.reversed {                self.start = self.end;                self.reversed = false;            }            self.end = head;        }    }     fn tail(&self) -> usize {        if self.reversed {            self.end        } else {            self.start        }    }} #[derive(Clone)]struct ParsedMarkdown {    source: SharedString,    events: Arc<[(Range<usize>, MarkdownEvent)]>,} impl Default for ParsedMarkdown {    fn default() -> Self {        Self {            source: SharedString::default(),            events: Arc::from([]),        }    }} pub struct MarkdownElement {    markdown: View<Markdown>,    style: MarkdownStyle,    language_registry: Option<Arc<LanguageRegistry>>,} impl MarkdownElement {    fn new(        markdown: View<Markdown>,        style: MarkdownStyle,        language_registry: Option<Arc<LanguageRegistry>>,    ) -> Self {        Self {            markdown,            style,            language_registry,        }    }     fn load_language(&self, name: &str, cx: &mut WindowContext) -> Option<Arc<Language>> {        let language = self            .language_registry            .as_ref()?            .language_for_name(name)            .map(|language| language.ok())            .shared();         match language.clone().now_or_never() {            Some(language) => language,            None => {                let markdown = self.markdown.downgrade();                cx.spawn(|mut cx| async move {                    language.await;                    markdown.update(&mut cx, |_, cx| cx.notify())                })                .detach_and_log_err(cx);                None            }        }    }     fn paint_selection(        &mut self,        bounds: Bounds<Pixels>,        rendered_text: &RenderedText,        cx: &mut WindowContext,    ) {        let selection = self.markdown.read(cx).selection;        let selection_start = rendered_text.position_for_source_index(selection.start);        let selection_end = rendered_text.position_for_source_index(selection.end);         if let Some(((start_position, start_line_height), (end_position, end_line_height))) =            selection_start.zip(selection_end)        {            if start_position.y == end_position.y {                cx.paint_quad(quad(                    Bounds::from_corners(                        start_position,                        point(end_position.x, end_position.y + end_line_height),                    ),                    Pixels::ZERO,                    self.style.selection_background_color,                    Edges::default(),                    Hsla::transparent_black(),                ));            } else {                cx.paint_quad(quad(                    Bounds::from_corners(                        start_position,                        point(bounds.right(), start_position.y + start_line_height),                    ),                    Pixels::ZERO,                    self.style.selection_background_color,                    Edges::default(),                    Hsla::transparent_black(),                ));                 if end_position.y > start_position.y + start_line_height {                    cx.paint_quad(quad(                        Bounds::from_corners(                            point(bounds.left(), start_position.y + start_line_height),                            point(bounds.right(), end_position.y),                        ),                        Pixels::ZERO,                        self.style.selection_background_color,                        Edges::default(),                        Hsla::transparent_black(),                    ));                }                 cx.paint_quad(quad(                    Bounds::from_corners(                        point(bounds.left(), end_position.y),                        point(end_position.x, end_position.y + end_line_height),                    ),                    Pixels::ZERO,                    self.style.selection_background_color,                    Edges::default(),                    Hsla::transparent_black(),                ));            }        }    }     fn paint_mouse_listeners(        &mut self,        hitbox: &Hitbox,        rendered_text: &RenderedText,        cx: &mut WindowContext,    ) {        let is_hovering_link = hitbox.is_hovered(cx)            && !self.markdown.read(cx).selection.pending            && rendered_text                .link_for_position(cx.mouse_position())                .is_some();         if is_hovering_link {            cx.set_cursor_style(CursorStyle::PointingHand, hitbox);        } else {            cx.set_cursor_style(CursorStyle::IBeam, hitbox);        }         self.on_mouse_event(cx, {            let rendered_text = rendered_text.clone();            let hitbox = hitbox.clone();            move |markdown, event: &MouseDownEvent, phase, cx| {                if hitbox.is_hovered(cx) {                    if phase.bubble() {                        if let Some(link) = rendered_text.link_for_position(event.position) {                            markdown.pressed_link = Some(link.clone());                        } else {                            let source_index =                                match rendered_text.source_index_for_position(event.position) {                                    Ok(ix) | Err(ix) => ix,                                };                            let range = if event.click_count == 2 {                                rendered_text.surrounding_word_range(source_index)                            } else if event.click_count == 3 {                                rendered_text.surrounding_line_range(source_index)                            } else {                                source_index..source_index                            };                            markdown.selection = Selection {                                start: range.start,                                end: range.end,                                reversed: false,                                pending: true,                            };                            cx.focus(&markdown.focus_handle);                            cx.prevent_default()                        }                         cx.notify();                    }                } else if phase.capture() {                    markdown.selection = Selection::default();                    markdown.pressed_link = None;                    cx.notify();                }            }        });        self.on_mouse_event(cx, {            let rendered_text = rendered_text.clone();            let hitbox = hitbox.clone();            let was_hovering_link = is_hovering_link;            move |markdown, event: &MouseMoveEvent, phase, cx| {                if phase.capture() {                    return;                }                 if markdown.selection.pending {                    let source_index = match rendered_text.source_index_for_position(event.position)                    {                        Ok(ix) | Err(ix) => ix,                    };                    markdown.selection.set_head(source_index);                    markdown.autoscroll_request = Some(source_index);                    cx.notify();                } else {                    let is_hovering_link = hitbox.is_hovered(cx)                        && rendered_text.link_for_position(event.position).is_some();                    if is_hovering_link != was_hovering_link {                        cx.notify();                    }                }            }        });        self.on_mouse_event(cx, {            let rendered_text = rendered_text.clone();            move |markdown, event: &MouseUpEvent, phase, cx| {                if phase.bubble() {                    if let Some(pressed_link) = markdown.pressed_link.take() {                        if Some(&pressed_link) == rendered_text.link_for_position(event.position) {                            cx.open_url(&pressed_link.destination_url);                        }                    }                } else {                    if markdown.selection.pending {                        markdown.selection.pending = false;                        #[cfg(target_os = "linux")]                        {                            let text = rendered_text                                .text_for_range(markdown.selection.start..markdown.selection.end);                            cx.write_to_primary(ClipboardItem::new(text))                        }                        cx.notify();                    }                }            }        });    }     fn autoscroll(&mut self, rendered_text: &RenderedText, cx: &mut WindowContext) -> Option<()> {        let autoscroll_index = self            .markdown            .update(cx, |markdown, _| markdown.autoscroll_request.take())?;        let (position, line_height) = rendered_text.position_for_source_index(autoscroll_index)?;         let text_style = cx.text_style();        let font_id = cx.text_system().resolve_font(&text_style.font());        let font_size = text_style.font_size.to_pixels(cx.rem_size());        let em_width = cx            .text_system()            .typographic_bounds(font_id, font_size, 'm')            .unwrap()            .size            .width;        cx.request_autoscroll(Bounds::from_corners(            point(position.x - 3. * em_width, position.y - 3. * line_height),            point(position.x + 3. * em_width, position.y + 3. * line_height),        ));        Some(())    }     fn on_mouse_event<T: MouseEvent>(        &self,        cx: &mut WindowContext,        mut f: impl 'static + FnMut(&mut Markdown, &T, DispatchPhase, &mut ViewContext<Markdown>),    ) {        cx.on_mouse_event({            let markdown = self.markdown.downgrade();            move |event, phase, cx| {                markdown                    .update(cx, |markdown, cx| f(markdown, event, phase, cx))                    .log_err();            }        });    }} impl Element for MarkdownElement {    type RequestLayoutState = RenderedMarkdown;    type PrepaintState = Hitbox;     fn id(&self) -> Option<ElementId> {        None    }     fn request_layout(        &mut self,        _id: Option<&GlobalElementId>,        cx: &mut WindowContext,    ) -> (gpui::LayoutId, Self::RequestLayoutState) {        let mut builder = MarkdownElementBuilder::new(cx.text_style(), self.style.syntax.clone());        let parsed_markdown = self.markdown.read(cx).parsed_markdown.clone();        for (range, event) in parsed_markdown.events.iter() {            match event {                MarkdownEvent::Start(tag) => {                    match tag {                        MarkdownTag::Paragraph => {                            builder.push_div(div().mb_2().line_height(rems(1.3)));                        }                        MarkdownTag::Heading { level, .. } => {                            let mut heading = div().mb_2();                            heading = match level {                                pulldown_cmark::HeadingLevel::H1 => heading.text_3xl(),                                pulldown_cmark::HeadingLevel::H2 => heading.text_2xl(),                                pulldown_cmark::HeadingLevel::H3 => heading.text_xl(),                                pulldown_cmark::HeadingLevel::H4 => heading.text_lg(),                                _ => heading,                            };                            builder.push_div(heading);                        }                        MarkdownTag::BlockQuote => {                            builder.push_text_style(self.style.block_quote.clone());                            builder.push_div(                                div()                                    .pl_4()                                    .mb_2()                                    .border_l_4()                                    .border_color(self.style.block_quote_border_color),                            );                        }                        MarkdownTag::CodeBlock(kind) => {                            let language = if let CodeBlockKind::Fenced(language) = kind {                                self.load_language(language.as_ref(), cx)                            } else {                                None                            };                             builder.push_code_block(language);                            builder.push_text_style(self.style.code_block.clone());

Lines 1–500 of 1033.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t