use crate::context_picker::{ContextPicker, MentionLink};use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};use crate::message_editor::{extract_message_creases, insert_message_creases};use crate::ui::{AddedContext, AgentNotification, AgentNotificationEvent, ContextPill};use crate::{AgentPanel, ModelUsageContext};use agent::{ ContextStore, LastRestoreCheckpoint, MessageCrease, MessageId, MessageSegment, TextThreadStore, Thread, ThreadError, ThreadEvent, ThreadFeedback, ThreadStore, ThreadSummary, context::{self, AgentContextHandle, RULES_ICON}, thread_store::RulesLoadingError, tool_use::{PendingToolUseStatus, ToolUse},};use agent_settings::{AgentSettings, NotifyWhenAgentWaiting};use anyhow::Context as _;use assistant_tool::ToolUseStatus;use audio::{Audio, Sound};use collections::{HashMap, HashSet};use editor::actions::{MoveUp, Paste};use editor::scroll::Autoscroll;use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer, SelectionEffects};use gpui::{ AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, ClipboardEntry, ClipboardItem, DefiniteLength, EdgesRefinement, Empty, Entity, EventEmitter, Focusable, Hsla, ListAlignment, ListOffset, ListState, MouseButton, PlatformDisplay, ScrollHandle, Stateful, StyleRefinement, Subscription, Task, TextStyle, TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity, WindowHandle, linear_color_stop, linear_gradient, list, percentage, pulsating_between,};use language::{Buffer, Language, LanguageRegistry};use language_model::{ LanguageModelRequestMessage, LanguageModelToolUseId, MessageContent, Role, StopReason,};use markdown::parser::{CodeBlockKind, CodeBlockMetadata};use markdown::{ HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle, ParsedMarkdown, PathWithRange,};use project::{ProjectEntryId, ProjectItem as _};use rope::Point;use settings::{Settings as _, SettingsStore, update_settings_file};use std::ffi::OsStr;use std::path::Path;use std::rc::Rc;use std::sync::Arc;use std::time::Duration;use text::ToPoint;use theme::ThemeSettings;use ui::{ Banner, Disclosure, KeyBinding, PopoverMenuHandle, Scrollbar, ScrollbarState, TextSize, Tooltip, prelude::*,};use util::ResultExt as _;use util::markdown::MarkdownCodeBlock;use workspace::{CollaboratorId, Workspace};use zed_actions::assistant::OpenRulesLibrary;use zed_llm_client::CompletionIntent; const CODEBLOCK_CONTAINER_GROUP: &str = "codeblock_container";const EDIT_PREVIOUS_MESSAGE_MIN_LINES: usize = 1;const RESPONSE_PADDING_X: Pixels = px(19.); pub struct ActiveThread { context_store: Entity<ContextStore>, language_registry: Arc<LanguageRegistry>, thread_store: Entity<ThreadStore>, text_thread_store: Entity<TextThreadStore>, thread: Entity<Thread>, workspace: WeakEntity<Workspace>, save_thread_task: Option<Task<()>>, messages: Vec<MessageId>, list_state: ListState, scrollbar_state: ScrollbarState, show_scrollbar: bool, hide_scrollbar_task: Option<Task<()>>, rendered_messages_by_id: HashMap<MessageId, RenderedMessage>, rendered_tool_uses: HashMap<LanguageModelToolUseId, RenderedToolUse>, editing_message: Option<(MessageId, EditingMessageState)>, expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>, expanded_thinking_segments: HashMap<(MessageId, usize), bool>, expanded_code_blocks: HashMap<(MessageId, usize), bool>, last_error: Option<ThreadError>, notifications: Vec<WindowHandle<AgentNotification>>, copied_code_block_ids: HashSet<(MessageId, usize)>, _subscriptions: Vec<Subscription>, notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>, open_feedback_editors: HashMap<MessageId, Entity<Editor>>, _load_edited_message_context_task: Option<Task<()>>,} struct RenderedMessage { language_registry: Arc<LanguageRegistry>, segments: Vec<RenderedMessageSegment>,} #[derive(Clone)]struct RenderedToolUse { label: Entity<Markdown>, input: Entity<Markdown>, output: Entity<Markdown>,} impl RenderedMessage { fn from_segments( segments: &[MessageSegment], language_registry: Arc<LanguageRegistry>, cx: &mut App, ) -> Self { let mut this = Self { language_registry, segments: Vec::with_capacity(segments.len()), }; for segment in segments { this.push_segment(segment, cx); } this } fn append_thinking(&mut self, text: &String, cx: &mut App) { if let Some(RenderedMessageSegment::Thinking { content, scroll_handle, }) = self.segments.last_mut() { content.update(cx, |markdown, cx| { markdown.append(text, cx); }); scroll_handle.scroll_to_bottom(); } else { self.segments.push(RenderedMessageSegment::Thinking { content: parse_markdown(text.into(), self.language_registry.clone(), cx), scroll_handle: ScrollHandle::default(), }); } } fn append_text(&mut self, text: &String, cx: &mut App) { if let Some(RenderedMessageSegment::Text(markdown)) = self.segments.last_mut() { markdown.update(cx, |markdown, cx| markdown.append(text, cx)); } else { self.segments .push(RenderedMessageSegment::Text(parse_markdown( SharedString::from(text), self.language_registry.clone(), cx, ))); } } fn push_segment(&mut self, segment: &MessageSegment, cx: &mut App) { match segment { MessageSegment::Thinking { text, .. } => { self.segments.push(RenderedMessageSegment::Thinking { content: parse_markdown(text.into(), self.language_registry.clone(), cx), scroll_handle: ScrollHandle::default(), }) } MessageSegment::Text(text) => { self.segments .push(RenderedMessageSegment::Text(parse_markdown( text.into(), self.language_registry.clone(), cx, ))) } MessageSegment::RedactedThinking(_) => {} }; }} enum RenderedMessageSegment { Thinking { content: Entity<Markdown>, scroll_handle: ScrollHandle, }, Text(Entity<Markdown>),} fn parse_markdown( text: SharedString, language_registry: Arc<LanguageRegistry>, cx: &mut App,) -> Entity<Markdown> { cx.new(|cx| Markdown::new(text, Some(language_registry), None, cx))} pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle { let theme_settings = ThemeSettings::get_global(cx); let colors = cx.theme().colors(); let ui_font_size = TextSize::Default.rems(cx); let buffer_font_size = TextSize::Small.rems(cx); let mut text_style = window.text_style(); let line_height = buffer_font_size * 1.75; text_style.refine(&TextStyleRefinement { font_family: Some(theme_settings.ui_font.family.clone()), font_fallbacks: theme_settings.ui_font.fallbacks.clone(), font_features: Some(theme_settings.ui_font.features.clone()), font_size: Some(ui_font_size.into()), line_height: Some(line_height.into()), color: Some(cx.theme().colors().text), ..Default::default() }); MarkdownStyle { base_text_style: text_style.clone(), syntax: cx.theme().syntax().clone(), selection_background_color: cx.theme().colors().element_selection_background, code_block_overflow_x_scroll: true, table_overflow_x_scroll: true, heading_level_styles: 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() }), }), code_block: StyleRefinement { padding: EdgesRefinement { top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), }, background: Some(colors.editor_background.into()), text: Some(TextStyleRefinement { font_family: Some(theme_settings.buffer_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()), ..Default::default() }), ..Default::default() }, inline_code: TextStyleRefinement { font_family: Some(theme_settings.buffer_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()), background_color: Some(colors.editor_foreground.opacity(0.08)), ..Default::default() }, link: TextStyleRefinement { background_color: Some(colors.editor_foreground.opacity(0.025)), underline: Some(UnderlineStyle { color: Some(colors.text_accent.opacity(0.5)), thickness: px(1.), ..Default::default() }), ..Default::default() }, link_callback: Some(Rc::new(move |url, cx| { if MentionLink::is_valid(url) { let colors = cx.theme().colors(); Some(TextStyleRefinement { background_color: Some(colors.element_background), ..Default::default() }) } else { None } })), ..Default::default() }} fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle { let theme_settings = ThemeSettings::get_global(cx); let colors = cx.theme().colors(); let ui_font_size = TextSize::Default.rems(cx); let buffer_font_size = TextSize::Small.rems(cx); let mut text_style = window.text_style(); text_style.refine(&TextStyleRefinement { font_family: Some(theme_settings.ui_font.family.clone()), font_fallbacks: theme_settings.ui_font.fallbacks.clone(), font_features: Some(theme_settings.ui_font.features.clone()), font_size: Some(ui_font_size.into()), color: Some(cx.theme().colors().text), ..Default::default() }); MarkdownStyle { base_text_style: text_style, syntax: cx.theme().syntax().clone(), selection_background_color: cx.theme().colors().element_selection_background, code_block_overflow_x_scroll: false, code_block: StyleRefinement { margin: EdgesRefinement::default(), padding: EdgesRefinement::default(), background: Some(colors.editor_background.into()), border_color: None, border_widths: EdgesRefinement::default(), text: Some(TextStyleRefinement { font_family: Some(theme_settings.buffer_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()), ..Default::default() }), ..Default::default() }, inline_code: TextStyleRefinement { font_family: Some(theme_settings.buffer_font.family.clone()), font_fallbacks: theme_settings.buffer_font.fallbacks.clone(), font_features: Some(theme_settings.buffer_font.features.clone()), font_size: Some(TextSize::XSmall.rems(cx).into()), ..Default::default() }, heading: StyleRefinement { text: Some(TextStyleRefinement { font_size: Some(ui_font_size.into()), ..Default::default() }), ..Default::default() }, ..Default::default() }} fn render_markdown_code_block( message_id: MessageId, ix: usize, kind: &CodeBlockKind, parsed_markdown: &ParsedMarkdown, metadata: CodeBlockMetadata, active_thread: Entity<ActiveThread>, workspace: WeakEntity<Workspace>, _window: &Window, cx: &App,) -> Div { let label_size = rems(0.8125); let label = match kind { CodeBlockKind::Indented => None, CodeBlockKind::Fenced => Some( h_flex() .px_1() .gap_1() .child( Icon::new(IconName::Code) .color(Color::Muted) .size(IconSize::XSmall), ) .child(div().text_size(label_size).child("Plain Text")) .into_any_element(), ), CodeBlockKind::FencedLang(raw_language_name) => Some(render_code_language( parsed_markdown.languages_by_name.get(raw_language_name), raw_language_name.clone(), cx, )), CodeBlockKind::FencedSrc(path_range) => path_range.path.file_name().map(|file_name| { // We tell the model to use /dev/null for the path instead of using ```language // because otherwise it consistently fails to use code citations. if path_range.path.starts_with("/dev/null") { let ext = path_range .path .extension() .and_then(OsStr::to_str) .map(|str| SharedString::new(str.to_string())) .unwrap_or_default(); render_code_language( parsed_markdown .languages_by_path .get(&path_range.path) .or_else(|| parsed_markdown.languages_by_name.get(&ext)), ext, cx, ) } else { let content = if let Some(parent) = path_range.path.parent() { let file_name = file_name.to_string_lossy().to_string(); let path = parent.to_string_lossy().to_string(); let path_and_file = format!("{}/{}", path, file_name); h_flex() .id(("code-block-header-label", ix)) .ml_1() .gap_1() .child(div().text_size(label_size).child(file_name)) .child(Label::new(path).color(Color::Muted).size(LabelSize::Small)) .tooltip(move |window, cx| { Tooltip::with_meta( "Jump to File", None, path_and_file.clone(), window, cx, ) }) .into_any_element() } else { div() .ml_1() .text_size(label_size) .child(path_range.path.to_string_lossy().to_string()) .into_any_element() }; h_flex() .id(("code-block-header-button", ix)) .w_full() .max_w_full() .px_1() .gap_0p5() .cursor_pointer() .rounded_sm() .hover(|item| item.bg(cx.theme().colors().element_hover.opacity(0.5))) .child( h_flex() .gap_0p5() .children( file_icons::FileIcons::get_icon(&path_range.path, cx) .map(Icon::from_path) .map(|icon| icon.color(Color::Muted).size(IconSize::XSmall)), ) .child(content) .child( Icon::new(IconName::ArrowUpRight) .size(IconSize::XSmall) .color(Color::Ignored), ), ) .on_click({ let path_range = path_range.clone(); move |_, window, cx| { workspace .update(cx, |workspace, cx| { open_path(&path_range, window, workspace, cx) }) .ok(); } }) .into_any_element() } }), }; let codeblock_was_copied = active_thread .read(cx) .copied_code_block_ids .contains(&(message_id, ix)); let is_expanded = active_thread.read(cx).is_codeblock_expanded(message_id, ix); let codeblock_header_bg = cx .theme() .colors() .element_background .blend(cx.theme().colors().editor_foreground.opacity(0.025)); let control_buttons = h_flex() .visible_on_hover(CODEBLOCK_CONTAINER_GROUP) .absolute() .top_0() .right_0() .h_full() .bg(codeblock_header_bg) .rounded_tr_md() .px_1() .gap_1() .child( IconButton::new( ("copy-markdown-code", ix), if codeblock_was_copied { IconName::Check } else { IconName::Copy }, ) .icon_color(Color::Muted) .shape(ui::IconButtonShape::Square) .tooltip(Tooltip::text("Copy Code")) .on_click({ let active_thread = active_thread.clone(); let parsed_markdown = parsed_markdown.clone(); let code_block_range = metadata.content_range.clone(); move |_event, _window, cx| { active_thread.update(cx, |this, cx| { this.copied_code_block_ids.insert((message_id, ix)); Lines 1–500 of 4163. Load more
use crate::context_picker::{ContextPicker, MentionLink}; use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind}; use crate::message_editor::{extract_message_creases, insert_message_creases}; use crate::ui::{AddedContext, AgentNotification, AgentNotificationEvent, ContextPill}; use crate::{AgentPanel, ModelUsageContext}; use agent::{ ContextStore, LastRestoreCheckpoint, MessageCrease, MessageId, MessageSegment, TextThreadStore, Thread, ThreadError, ThreadEvent, ThreadFeedback, ThreadStore, ThreadSummary, context::{self, AgentContextHandle, RULES_ICON}, thread_store::RulesLoadingError, tool_use::{PendingToolUseStatus, ToolUse}, }; use agent_settings::{AgentSettings, NotifyWhenAgentWaiting}; use anyhow::Context as _; use assistant_tool::ToolUseStatus; use audio::{Audio, Sound}; use collections::{HashMap, HashSet}; use editor::actions::{MoveUp, Paste}; use editor::scroll::Autoscroll; use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer, SelectionEffects}; use gpui::{ AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, ClipboardEntry, ClipboardItem, DefiniteLength, EdgesRefinement, Empty, Entity, EventEmitter, Focusable, Hsla, ListAlignment, ListOffset, ListState, MouseButton, PlatformDisplay, ScrollHandle, Stateful, StyleRefinement, Subscription, Task, TextStyle, TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity, WindowHandle, linear_color_stop, linear_gradient, list, percentage, pulsating_between, }; use language::{Buffer, Language, LanguageRegistry}; use language_model::{ LanguageModelRequestMessage, LanguageModelToolUseId, MessageContent, Role, StopReason, }; use markdown::parser::{CodeBlockKind, CodeBlockMetadata}; use markdown::{ HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle, ParsedMarkdown, PathWithRange, }; use project::{ProjectEntryId, ProjectItem as _}; use rope::Point; use settings::{Settings as _, SettingsStore, update_settings_file}; use std::ffi::OsStr; use std::path::Path; use std::rc::Rc; use std::sync::Arc; use std::time::Duration; use text::ToPoint; use theme::ThemeSettings; use ui::{ Banner, Disclosure, KeyBinding, PopoverMenuHandle, Scrollbar, ScrollbarState, TextSize, Tooltip, prelude::*, }; use util::ResultExt as _; use util::markdown::MarkdownCodeBlock; use workspace::{CollaboratorId, Workspace}; use zed_actions::assistant::OpenRulesLibrary; use zed_llm_client::CompletionIntent; const CODEBLOCK_CONTAINER_GROUP: &str = "codeblock_container"; const EDIT_PREVIOUS_MESSAGE_MIN_LINES: usize = 1; const RESPONSE_PADDING_X: Pixels = px(19.); pub struct ActiveThread { context_store: Entity<ContextStore>, language_registry: Arc<LanguageRegistry>, thread_store: Entity<ThreadStore>, text_thread_store: Entity<TextThreadStore>, thread: Entity<Thread>, workspace: WeakEntity<Workspace>, save_thread_task: Option<Task<()>>, messages: Vec<MessageId>, list_state: ListState, scrollbar_state: ScrollbarState, show_scrollbar: bool, hide_scrollbar_task: Option<Task<()>>, rendered_messages_by_id: HashMap<MessageId, RenderedMessage>, rendered_tool_uses: HashMap<LanguageModelToolUseId, RenderedToolUse>, editing_message: Option<(MessageId, EditingMessageState)>, expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>, expanded_thinking_segments: HashMap<(MessageId, usize), bool>, expanded_code_blocks: HashMap<(MessageId, usize), bool>, last_error: Option<ThreadError>, notifications: Vec<WindowHandle<AgentNotification>>, copied_code_block_ids: HashSet<(MessageId, usize)>, _subscriptions: Vec<Subscription>, notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>, open_feedback_editors: HashMap<MessageId, Entity<Editor>>, _load_edited_message_context_task: Option<Task<()>>, } struct RenderedMessage { language_registry: Arc<LanguageRegistry>, segments: Vec<RenderedMessageSegment>, } #[derive(Clone)] struct RenderedToolUse { label: Entity<Markdown>, input: Entity<Markdown>, output: Entity<Markdown>, } impl RenderedMessage { fn from_segments( segments: &[MessageSegment], language_registry: Arc<LanguageRegistry>, cx: &mut App, ) -> Self { let mut this = Self { language_registry, segments: Vec::with_capacity(segments.len()), }; for segment in segments { this.push_segment(segment, cx); } this } fn append_thinking(&mut self, text: &String, cx: &mut App) { if let Some(RenderedMessageSegment::Thinking { content, scroll_handle, }) = self.segments.last_mut() { content.update(cx, |markdown, cx| { markdown.append(text, cx); }); scroll_handle.scroll_to_bottom(); } else { self.segments.push(RenderedMessageSegment::Thinking { content: parse_markdown(text.into(), self.language_registry.clone(), cx), scroll_handle: ScrollHandle::default(), }); } } fn append_text(&mut self, text: &String, cx: &mut App) { if let Some(RenderedMessageSegment::Text(markdown)) = self.segments.last_mut() { markdown.update(cx, |markdown, cx| markdown.append(text, cx)); } else { self.segments .push(RenderedMessageSegment::Text(parse_markdown( SharedString::from(text), self.language_registry.clone(), cx, ))); } } fn push_segment(&mut self, segment: &MessageSegment, cx: &mut App) { match segment { MessageSegment::Thinking { text, .. } => { self.segments.push(RenderedMessageSegment::Thinking { content: parse_markdown(text.into(), self.language_registry.clone(), cx), scroll_handle: ScrollHandle::default(), }) } MessageSegment::Text(text) => { self.segments .push(RenderedMessageSegment::Text(parse_markdown( text.into(), self.language_registry.clone(), cx, ))) } MessageSegment::RedactedThinking(_) => {} }; } } enum RenderedMessageSegment { Thinking { content: Entity<Markdown>, scroll_handle: ScrollHandle, }, Text(Entity<Markdown>), } fn parse_markdown( text: SharedString, language_registry: Arc<LanguageRegistry>, cx: &mut App, ) -> Entity<Markdown> { cx.new(|cx| Markdown::new(text, Some(language_registry), None, cx)) } pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle { let theme_settings = ThemeSettings::get_global(cx); let colors = cx.theme().colors(); let ui_font_size = TextSize::Default.rems(cx); let buffer_font_size = TextSize::Small.rems(cx); let mut text_style = window.text_style(); let line_height = buffer_font_size * 1.75; text_style.refine(&TextStyleRefinement { font_family: Some(theme_settings.ui_font.family.clone()), font_fallbacks: theme_settings.ui_font.fallbacks.clone(), font_features: Some(theme_settings.ui_font.features.clone()), font_size: Some(ui_font_size.into()), line_height: Some(line_height.into()), color: Some(cx.theme().colors().text), ..Default::default() }); MarkdownStyle { base_text_style: text_style.clone(), syntax: cx.theme().syntax().clone(), selection_background_color: cx.theme().colors().element_selection_background, code_block_overflow_x_scroll: true, table_overflow_x_scroll: true, heading_level_styles: 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() }), }), code_block: StyleRefinement { padding: EdgesRefinement { top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))), }, background: Some(colors.editor_background.into()), text: Some(TextStyleRefinement { font_family: Some(theme_settings.buffer_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()), ..Default::default() }), ..Default::default() }, inline_code: TextStyleRefinement { font_family: Some(theme_settings.buffer_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()), background_color: Some(colors.editor_foreground.opacity(0.08)), ..Default::default() }, link: TextStyleRefinement { background_color: Some(colors.editor_foreground.opacity(0.025)), underline: Some(UnderlineStyle { color: Some(colors.text_accent.opacity(0.5)), thickness: px(1.), ..Default::default() }), ..Default::default() }, link_callback: Some(Rc::new(move |url, cx| { if MentionLink::is_valid(url) { let colors = cx.theme().colors(); Some(TextStyleRefinement { background_color: Some(colors.element_background), ..Default::default() }) } else { None } })), ..Default::default() } } fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle { let theme_settings = ThemeSettings::get_global(cx); let colors = cx.theme().colors(); let ui_font_size = TextSize::Default.rems(cx); let buffer_font_size = TextSize::Small.rems(cx); let mut text_style = window.text_style(); text_style.refine(&TextStyleRefinement { font_family: Some(theme_settings.ui_font.family.clone()), font_fallbacks: theme_settings.ui_font.fallbacks.clone(), font_features: Some(theme_settings.ui_font.features.clone()), font_size: Some(ui_font_size.into()), color: Some(cx.theme().colors().text), ..Default::default() }); MarkdownStyle { base_text_style: text_style, syntax: cx.theme().syntax().clone(), selection_background_color: cx.theme().colors().element_selection_background, code_block_overflow_x_scroll: false, code_block: StyleRefinement { margin: EdgesRefinement::default(), padding: EdgesRefinement::default(), background: Some(colors.editor_background.into()), border_color: None, border_widths: EdgesRefinement::default(), text: Some(TextStyleRefinement { font_family: Some(theme_settings.buffer_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()), ..Default::default() }), ..Default::default() }, inline_code: TextStyleRefinement { font_family: Some(theme_settings.buffer_font.family.clone()), font_fallbacks: theme_settings.buffer_font.fallbacks.clone(), font_features: Some(theme_settings.buffer_font.features.clone()), font_size: Some(TextSize::XSmall.rems(cx).into()), ..Default::default() }, heading: StyleRefinement { text: Some(TextStyleRefinement { font_size: Some(ui_font_size.into()), ..Default::default() }), ..Default::default() }, ..Default::default() } } fn render_markdown_code_block( message_id: MessageId, ix: usize, kind: &CodeBlockKind, parsed_markdown: &ParsedMarkdown, metadata: CodeBlockMetadata, active_thread: Entity<ActiveThread>, workspace: WeakEntity<Workspace>, _window: &Window, cx: &App, ) -> Div { let label_size = rems(0.8125); let label = match kind { CodeBlockKind::Indented => None, CodeBlockKind::Fenced => Some( h_flex() .px_1() .gap_1() .child( Icon::new(IconName::Code) .color(Color::Muted) .size(IconSize::XSmall), ) .child(div().text_size(label_size).child("Plain Text")) .into_any_element(), ), CodeBlockKind::FencedLang(raw_language_name) => Some(render_code_language( parsed_markdown.languages_by_name.get(raw_language_name), raw_language_name.clone(), cx, )), CodeBlockKind::FencedSrc(path_range) => path_range.path.file_name().map(|file_name| { // We tell the model to use /dev/null for the path instead of using ```language // because otherwise it consistently fails to use code citations. if path_range.path.starts_with("/dev/null") { let ext = path_range .path .extension() .and_then(OsStr::to_str) .map(|str| SharedString::new(str.to_string())) .unwrap_or_default(); render_code_language( parsed_markdown .languages_by_path .get(&path_range.path) .or_else(|| parsed_markdown.languages_by_name.get(&ext)), ext, cx, ) } else { let content = if let Some(parent) = path_range.path.parent() { let file_name = file_name.to_string_lossy().to_string(); let path = parent.to_string_lossy().to_string(); let path_and_file = format!("{}/{}", path, file_name); h_flex() .id(("code-block-header-label", ix)) .ml_1() .gap_1() .child(div().text_size(label_size).child(file_name)) .child(Label::new(path).color(Color::Muted).size(LabelSize::Small)) .tooltip(move |window, cx| { Tooltip::with_meta( "Jump to File", None, path_and_file.clone(), window, cx, ) }) .into_any_element() } else { div() .ml_1() .text_size(label_size) .child(path_range.path.to_string_lossy().to_string()) .into_any_element() }; h_flex() .id(("code-block-header-button", ix)) .w_full() .max_w_full() .px_1() .gap_0p5() .cursor_pointer() .rounded_sm() .hover(|item| item.bg(cx.theme().colors().element_hover.opacity(0.5))) .child( h_flex() .gap_0p5() .children( file_icons::FileIcons::get_icon(&path_range.path, cx) .map(Icon::from_path) .map(|icon| icon.color(Color::Muted).size(IconSize::XSmall)), ) .child(content) .child( Icon::new(IconName::ArrowUpRight) .size(IconSize::XSmall) .color(Color::Ignored), ), ) .on_click({ let path_range = path_range.clone(); move |_, window, cx| { workspace .update(cx, |workspace, cx| { open_path(&path_range, window, workspace, cx) }) .ok(); } }) .into_any_element() } }), }; let codeblock_was_copied = active_thread .read(cx) .copied_code_block_ids .contains(&(message_id, ix)); let is_expanded = active_thread.read(cx).is_codeblock_expanded(message_id, ix); let codeblock_header_bg = cx .theme() .colors() .element_background .blend(cx.theme().colors().editor_foreground.opacity(0.025)); let control_buttons = h_flex() .visible_on_hover(CODEBLOCK_CONTAINER_GROUP) .absolute() .top_0() .right_0() .h_full() .bg(codeblock_header_bg) .rounded_tr_md() .px_1() .gap_1() .child( IconButton::new( ("copy-markdown-code", ix), if codeblock_was_copied { IconName::Check } else { IconName::Copy }, ) .icon_color(Color::Muted) .shape(ui::IconButtonShape::Square) .tooltip(Tooltip::text("Copy Code")) .on_click({ let active_thread = active_thread.clone(); let parsed_markdown = parsed_markdown.clone(); let code_block_range = metadata.content_range.clone(); move |_event, _window, cx| { active_thread.update(cx, |this, cx| { this.copied_code_block_ids.insert((message_id, ix));