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. Load more
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) {