5940 lines · 217.1 KB · Rust
RawDownload
pub mod html;mod mermaid;pub mod parser;mod path_range;mod selection; use base64::Engine as _;use futures::FutureExt as _;use gpui::EdgesRefinement;use gpui::HitboxBehavior;use gpui::UnderlineStyle;use language::LanguageName; use log::Level;use mermaid::{    MermaidState, ParsedMarkdownMermaidDiagram, extract_mermaid_diagrams, render_mermaid_diagram,};pub use path_range::{LineCol, PathWithRange};use settings::Settings as _;use smallvec::SmallVec;use theme_settings::ThemeSettings;use util::maybe; use std::borrow::Cow;use std::cell::Cell;use std::collections::BTreeMap;use std::mem;use std::ops::Range;use std::path::Path;use std::rc::Rc;use std::sync::Arc;use std::time::Duration; use collections::{HashMap, HashSet};use gpui::{    AnyElement, App, BorderStyle, Bounds, ClipboardItem, CursorStyle, DispatchPhase, Edges, Entity,    FocusHandle, Focusable, FontStyle, FontWeight, GlobalElementId, Hitbox, Hsla, Image,    ImageFormat, ImageSource, KeyContext, Length, MouseButton, MouseDownEvent, MouseEvent,    MouseMoveEvent, MouseUpEvent, Point, ScrollHandle, Stateful, StrikethroughStyle,    StyleRefinement, StyledImage, StyledText, Subscription, Task, TextAlign, TextLayout, TextRun,    TextStyle, TextStyleRefinement, WrappedLineLayout, actions, canvas, img, point, quad,};use language::{CharClassifier, Language, LanguageRegistry, Rope};use parser::CodeBlockMetadata;use parser::{    MarkdownEvent, MarkdownTag, MarkdownTagEnd, ParsedMetadataBlock, parse_links_only,    parse_markdown_with_options,};use pulldown_cmark::{Alignment, BlockQuoteKind};use sum_tree::TreeMap;use theme::SyntaxTheme;use ui::{Checkbox, CopyButton, ScrollAxes, Scrollbars, Tooltip, WithScrollbar, prelude::*};use util::ResultExt; use crate::parser::CodeBlockKind; /// A callback function that can be used to customize the style of links based on the destination URL./// If the callback returns `None`, the default link style will be used.type LinkStyleCallback = Rc<dyn Fn(&str, &App) -> Option<TextStyleRefinement>>;pub type CodeSpanLinkCallback = Arc<dyn Fn(&str, &App) -> Option<SharedString> + 'static>;type UrlHoverCallback = Rc<dyn Fn(Option<SharedString>, &mut Window, &mut App)>;type SourceClickCallback = Box<dyn Fn(usize, usize, &mut Window, &mut App) -> bool>;type CheckboxToggleCallback = Rc<dyn Fn(Range<usize>, bool, &mut Window, &mut App)>; #[derive(Clone, Copy, Default)]pub struct BlockQuoteKindColors {    pub note: Hsla,    pub tip: Hsla,    pub important: Hsla,    pub warning: Hsla,    pub caution: Hsla,} impl BlockQuoteKindColors {    fn for_kind(&self, kind: Option<BlockQuoteKind>, default: Hsla) -> Hsla {        match kind {            Some(BlockQuoteKind::Note) => self.note,            Some(BlockQuoteKind::Tip) => self.tip,            Some(BlockQuoteKind::Important) => self.important,            Some(BlockQuoteKind::Warning) => self.warning,            Some(BlockQuoteKind::Caution) => self.caution,            None => default,        }    }} #[derive(Clone, Default)]pub struct HeadingLevelStyles {    pub h1: Option<TextStyleRefinement>,    pub h2: Option<TextStyleRefinement>,    pub h3: Option<TextStyleRefinement>,    pub h4: Option<TextStyleRefinement>,    pub h5: Option<TextStyleRefinement>,    pub h6: Option<TextStyleRefinement>,} #[derive(Clone)]pub struct MarkdownStyle {    pub base_text_style: TextStyle,    pub container_style: StyleRefinement,    pub code_block: StyleRefinement,    pub code_block_overflow_x_scroll: bool,    pub inline_code: TextStyleRefinement,    pub block_quote: TextStyleRefinement,    pub link: TextStyleRefinement,    pub link_callback: Option<LinkStyleCallback>,    pub rule_color: Hsla,    pub block_quote_border_color: Hsla,    pub block_quote_kind_colors: BlockQuoteKindColors,    pub syntax: Arc<SyntaxTheme>,    pub selection_background_color: Hsla,    pub heading: StyleRefinement,    pub heading_level_styles: Option<HeadingLevelStyles>,    pub heading_border_color: Option<Hsla>,    pub height_is_multiple_of_line_height: bool,    pub prevent_mouse_interaction: bool,    pub table_columns_min_size: bool,    pub soft_break_as_hard_break: bool,} impl Default for MarkdownStyle {    fn default() -> Self {        Self {            base_text_style: Default::default(),            container_style: Default::default(),            code_block: Default::default(),            code_block_overflow_x_scroll: false,            inline_code: Default::default(),            block_quote: Default::default(),            link: Default::default(),            link_callback: None,            rule_color: Default::default(),            block_quote_border_color: Default::default(),            block_quote_kind_colors: Default::default(),            syntax: Arc::new(SyntaxTheme::default()),            selection_background_color: Default::default(),            heading: Default::default(),            heading_level_styles: None,            heading_border_color: None,            height_is_multiple_of_line_height: false,            prevent_mouse_interaction: false,            table_columns_min_size: false,            soft_break_as_hard_break: false,        }    }} #[derive(Clone, Copy)]pub enum MarkdownFont {    Agent,    Editor,    Preview,} impl MarkdownStyle {    pub fn themed(font: MarkdownFont, window: &Window, cx: &App) -> Self {        let colors = cx.theme().colors();        let syntax = cx.theme().syntax().clone();        Self::themed_with_overrides(font, colors, &syntax, window, cx)    }     /// Like [`Self::themed`], but takes explicit [`ThemeColors`] and    /// [`SyntaxTheme`] so callers (e.g. the markdown preview) can render the    /// markdown using a theme other than the active editor theme.    pub fn themed_with_overrides(        font: MarkdownFont,        colors: &theme::ThemeColors,        syntax: &Arc<SyntaxTheme>,        window: &Window,        cx: &App,    ) -> Self {        let theme_settings = ThemeSettings::get_global(cx);        let is_preview = matches!(font, MarkdownFont::Preview);         let buffer_font_weight = theme_settings.buffer_font.weight;        let (buffer_font_size, ui_font_size) = match font {            MarkdownFont::Agent => (                theme_settings.agent_buffer_font_size(cx),                theme_settings.agent_ui_font_size(cx),            ),            MarkdownFont::Editor => (                theme_settings.buffer_font_size(cx),                theme_settings.ui_font_size(cx),            ),            MarkdownFont::Preview => (                theme_settings.markdown_preview_font_size(cx),                theme_settings.ui_font_size(cx),            ),        };         let body_font_family = match font {            MarkdownFont::Preview => theme_settings.markdown_preview_font_family().clone(),            MarkdownFont::Agent => theme_settings.agent_ui_font_family().clone(),            MarkdownFont::Editor => theme_settings.ui_font.family.clone(),        };        let code_font_family = match font {            MarkdownFont::Preview => theme_settings.markdown_preview_code_font_family().clone(),            MarkdownFont::Agent => theme_settings.agent_buffer_font_family().clone(),            MarkdownFont::Editor => theme_settings.buffer_font.family.clone(),        };         let mut text_style = window.text_style();        let line_height = buffer_font_size * 1.75;         text_style.refine(&TextStyleRefinement {            font_family: Some(body_font_family),            font_fallbacks: theme_settings.ui_font.fallbacks.clone(),            font_features: Some(theme_settings.ui_font.features.clone()),            font_size: Some(if is_preview {                rems(1.0).into()            } else {                ui_font_size.into()            }),            line_height: Some(line_height.into()),            color: Some(colors.text),            ..Default::default()        });         let style = MarkdownStyle {            base_text_style: text_style.clone(),            syntax: syntax.clone(),            selection_background_color: colors.element_selection_background,            rule_color: colors.border,            block_quote_border_color: colors.border,            block_quote_kind_colors: {                let status = cx.theme().status();                BlockQuoteKindColors {                    note: status.info,                    tip: status.success,                    important: status.info,                    warning: status.warning,                    caution: status.error,                }            },            code_block_overflow_x_scroll: true,            code_block: StyleRefinement {                padding: EdgesRefinement {                    top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),                    left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),                    right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),                    bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),                },                margin: EdgesRefinement {                    top: Some(Length::Definite(px(8.).into())),                    left: Some(Length::Definite(px(0.).into())),                    right: Some(Length::Definite(px(0.).into())),                    bottom: Some(Length::Definite(px(12.).into())),                },                border_style: Some(BorderStyle::Solid),                border_widths: EdgesRefinement {                    top: Some(AbsoluteLength::Pixels(px(1.))),                    left: Some(AbsoluteLength::Pixels(px(1.))),                    right: Some(AbsoluteLength::Pixels(px(1.))),                    bottom: Some(AbsoluteLength::Pixels(px(1.))),                },                border_color: Some(colors.border_variant),                background: Some(colors.editor_background.into()),                text: TextStyleRefinement {                    font_family: Some(code_font_family.clone()),                    font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),                    font_features: Some(theme_settings.buffer_font.features.clone()),                    font_size: Some(buffer_font_size.into()),                    font_weight: Some(buffer_font_weight),                    ..Default::default()                },                ..Default::default()            },            inline_code: TextStyleRefinement {                font_family: Some(code_font_family),                font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),                font_features: Some(theme_settings.buffer_font.features.clone()),                font_size: Some(buffer_font_size.into()),                font_weight: Some(buffer_font_weight),                background_color: Some(colors.editor_foreground.opacity(0.08)),                ..Default::default()            },            link: TextStyleRefinement {                background_color: Some(colors.editor_foreground.opacity(0.025)),                color: Some(colors.text_accent),                underline: Some(UnderlineStyle {                    color: Some(colors.text_accent.opacity(0.5)),                    thickness: px(1.),                    ..Default::default()                }),                ..Default::default()            },            soft_break_as_hard_break: matches!(font, MarkdownFont::Agent),            heading_level_styles: matches!(font, MarkdownFont::Agent).then_some(                HeadingLevelStyles {                    h1: Some(TextStyleRefinement {                        font_size: Some(rems(1.15).into()),                        ..Default::default()                    }),                    h2: Some(TextStyleRefinement {                        font_size: Some(rems(1.1).into()),                        ..Default::default()                    }),                    h3: Some(TextStyleRefinement {                        font_size: Some(rems(1.05).into()),                        ..Default::default()                    }),                    h4: Some(TextStyleRefinement {                        font_size: Some(rems(1.).into()),                        ..Default::default()                    }),                    h5: Some(TextStyleRefinement {                        font_size: Some(rems(0.95).into()),                        ..Default::default()                    }),                    h6: Some(TextStyleRefinement {                        font_size: Some(rems(0.875).into()),                        ..Default::default()                    }),                },            ),            ..Default::default()        };         if is_preview {            style.with_preview_overrides(colors)        } else {            style        }    }     fn with_preview_overrides(mut self, colors: &theme::ThemeColors) -> Self {        let body_font_size = rems(0.92);        self.base_text_style.font_size = body_font_size.into();        self.container_style.text.font_size = Some(body_font_size.into());         self.base_text_style.color = colors.text_muted.blend(colors.text.opacity(0.25));        self.inline_code.color = Some(colors.text);        self.heading.text.color = Some(colors.text);         self.heading_level_styles = Some(HeadingLevelStyles {            h1: Some(TextStyleRefinement {                font_size: Some(rems(1.45).into()),                ..Default::default()            }),            h2: Some(TextStyleRefinement {                font_size: Some(rems(1.3).into()),                ..Default::default()            }),            h3: Some(TextStyleRefinement {                font_size: Some(rems(1.1).into()),                ..Default::default()            }),            h4: Some(TextStyleRefinement {                font_size: Some(rems(1.01).into()),                ..Default::default()            }),            h5: Some(TextStyleRefinement {                font_size: Some(rems(0.95).into()),                ..Default::default()            }),            h6: Some(TextStyleRefinement {                font_size: Some(rems(0.85).into()),                ..Default::default()            }),        });         self.heading_border_color = Some(colors.border_variant);         self    }     pub fn with_buffer_font(mut self, cx: &App) -> Self {        let theme_settings = ThemeSettings::get_global(cx);        self.base_text_style.font_family = theme_settings.buffer_font.family.clone();        self.base_text_style.font_fallbacks = theme_settings.buffer_font.fallbacks.clone();        self.base_text_style.font_features = theme_settings.buffer_font.features.clone();        self.base_text_style.font_weight = theme_settings.buffer_font.weight;        self    }     pub fn with_agent_buffer_font(mut self, cx: &App) -> Self {        let theme_settings = ThemeSettings::get_global(cx);        self.base_text_style.font_family = theme_settings.agent_buffer_font_family().clone();        self.base_text_style.font_fallbacks = theme_settings.buffer_font.fallbacks.clone();        self.base_text_style.font_features = theme_settings.buffer_font.features.clone();        self.base_text_style.font_weight = theme_settings.buffer_font.weight;        self    }     pub fn with_muted_text(mut self, cx: &App) -> Self {        let colors = cx.theme().colors();        self.base_text_style.color = colors.text_muted;        self    }} pub struct Markdown {    source: SharedString,    selection: Selection,    pressed_link: Option<RenderedLink>,    pressed_footnote_ref: Option<RenderedFootnoteRef>,    autoscroll_request: Option<usize>,    pending_heading_scroll: Option<SharedString>,    pending_autoscroll: Option<usize>,    active_root_block: Option<usize>,    parsed_markdown: ParsedMarkdown,    images_by_source_offset: HashMap<usize, Arc<Image>>,    should_reparse: bool,    pending_parse: Option<Task<()>>,    focus_handle: FocusHandle,    language_registry: Option<Arc<LanguageRegistry>>,    fallback_code_block_language: Option<LanguageName>,    options: MarkdownOptions,    mermaid_state: MermaidState,    _mermaid_theme_subscription: Option<Subscription>,    mermaid_showing_code: HashSet<usize>,    copied_code_blocks: HashSet<ElementId>,    wrapped_code_blocks: HashSet<usize>,    code_block_scroll_handles: BTreeMap<usize, ScrollHandle>,    context_menu_link: Option<SharedString>,    context_menu_selected_text: Option<SharedString>,    context_menu_selected_markdown: Option<SharedString>,    search_highlights: Vec<Range<usize>>,    active_search_highlight: Option<usize>,} #[derive(Clone, Copy, Default)]pub struct MarkdownOptions {    pub parse_links_only: bool,    pub parse_html: bool,    pub render_mermaid_diagrams: bool,    pub parse_heading_slugs: bool,    pub render_metadata_blocks: bool,} #[derive(Clone, Copy, PartialEq, Eq)]pub enum CopyButtonVisibility {    Hidden,    AlwaysVisible,    VisibleOnHover,} #[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum WrapButtonVisibility {    Hidden,    AlwaysVisible,    VisibleOnHover,} pub enum CodeBlockRenderer {    Default {        copy_button_visibility: CopyButtonVisibility,        wrap_button_visibility: WrapButtonVisibility,        border: bool,    },    Custom {        render: CodeBlockRenderFn,        /// A function that can modify the parent container after the code block        /// content has been appended as a child element.        transform: Option<CodeBlockTransformFn>,    },} pub type CodeBlockRenderFn = Arc<    dyn Fn(        &CodeBlockKind,        &ParsedMarkdown,        Range<usize>,        CodeBlockMetadata,        &mut Window,        &App,    ) -> Div,>; pub type CodeBlockTransformFn =    Arc<dyn Fn(AnyDiv, Range<usize>, CodeBlockMetadata, &mut Window, &App) -> AnyDiv>; actions!(    markdown,    [        /// Copies the selected text to the clipboard.        Copy,        /// Copies the selected text as markdown to the clipboard.        CopyAsMarkdown    ]); enum EscapeAction {    PassThrough,    Nbsp(usize),    DoubleNewline,    PrefixBackslash,} impl EscapeAction {    fn output_len(&self, c: char) -> usize {        match self {            Self::PassThrough => c.len_utf8(),            Self::Nbsp(count) => count * '\u{00A0}'.len_utf8(),            Self::DoubleNewline => 2,            Self::PrefixBackslash => '\\'.len_utf8() + c.len_utf8(),        }    }     fn write_to(&self, c: char, output: &mut String) {

Lines 1–500 of 5940.