use crate::context::{AssistantContext, ContextId, RULES_ICON, format_context_as_string};use crate::context_picker::MentionLink;use crate::thread::{ LastRestoreCheckpoint, MessageId, MessageSegment, Thread, ThreadError, ThreadEvent, ThreadFeedback,};use crate::thread_store::{RulesLoadingError, ThreadStore};use crate::tool_use::{PendingToolUseStatus, ToolUse};use crate::ui::{AddedContext, AgentNotification, AgentNotificationEvent, ContextPill};use crate::{AssistantPanel, OpenActiveThreadAsMarkdown};use anyhow::Context as _;use assistant_settings::{AssistantSettings, NotifyWhenAgentWaiting};use assistant_tool::ToolUseStatus;use collections::{HashMap, HashSet};use editor::scroll::Autoscroll;use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer};use gpui::{ AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, ClipboardItem, DefiniteLength, EdgesRefinement, Empty, Entity, EventEmitter, Focusable, Hsla, ListAlignment, 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, LanguageRegistry};use language_model::{ LanguageModelRegistry, LanguageModelRequestMessage, LanguageModelToolUseId, RequestUsage, Role, StopReason,};use markdown::parser::{CodeBlockKind, CodeBlockMetadata};use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle, ParsedMarkdown};use project::ProjectItem as _;use rope::Point;use settings::{Settings as _, update_settings_file};use std::path::Path;use std::rc::Rc;use std::sync::Arc;use std::time::Duration;use text::ToPoint;use theme::ThemeSettings;use ui::{ Disclosure, IconButton, KeyBinding, Scrollbar, ScrollbarState, TextSize, Tooltip, prelude::*,};use util::ResultExt as _;use workspace::{OpenOptions, Workspace};use zed_actions::assistant::OpenPromptLibrary; use crate::context_store::ContextStore; pub struct ActiveThread { language_registry: Arc<LanguageRegistry>, thread_store: Entity<ThreadStore>, thread: Entity<Thread>, context_store: Entity<ContextStore>, 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, EditMessageState)>, expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>, expanded_thinking_segments: HashMap<(MessageId, usize), bool>, expanded_code_blocks: HashMap<(MessageId, usize), bool>, last_error: Option<ThreadError>, last_usage: Option<RequestUsage>, 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>>,} 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))} 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(); 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.clone(), syntax: cx.theme().syntax().clone(), selection_background_color: cx.theme().players().local().selection, 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().players().local().selection, code_block_overflow_x_scroll: true, 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() }} const MAX_UNCOLLAPSED_LINES_IN_CODE_BLOCK: usize = 10; 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 = match kind { CodeBlockKind::Indented => None, CodeBlockKind::Fenced => Some( h_flex() .gap_1() .child( Icon::new(IconName::Code) .color(Color::Muted) .size(IconSize::XSmall), ) .child(Label::new("untitled").size(LabelSize::Small)) .into_any_element(), ), CodeBlockKind::FencedLang(raw_language_name) => Some( h_flex() .gap_1() .children( parsed_markdown .languages_by_name .get(raw_language_name) .and_then(|language| { language .config() .matcher .path_suffixes .iter() .find_map(|extension| { file_icons::FileIcons::get_icon(Path::new(extension), cx) }) .map(Icon::from_path) .map(|icon| icon.color(Color::Muted).size(IconSize::Small)) }), ) .child( Label::new( parsed_markdown .languages_by_name .get(raw_language_name) .map(|language| language.name().into()) .clone() .unwrap_or_else(|| raw_language_name.clone()), ) .size(LabelSize::Small), ) .into_any_element(), ), CodeBlockKind::FencedSrc(path_range) => path_range.path.file_name().map(|file_name| { let content = if let Some(parent) = path_range.path.parent() { h_flex() .ml_1() .gap_1() .child( Label::new(file_name.to_string_lossy().to_string()).size(LabelSize::Small), ) .child( Label::new(parent.to_string_lossy().to_string()) .color(Color::Muted) .size(LabelSize::Small), ) .into_any_element() } else { Label::new(path_range.path.to_string_lossy().to_string()) .size(LabelSize::Small) .ml_1() .into_any_element() }; h_flex() .id(("code-block-header-label", 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))) .tooltip(Tooltip::text("Jump to File")) .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| { if let Some(project_path) = workspace .project() .read(cx) .find_project_path(&path_range.path, cx) { let target = path_range.range.as_ref().map(|range| { Point::new( // Line number is 1-based range.start.line.saturating_sub(1), range.start.col.unwrap_or(0), ) }); let open_task = workspace.open_path( project_path, None, true, window, cx, ); window .spawn(cx, async move |cx| { let item = open_task.await?; if let Some(target) = target { if let Some(active_editor) = item.downcast::<Editor>() { active_editor .downgrade() .update_in(cx, |editor, window, cx| { editor .go_to_singleton_buffer_point( target, window, cx, ); }) .log_err(); } } anyhow::Ok(()) }) .detach_and_log_err(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) .expanded_code_blocks .get(&(message_id, ix)) .copied() .unwrap_or(false); let codeblock_header_bg = cx .theme() .colors() .element_background .blend(cx.theme().colors().editor_foreground.opacity(0.01));Lines 1–500 of 3375. Load more
use crate::context::{AssistantContext, ContextId, RULES_ICON, format_context_as_string}; use crate::context_picker::MentionLink; use crate::thread::{ LastRestoreCheckpoint, MessageId, MessageSegment, Thread, ThreadError, ThreadEvent, ThreadFeedback, }; use crate::thread_store::{RulesLoadingError, ThreadStore}; use crate::tool_use::{PendingToolUseStatus, ToolUse}; use crate::ui::{AddedContext, AgentNotification, AgentNotificationEvent, ContextPill}; use crate::{AssistantPanel, OpenActiveThreadAsMarkdown}; use anyhow::Context as _; use assistant_settings::{AssistantSettings, NotifyWhenAgentWaiting}; use assistant_tool::ToolUseStatus; use collections::{HashMap, HashSet}; use editor::scroll::Autoscroll; use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer}; use gpui::{ AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, ClipboardItem, DefiniteLength, EdgesRefinement, Empty, Entity, EventEmitter, Focusable, Hsla, ListAlignment, 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, LanguageRegistry}; use language_model::{ LanguageModelRegistry, LanguageModelRequestMessage, LanguageModelToolUseId, RequestUsage, Role, StopReason, }; use markdown::parser::{CodeBlockKind, CodeBlockMetadata}; use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle, ParsedMarkdown}; use project::ProjectItem as _; use rope::Point; use settings::{Settings as _, update_settings_file}; use std::path::Path; use std::rc::Rc; use std::sync::Arc; use std::time::Duration; use text::ToPoint; use theme::ThemeSettings; use ui::{ Disclosure, IconButton, KeyBinding, Scrollbar, ScrollbarState, TextSize, Tooltip, prelude::*, }; use util::ResultExt as _; use workspace::{OpenOptions, Workspace}; use zed_actions::assistant::OpenPromptLibrary; use crate::context_store::ContextStore; pub struct ActiveThread { language_registry: Arc<LanguageRegistry>, thread_store: Entity<ThreadStore>, thread: Entity<Thread>, context_store: Entity<ContextStore>, 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, EditMessageState)>, expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>, expanded_thinking_segments: HashMap<(MessageId, usize), bool>, expanded_code_blocks: HashMap<(MessageId, usize), bool>, last_error: Option<ThreadError>, last_usage: Option<RequestUsage>, 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>>, } 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)) } 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(); 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.clone(), syntax: cx.theme().syntax().clone(), selection_background_color: cx.theme().players().local().selection, 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().players().local().selection, code_block_overflow_x_scroll: true, 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() } } const MAX_UNCOLLAPSED_LINES_IN_CODE_BLOCK: usize = 10; 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 = match kind { CodeBlockKind::Indented => None, CodeBlockKind::Fenced => Some( h_flex() .gap_1() .child( Icon::new(IconName::Code) .color(Color::Muted) .size(IconSize::XSmall), ) .child(Label::new("untitled").size(LabelSize::Small)) .into_any_element(), ), CodeBlockKind::FencedLang(raw_language_name) => Some( h_flex() .gap_1() .children( parsed_markdown .languages_by_name .get(raw_language_name) .and_then(|language| { language .config() .matcher .path_suffixes .iter() .find_map(|extension| { file_icons::FileIcons::get_icon(Path::new(extension), cx) }) .map(Icon::from_path) .map(|icon| icon.color(Color::Muted).size(IconSize::Small)) }), ) .child( Label::new( parsed_markdown .languages_by_name .get(raw_language_name) .map(|language| language.name().into()) .clone() .unwrap_or_else(|| raw_language_name.clone()), ) .size(LabelSize::Small), ) .into_any_element(), ), CodeBlockKind::FencedSrc(path_range) => path_range.path.file_name().map(|file_name| { let content = if let Some(parent) = path_range.path.parent() { h_flex() .ml_1() .gap_1() .child( Label::new(file_name.to_string_lossy().to_string()).size(LabelSize::Small), ) .child( Label::new(parent.to_string_lossy().to_string()) .color(Color::Muted) .size(LabelSize::Small), ) .into_any_element() } else { Label::new(path_range.path.to_string_lossy().to_string()) .size(LabelSize::Small) .ml_1() .into_any_element() }; h_flex() .id(("code-block-header-label", 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))) .tooltip(Tooltip::text("Jump to File")) .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| { if let Some(project_path) = workspace .project() .read(cx) .find_project_path(&path_range.path, cx) { let target = path_range.range.as_ref().map(|range| { Point::new( // Line number is 1-based range.start.line.saturating_sub(1), range.start.col.unwrap_or(0), ) }); let open_task = workspace.open_path( project_path, None, true, window, cx, ); window .spawn(cx, async move |cx| { let item = open_task.await?; if let Some(target) = target { if let Some(active_editor) = item.downcast::<Editor>() { active_editor .downgrade() .update_in(cx, |editor, window, cx| { editor .go_to_singleton_buffer_point( target, window, cx, ); }) .log_err(); } } anyhow::Ok(()) }) .detach_and_log_err(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) .expanded_code_blocks .get(&(message_id, ix)) .copied() .unwrap_or(false); let codeblock_header_bg = cx .theme() .colors() .element_background .blend(cx.theme().colors().editor_foreground.opacity(0.01));