3324 lines · 140.5 KB · Rust
RawDownload
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, AnimatedLabel, 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()

Lines 1–500 of 3324.